From 8e6181da005d8d57cde0e3cf70e4084d199ae9c0 Mon Sep 17 00:00:00 2001 From: Matthias Kay Date: Thu, 2 Feb 2023 21:15:28 +0100 Subject: [PATCH 01/13] add panic switch off --- README.md | 16 +++++++--- lambda/panic_button_switch_off.py | 22 +++++++++++++ locals.tf | 3 ++ panic-button.tf | 52 +++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 lambda/panic_button_switch_off.py create mode 100644 panic-button.tf diff --git a/README.md b/README.md index c894a3c..21b821a 100644 --- a/README.md +++ b/README.md @@ -197,13 +197,14 @@ way you can access the database, Redis cluster, ... directly from your localhost | Name | Version | |------|---------| +| [archive](#provider\_archive) | 2.3.0 | | [aws](#provider\_aws) | 4.24.0 | ## Modules | Name | Source | Version | |------|--------|---------| -| [instance\_profile\_role](#module\_instance\_profile\_role) | terraform-aws-modules/iam/aws//modules/iam-assumable-role | 5.2.0 | +| [instance\_profile\_role](#module\_instance\_profile\_role) | terraform-aws-modules/iam/aws//modules/iam-assumable-role | 5.11.1 | ## Resources @@ -218,25 +219,32 @@ way you can access the database, Redis cluster, ... directly from your localhost | [aws_autoscaling_schedule.on_spot_up](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_schedule) | resource | | [aws_iam_policy.access_bastion](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_role.access_bastion](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role_policy_attachment.access_bastion](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_lambda_function.panic_button_switch_off](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource | | [aws_launch_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_configuration) | resource | | [aws_launch_template.manual_start](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template) | resource | | [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | | [aws_security_group_rule.egress_open_ports](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | | [aws_security_group_rule.egress_ssm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [archive_file.panic_button_lambda_switch_off](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source | | [aws_ami.latest_amazon_linux](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_iam_policy_document.access_bastion](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.lambda_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_region.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| [ami\_name\_filter](#input\_ami\_name\_filter) | The search filter string for the bastion AMI. | `string` | `"amzn2-ami-hvm-*-x86_64-ebs"` | no | | [bastion\_access\_tag\_value](#input\_bastion\_access\_tag\_value) | Value added as tag 'bastion-access' of the launched EC2 instance to be used to restrict access to the machine vie IAM. | `string` | `"developer"` | no | | [egress\_open\_tcp\_ports](#input\_egress\_open\_tcp\_ports) | The list of TCP ports to open for outgoing traffic. | `list(number)` | n/a | yes | -| [iam\_role\_path](#input\_iam\_role\_path) | Role path for the created bastion instance profile. Must end with '/' | `string` | `"/"` | no | -| [iam\_user\_arns](#input\_iam\_user\_arns) | ARNs of the user who are allowed to assume the role giving access to the bastion host. | `list(string)` | n/a | yes | -| [instance](#input\_instance) | Defines the basic parameters for the EC2 instance used as Bastion host |
object({
type = string # EC2 instance type
desired_capacity = number # number of EC2 instances to run
root_volume_size = number # in GB
enable_monitoring = bool

enable_spot = bool
})
|
{
"desired_capacity": 1,
"enable_monitoring": false,
"enable_spot": false,
"root_volume_size": 8,
"type": "t3.nano"
}
| no | +| [iam\_role\_path](#input\_iam\_role\_path) | Role path for the created bastion instance profile. Must end with '/'. Not used if instance["profile\_name"] is set. | `string` | `"/"` | no | +| [iam\_user\_arns](#input\_iam\_user\_arns) | ARNs of the user who are allowed to assume the role giving access to the bastion host. Not used if instance["profile\_name"] is set. | `list(string)` | n/a | yes | +| [instance](#input\_instance) | Defines the basic parameters for the EC2 instance used as Bastion host |
object({
type = string # EC2 instance type
desired_capacity = number # number of EC2 instances to run
root_volume_size = number # in GB
enable_monitoring = bool

enable_spot = bool

profile_name = string
})
|
{
"desired_capacity": 1,
"enable_monitoring": false,
"enable_spot": false,
"profile_name": "",
"root_volume_size": 8,
"type": "t3.nano"
}
| no | +| [instances\_distribution](#input\_instances\_distribution) | Defines the parameters for mixed instances policy auto scaling |
object({
on_demand_base_capacity = number # absolute minimum amount of on_demand instances
on_demand_percentage_above_base_capacity = number # percentage split between on-demand and Spot instances
spot_allocation_strategy = string
})
|
{
"on_demand_base_capacity": 0,
"on_demand_percentage_above_base_capacity": 0,
"spot_allocation_strategy": "lowest-price"
}
| no | | [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key used to encrypt the resources. | `string` | `null` | no | | [resource\_names](#input\_resource\_names) | Settings for generating resource names. Set the prefix and the separator according to your company style guide. |
object({
prefix = string
separator = string
})
|
{
"prefix": "bastion",
"separator": "-"
}
| no | | [schedule](#input\_schedule) | Defines when to start and stop the instances. Use 'start' and 'stop' with a cron expression and add the 'time\_zone'. |
object({
start = string
stop = string
time_zone = string
})
| `null` | no | diff --git a/lambda/panic_button_switch_off.py b/lambda/panic_button_switch_off.py new file mode 100644 index 0000000..3928922 --- /dev/null +++ b/lambda/panic_button_switch_off.py @@ -0,0 +1,22 @@ +import os + +import boto3 + +def handler(event, context): + # change the ASG to min=0, max=0, desired=0 + disable_asg() + + # find the EC2 instances and kill them + kill_running_bastion_hosts(os.environ['BASTION_HOST_NAME']) + +def disable_asg(): + pass + +def kill_running_bastion_hosts(name): + ec2 = boto3.resource('ec2',"us-west-1") + + instances = ec2.describe_instances(Filters=[{'Name': 'tag:Name', 'Values': [f'{name}']}, + {'Name': 'instance-state-name', 'Values': ['pending', 'running']}]) + instance_ids = [instance.InstanceId for instance in instances.Reservations.Instances] + + ec2.stop_instances(InstanceIds=instance_ids) diff --git a/locals.tf b/locals.tf index 381ad07..5ebb70f 100644 --- a/locals.tf +++ b/locals.tf @@ -14,4 +14,7 @@ locals { bastion_access_tag_name = "bastion-access" bastion_instance_profile_name = var.instance["profile_name"] != "" ? var.instance["profile_name"] : module.instance_profile_role[0].iam_role_name + + panic_button_switch_off_lambda_source = "${path.module}/lambda/panic_button_switch_off.py" + panic_button_switch_off_lambda_source_sha256 = filesha256(local.panic_button_switch_off_lambda_source) } diff --git a/panic-button.tf b/panic-button.tf new file mode 100644 index 0000000..e1a2155 --- /dev/null +++ b/panic-button.tf @@ -0,0 +1,52 @@ +resource "aws_iam_role" "lambda" { + name = "${var.resource_names.prefix}${var.resource_names.separator}panic-button-off" + description = "Role for executing the bastion panic button switch off" + path = "/" + assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json + force_detach_policies = true + + tags = var.tags +} + +data "aws_iam_policy_document" "lambda_assume_role" { + statement { + actions = [ + "sts:AssumeRole", + ] + effect = "Allow" + + principals { + identifiers = ["lambda.amazonaws.com"] + type = "Service" + } + } +} + +data "archive_file" "panic_button_lambda_switch_off" { + type = "zip" + source_file = local.panic_button_switch_off_lambda_source + output_path = "builds/lambda_function_${local.panic_button_switch_off_lambda_source_sha256}.zip" +} + +resource "aws_lambda_function" "panic_button_switch_off" { + architectures = ["arm64"] + description = "Terminates all bastion hosts forever" + filename = data.archive_file.panic_button_lambda_switch_off.output_path + source_code_hash = data.archive_file.panic_button_lambda_switch_off.output_base64sha256 + function_name = "${var.resource_names.prefix}${var.resource_names.separator}switch-off" + handler = "panic_button_switch_off.handler" + memory_size = 128 + package_type = "Zip" + publish = true + role = aws_iam_role.lambda.arn + runtime = "python3.8" + timeout = 30 + + environment { + variables = { + BASTION_HOST_NAME = local.bastion_host_name + } + } + + tags = var.tags +} From b395cc0c421a10d39e408bca1705359182ffd15c Mon Sep 17 00:00:00 2001 From: Matthias Kay Date: Thu, 2 Feb 2023 21:25:40 +0100 Subject: [PATCH 02/13] add policies --- README.md | 5 ++++- panic-button.tf | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 21b821a..015b9e5 100644 --- a/README.md +++ b/README.md @@ -218,9 +218,11 @@ way you can access the database, Redis cluster, ... directly from your localhost | [aws_autoscaling_schedule.on_spot_down](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_schedule) | resource | | [aws_autoscaling_schedule.on_spot_up](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_schedule) | resource | | [aws_iam_policy.access_bastion](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.lambda_switch_off](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_role.access_bastion](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | -| [aws_iam_role.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.lambda_switch_off](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role_policy_attachment.access_bastion](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.lambda_switch_off](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_lambda_function.panic_button_switch_off](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource | | [aws_launch_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_configuration) | resource | | [aws_launch_template.manual_start](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template) | resource | @@ -232,6 +234,7 @@ way you can access the database, Redis cluster, ... directly from your localhost | [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_iam_policy_document.access_bastion](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.lambda_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.lambda_switch_off](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_region.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | ## Inputs diff --git a/panic-button.tf b/panic-button.tf index e1a2155..4ae251f 100644 --- a/panic-button.tf +++ b/panic-button.tf @@ -1,4 +1,4 @@ -resource "aws_iam_role" "lambda" { +resource "aws_iam_role" "lambda_switch_off" { name = "${var.resource_names.prefix}${var.resource_names.separator}panic-button-off" description = "Role for executing the bastion panic button switch off" path = "/" @@ -22,6 +22,30 @@ data "aws_iam_policy_document" "lambda_assume_role" { } } +data "aws_iam_policy_document" "lambda_switch_off" { + statement { + sid = "KillBastionHosts" + actions = [ + "ec2:DescribeInstances", + "ec2:StopInstances" + ] + resources = ["*"] + effect = "Allow" + } +} + +resource "aws_iam_policy" "lambda_switch_off" { + name = "${var.resource_names.prefix}${var.resource_names.separator}switch-off" + policy = data.aws_iam_policy_document.lambda_switch_off.json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "lambda_switch_off" { + role = aws_iam_role.lambda_switch_off.name + policy_arn = aws_iam_policy.lambda_switch_off.arn +} + data "archive_file" "panic_button_lambda_switch_off" { type = "zip" source_file = local.panic_button_switch_off_lambda_source @@ -38,7 +62,7 @@ resource "aws_lambda_function" "panic_button_switch_off" { memory_size = 128 package_type = "Zip" publish = true - role = aws_iam_role.lambda.arn + role = aws_iam_role.lambda_switch_off.arn runtime = "python3.8" timeout = 30 @@ -48,5 +72,9 @@ resource "aws_lambda_function" "panic_button_switch_off" { } } + tracing_config { + mode = "Passthrough" + } + tags = var.tags } From b14c0c490873d2a84ad65894e3b6f04c4ccbc598 Mon Sep 17 00:00:00 2001 From: Matthias Kay Date: Thu, 2 Feb 2023 21:31:33 +0100 Subject: [PATCH 03/13] fix IAM --- panic-button.tf | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/panic-button.tf b/panic-button.tf index 4ae251f..6c50fc3 100644 --- a/panic-button.tf +++ b/panic-button.tf @@ -30,7 +30,12 @@ data "aws_iam_policy_document" "lambda_switch_off" { "ec2:StopInstances" ] resources = ["*"] - effect = "Allow" + condition { + test = "StringEquals" + values = [local.bastion_host_name] + variable = "aws:ResourceTag/Name" + } + effect = "Allow" } } From 95443b868e557cfdb7b47b43f7be48fe128c46fa Mon Sep 17 00:00:00 2001 From: Matthias Kay Date: Thu, 2 Feb 2023 21:40:50 +0100 Subject: [PATCH 04/13] x --- locals.tf | 4 ++-- panic-button.tf | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/locals.tf b/locals.tf index 5ebb70f..0d3d38e 100644 --- a/locals.tf +++ b/locals.tf @@ -15,6 +15,6 @@ locals { bastion_instance_profile_name = var.instance["profile_name"] != "" ? var.instance["profile_name"] : module.instance_profile_role[0].iam_role_name - panic_button_switch_off_lambda_source = "${path.module}/lambda/panic_button_switch_off.py" - panic_button_switch_off_lambda_source_sha256 = filesha256(local.panic_button_switch_off_lambda_source) + panic_button_switch_off_lambda_source_file_name = "panic_button_switch_off.py" + panic_button_switch_off_lambda_source = "${path.module}/lambda/${local.panic_button_switch_off_lambda_source_file_name}" } diff --git a/panic-button.tf b/panic-button.tf index 6c50fc3..5d607f2 100644 --- a/panic-button.tf +++ b/panic-button.tf @@ -54,14 +54,14 @@ resource "aws_iam_role_policy_attachment" "lambda_switch_off" { data "archive_file" "panic_button_lambda_switch_off" { type = "zip" source_file = local.panic_button_switch_off_lambda_source - output_path = "builds/lambda_function_${local.panic_button_switch_off_lambda_source_sha256}.zip" + output_path = "builds/${local.panic_button_switch_off_lambda_source_file_name}.zip" } resource "aws_lambda_function" "panic_button_switch_off" { architectures = ["arm64"] description = "Terminates all bastion hosts forever" filename = data.archive_file.panic_button_lambda_switch_off.output_path - source_code_hash = data.archive_file.panic_button_lambda_switch_off.output_base64sha256 + source_code_hash = filebase64sha256(data.archive_file.panic_button_lambda_switch_off.output_path) function_name = "${var.resource_names.prefix}${var.resource_names.separator}switch-off" handler = "panic_button_switch_off.handler" memory_size = 128 From 9f505e78a4e1e84a92a451c271eec7ab743152f5 Mon Sep 17 00:00:00 2001 From: kayma Date: Thu, 16 Feb 2023 11:08:50 +0100 Subject: [PATCH 05/13] logging and set ASG --- .gitignore | 1 + lambda/panic_button_switch_off.py | 45 ++++++++++++++++++++++--------- panic-button.tf | 3 +++ provider.tf | 5 ++++ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 5c2a2a8..f8e72db 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .idea/ .infracost/ node_modules/ +venv/ .terraform.lock.hcl *.tfstate* diff --git a/lambda/panic_button_switch_off.py b/lambda/panic_button_switch_off.py index 3928922..6aca8a7 100644 --- a/lambda/panic_button_switch_off.py +++ b/lambda/panic_button_switch_off.py @@ -1,22 +1,43 @@ +import logging import os - import boto3 +from botocore.exceptions import ClientError + +logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=os.environ.get('LOG_LEVEL', 'info')) + +logger = logging.getLogger(__name__) + def handler(event, context): - # change the ASG to min=0, max=0, desired=0 - disable_asg() + # change the ASG to disable automatic restart + disable_asg(os.environ['AUTO_SCALING_GROUP_NAME']) + + # find the EC2 instances and kill them + kill_running_bastion_hosts(os.environ['BASTION_HOST_NAME']) - # find the EC2 instances and kill them - kill_running_bastion_hosts(os.environ['BASTION_HOST_NAME']) -def disable_asg(): - pass +def disable_asg(autoscalingGroupName): + asg = boto3.resource('autoscaling') + + try: + asg.update_auto_scaling_group(AutoScalingGroupName=autoscalingGroupName, MinSize=0, MaxSize=0, + DesiredCapacity=0) + except ClientError as e: + logger.error('Failed to update the ASG %s', autoscalingGroupName, exc_info=e) + + raise + def kill_running_bastion_hosts(name): - ec2 = boto3.resource('ec2',"us-west-1") + ec2 = boto3.resource('ec2') + + try: + instances = ec2.describe_instances(Filters=[{'Name': 'tag:Name', 'Values': [f'{name}']}, + {'Name': 'instance-state-name', 'Values': ['pending', 'running']}]) + instance_ids = [instance.InstanceId for instance in instances.Reservations.Instances] - instances = ec2.describe_instances(Filters=[{'Name': 'tag:Name', 'Values': [f'{name}']}, - {'Name': 'instance-state-name', 'Values': ['pending', 'running']}]) - instance_ids = [instance.InstanceId for instance in instances.Reservations.Instances] + ec2.stop_instances(InstanceIds=instance_ids) + except ClientError as e: + logger.error('Failed to kill the bastion EC2 instance(s): %s', name, exc_info=e) - ec2.stop_instances(InstanceIds=instance_ids) + raise diff --git a/panic-button.tf b/panic-button.tf index 5d607f2..5aa6d5b 100644 --- a/panic-button.tf +++ b/panic-button.tf @@ -73,7 +73,10 @@ resource "aws_lambda_function" "panic_button_switch_off" { environment { variables = { + AUTO_SCALING_GROUP_NAME = var.instance.enable_spot ? aws_autoscaling_group.on_demand[0].name : aws_autoscaling_group.on_demand[0].name BASTION_HOST_NAME = local.bastion_host_name + + LOG_LEVEL = "info" } } diff --git a/provider.tf b/provider.tf index 8d06536..9c6863c 100644 --- a/provider.tf +++ b/provider.tf @@ -1,5 +1,10 @@ terraform { required_providers { + archive = { + source = "hashicorp/archive" + version = ">= 2.0.0" + }, + aws = { source = "hashicorp/aws" version = ">= 4.0.0" From b9c60d5bd5bc46bbef81e8504453e5a33415b33e Mon Sep 17 00:00:00 2001 From: kayma Date: Thu, 16 Feb 2023 12:04:46 +0100 Subject: [PATCH 06/13] PANIC-BUTTON --- .../builds/panic_button_switch_off.py.zip | Bin 0 -> 825 bytes examples/simple/panic_button_switch_off.py | 44 ++++++++++++++++++ .../simple/panic_button_switch_off.py.zip | Bin 0 -> 825 bytes examples/simple/panic_button_switch_off.zip | Bin 0 -> 825 bytes 4 files changed, 44 insertions(+) create mode 100644 examples/simple/builds/panic_button_switch_off.py.zip create mode 100644 examples/simple/panic_button_switch_off.py create mode 100644 examples/simple/panic_button_switch_off.py.zip create mode 100644 examples/simple/panic_button_switch_off.zip diff --git a/examples/simple/builds/panic_button_switch_off.py.zip b/examples/simple/builds/panic_button_switch_off.py.zip new file mode 100644 index 0000000000000000000000000000000000000000..504fc2955d5bab38a680d4d34981c435454c6694 GIT binary patch literal 825 zcmWIWW@Zs#-~d7f2E{HQ0S8h*RzYH3W^#N|X-P?bUVL$RW=V2Je12M*UP0xO5Z~;_ zHUj&8hx62GZc;Q|`d;^e^DN)ZjV6(^woMmkQRmFaeDqCHgszFRo@l+ z3wK|PdO1m-wer=^Qxg^&AGv++O`*x$59=@Ad~6wZ+V9oPF8#dQOb@t^OkwyN;J40z zJy=BC^Yq1+0d+Z(7JYiK)O2>~;q(XI0i2QAlddSu?42sHd9sLDRKd5UXE`EYrp@JU zztjF{^QXf6$7j~mO8#o^Tw(ZoM>pRcZQEdb!`<%=?7hMInc>UNpG`}2p4Dy?3)(sT zja8;lbmjakDHcY#+x#Vq%8$PBTftSVKc#F*bBW&Gj{587?^M43K4Bp6q+hhl;KY~7 zOXq*+|MZOcU*Nr(6TbsfW}f#cR>{07^z)$L{gtN{F3tIO%I5tV?lYa6Zr_kiIKSwY z!bZI_+}~dPu1N0QzW?d(KQfHpUW7ksIjsGcuX}Ca^Ee;bghrul711j1*&b_s-+cD_ z_Y#qwK)%+E?G^#QJluXYw3KPjvcIL+EOvZv#kvzSzjFE?KQv|TqW_CqzRBCQ^v?e) zo#eG^?W%t(7REC&F#P`?;LXkv^j>|#Ph60p5&EBFu=yjVuRB+%T}M5yZls W)C0U(*+427fzSg;-v*k`zyJV{b#iS0 literal 0 HcmV?d00001 diff --git a/examples/simple/panic_button_switch_off.py b/examples/simple/panic_button_switch_off.py new file mode 100644 index 0000000..e7c1ef2 --- /dev/null +++ b/examples/simple/panic_button_switch_off.py @@ -0,0 +1,44 @@ +import logging +import os +import boto3 +from botocore.exceptions import ClientError + +logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=os.environ.get('LOG_LEVEL', 'info')) + +logger = logging.getLogger(__name__) + + +def handler(event, context): + # change the ASG to disable automatic restart + disable_asg(os.environ['AUTO_SCALING_GROUP_NAME']) + + # find the EC2 instances and kill them + kill_running_bastion_hosts(os.environ['BASTION_HOST_NAME']) + + logger.info("Bastion host(s) switched off") + +def disable_asg(autoscaling_group_name): + asg = boto3.resource('autoscaling') + + try: + asg.update_auto_scaling_group(AutoScalingGroupName=autoscaling_group_name, MinSize=0, MaxSize=0, + DesiredCapacity=0) + except ClientError as e: + logger.error('Failed to update the ASG %s', autoscaling_group_name, exc_info=e) + + raise + + +def kill_running_bastion_hosts(name): + ec2 = boto3.resource('ec2') + + try: + instances = ec2.describe_instances(Filters=[{'Name': 'tag:Name', 'Values': [f'{name}']}, + {'Name': 'instance-state-name', 'Values': ['pending', 'running']}]) + instance_ids = [instance.InstanceId for instance in instances.Reservations.Instances] + + ec2.stop_instances(InstanceIds=instance_ids) + except ClientError as e: + logger.error('Failed to kill the bastion EC2 instance(s): %s', name, exc_info=e) + + raise diff --git a/examples/simple/panic_button_switch_off.py.zip b/examples/simple/panic_button_switch_off.py.zip new file mode 100644 index 0000000000000000000000000000000000000000..504fc2955d5bab38a680d4d34981c435454c6694 GIT binary patch literal 825 zcmWIWW@Zs#-~d7f2E{HQ0S8h*RzYH3W^#N|X-P?bUVL$RW=V2Je12M*UP0xO5Z~;_ zHUj&8hx62GZc;Q|`d;^e^DN)ZjV6(^woMmkQRmFaeDqCHgszFRo@l+ z3wK|PdO1m-wer=^Qxg^&AGv++O`*x$59=@Ad~6wZ+V9oPF8#dQOb@t^OkwyN;J40z zJy=BC^Yq1+0d+Z(7JYiK)O2>~;q(XI0i2QAlddSu?42sHd9sLDRKd5UXE`EYrp@JU zztjF{^QXf6$7j~mO8#o^Tw(ZoM>pRcZQEdb!`<%=?7hMInc>UNpG`}2p4Dy?3)(sT zja8;lbmjakDHcY#+x#Vq%8$PBTftSVKc#F*bBW&Gj{587?^M43K4Bp6q+hhl;KY~7 zOXq*+|MZOcU*Nr(6TbsfW}f#cR>{07^z)$L{gtN{F3tIO%I5tV?lYa6Zr_kiIKSwY z!bZI_+}~dPu1N0QzW?d(KQfHpUW7ksIjsGcuX}Ca^Ee;bghrul711j1*&b_s-+cD_ z_Y#qwK)%+E?G^#QJluXYw3KPjvcIL+EOvZv#kvzSzjFE?KQv|TqW_CqzRBCQ^v?e) zo#eG^?W%t(7REC&F#P`?;LXkv^j>|#Ph60p5&EBFu=yjVuRB+%T}M5yZls W)C0U(*+427fzSg;-v*k`zyJV{b#iS0 literal 0 HcmV?d00001 diff --git a/examples/simple/panic_button_switch_off.zip b/examples/simple/panic_button_switch_off.zip new file mode 100644 index 0000000000000000000000000000000000000000..504fc2955d5bab38a680d4d34981c435454c6694 GIT binary patch literal 825 zcmWIWW@Zs#-~d7f2E{HQ0S8h*RzYH3W^#N|X-P?bUVL$RW=V2Je12M*UP0xO5Z~;_ zHUj&8hx62GZc;Q|`d;^e^DN)ZjV6(^woMmkQRmFaeDqCHgszFRo@l+ z3wK|PdO1m-wer=^Qxg^&AGv++O`*x$59=@Ad~6wZ+V9oPF8#dQOb@t^OkwyN;J40z zJy=BC^Yq1+0d+Z(7JYiK)O2>~;q(XI0i2QAlddSu?42sHd9sLDRKd5UXE`EYrp@JU zztjF{^QXf6$7j~mO8#o^Tw(ZoM>pRcZQEdb!`<%=?7hMInc>UNpG`}2p4Dy?3)(sT zja8;lbmjakDHcY#+x#Vq%8$PBTftSVKc#F*bBW&Gj{587?^M43K4Bp6q+hhl;KY~7 zOXq*+|MZOcU*Nr(6TbsfW}f#cR>{07^z)$L{gtN{F3tIO%I5tV?lYa6Zr_kiIKSwY z!bZI_+}~dPu1N0QzW?d(KQfHpUW7ksIjsGcuX}Ca^Ee;bghrul711j1*&b_s-+cD_ z_Y#qwK)%+E?G^#QJluXYw3KPjvcIL+EOvZv#kvzSzjFE?KQv|TqW_CqzRBCQ^v?e) zo#eG^?W%t(7REC&F#P`?;LXkv^j>|#Ph60p5&EBFu=yjVuRB+%T}M5yZls W)C0U(*+427fzSg;-v*k`zyJV{b#iS0 literal 0 HcmV?d00001 From 521be747f327bac6344620c91ea731437aff6522 Mon Sep 17 00:00:00 2001 From: kayma Date: Thu, 16 Feb 2023 12:23:07 +0100 Subject: [PATCH 07/13] finish stop code --- examples/simple/panic_button_switch_off.py | 44 -------------- .../simple/panic_button_switch_off.py.zip | Bin 825 -> 0 bytes examples/simple/panic_button_switch_off.zip | Bin 825 -> 0 bytes lambda/panic_button_switch_off.py | 24 +++++--- locals.tf | 1 + panic-button.tf | 54 +++++++++++++----- provider.tf | 2 +- 7 files changed, 58 insertions(+), 67 deletions(-) delete mode 100644 examples/simple/panic_button_switch_off.py delete mode 100644 examples/simple/panic_button_switch_off.py.zip delete mode 100644 examples/simple/panic_button_switch_off.zip diff --git a/examples/simple/panic_button_switch_off.py b/examples/simple/panic_button_switch_off.py deleted file mode 100644 index e7c1ef2..0000000 --- a/examples/simple/panic_button_switch_off.py +++ /dev/null @@ -1,44 +0,0 @@ -import logging -import os -import boto3 -from botocore.exceptions import ClientError - -logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=os.environ.get('LOG_LEVEL', 'info')) - -logger = logging.getLogger(__name__) - - -def handler(event, context): - # change the ASG to disable automatic restart - disable_asg(os.environ['AUTO_SCALING_GROUP_NAME']) - - # find the EC2 instances and kill them - kill_running_bastion_hosts(os.environ['BASTION_HOST_NAME']) - - logger.info("Bastion host(s) switched off") - -def disable_asg(autoscaling_group_name): - asg = boto3.resource('autoscaling') - - try: - asg.update_auto_scaling_group(AutoScalingGroupName=autoscaling_group_name, MinSize=0, MaxSize=0, - DesiredCapacity=0) - except ClientError as e: - logger.error('Failed to update the ASG %s', autoscaling_group_name, exc_info=e) - - raise - - -def kill_running_bastion_hosts(name): - ec2 = boto3.resource('ec2') - - try: - instances = ec2.describe_instances(Filters=[{'Name': 'tag:Name', 'Values': [f'{name}']}, - {'Name': 'instance-state-name', 'Values': ['pending', 'running']}]) - instance_ids = [instance.InstanceId for instance in instances.Reservations.Instances] - - ec2.stop_instances(InstanceIds=instance_ids) - except ClientError as e: - logger.error('Failed to kill the bastion EC2 instance(s): %s', name, exc_info=e) - - raise diff --git a/examples/simple/panic_button_switch_off.py.zip b/examples/simple/panic_button_switch_off.py.zip deleted file mode 100644 index 504fc2955d5bab38a680d4d34981c435454c6694..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 825 zcmWIWW@Zs#-~d7f2E{HQ0S8h*RzYH3W^#N|X-P?bUVL$RW=V2Je12M*UP0xO5Z~;_ zHUj&8hx62GZc;Q|`d;^e^DN)ZjV6(^woMmkQRmFaeDqCHgszFRo@l+ z3wK|PdO1m-wer=^Qxg^&AGv++O`*x$59=@Ad~6wZ+V9oPF8#dQOb@t^OkwyN;J40z zJy=BC^Yq1+0d+Z(7JYiK)O2>~;q(XI0i2QAlddSu?42sHd9sLDRKd5UXE`EYrp@JU zztjF{^QXf6$7j~mO8#o^Tw(ZoM>pRcZQEdb!`<%=?7hMInc>UNpG`}2p4Dy?3)(sT zja8;lbmjakDHcY#+x#Vq%8$PBTftSVKc#F*bBW&Gj{587?^M43K4Bp6q+hhl;KY~7 zOXq*+|MZOcU*Nr(6TbsfW}f#cR>{07^z)$L{gtN{F3tIO%I5tV?lYa6Zr_kiIKSwY z!bZI_+}~dPu1N0QzW?d(KQfHpUW7ksIjsGcuX}Ca^Ee;bghrul711j1*&b_s-+cD_ z_Y#qwK)%+E?G^#QJluXYw3KPjvcIL+EOvZv#kvzSzjFE?KQv|TqW_CqzRBCQ^v?e) zo#eG^?W%t(7REC&F#P`?;LXkv^j>|#Ph60p5&EBFu=yjVuRB+%T}M5yZls W)C0U(*+427fzSg;-v*k`zyJV{b#iS0 diff --git a/examples/simple/panic_button_switch_off.zip b/examples/simple/panic_button_switch_off.zip deleted file mode 100644 index 504fc2955d5bab38a680d4d34981c435454c6694..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 825 zcmWIWW@Zs#-~d7f2E{HQ0S8h*RzYH3W^#N|X-P?bUVL$RW=V2Je12M*UP0xO5Z~;_ zHUj&8hx62GZc;Q|`d;^e^DN)ZjV6(^woMmkQRmFaeDqCHgszFRo@l+ z3wK|PdO1m-wer=^Qxg^&AGv++O`*x$59=@Ad~6wZ+V9oPF8#dQOb@t^OkwyN;J40z zJy=BC^Yq1+0d+Z(7JYiK)O2>~;q(XI0i2QAlddSu?42sHd9sLDRKd5UXE`EYrp@JU zztjF{^QXf6$7j~mO8#o^Tw(ZoM>pRcZQEdb!`<%=?7hMInc>UNpG`}2p4Dy?3)(sT zja8;lbmjakDHcY#+x#Vq%8$PBTftSVKc#F*bBW&Gj{587?^M43K4Bp6q+hhl;KY~7 zOXq*+|MZOcU*Nr(6TbsfW}f#cR>{07^z)$L{gtN{F3tIO%I5tV?lYa6Zr_kiIKSwY z!bZI_+}~dPu1N0QzW?d(KQfHpUW7ksIjsGcuX}Ca^Ee;bghrul711j1*&b_s-+cD_ z_Y#qwK)%+E?G^#QJluXYw3KPjvcIL+EOvZv#kvzSzjFE?KQv|TqW_CqzRBCQ^v?e) zo#eG^?W%t(7REC&F#P`?;LXkv^j>|#Ph60p5&EBFu=yjVuRB+%T}M5yZls W)C0U(*+427fzSg;-v*k`zyJV{b#iS0 diff --git a/lambda/panic_button_switch_off.py b/lambda/panic_button_switch_off.py index 6aca8a7..c78c289 100644 --- a/lambda/panic_button_switch_off.py +++ b/lambda/panic_button_switch_off.py @@ -15,28 +15,38 @@ def handler(event, context): # find the EC2 instances and kill them kill_running_bastion_hosts(os.environ['BASTION_HOST_NAME']) + logger.info("Bastion host(s) switched off") -def disable_asg(autoscalingGroupName): - asg = boto3.resource('autoscaling') +def disable_asg(autoscaling_group_name): + asg = boto3.client('autoscaling') try: - asg.update_auto_scaling_group(AutoScalingGroupName=autoscalingGroupName, MinSize=0, MaxSize=0, + asg.update_auto_scaling_group(AutoScalingGroupName=autoscaling_group_name, MinSize=0, MaxSize=0, DesiredCapacity=0) except ClientError as e: - logger.error('Failed to update the ASG %s', autoscalingGroupName, exc_info=e) + logger.error('Failed to update the ASG %s', autoscaling_group_name, exc_info=e) raise def kill_running_bastion_hosts(name): - ec2 = boto3.resource('ec2') + ec2 = boto3.client('ec2') try: instances = ec2.describe_instances(Filters=[{'Name': 'tag:Name', 'Values': [f'{name}']}, {'Name': 'instance-state-name', 'Values': ['pending', 'running']}]) - instance_ids = [instance.InstanceId for instance in instances.Reservations.Instances] - ec2.stop_instances(InstanceIds=instance_ids) + if 'Reservations' in instances: + instance_ids = [] + + for r in instances['Reservations']: + for i in r['Instances']: + instance_ids.append(i['InstanceId']) + + if instance_ids: + ec2.stop_instances(InstanceIds=instance_ids) + + logger.info("Bastion killed: %s", instance_ids) except ClientError as e: logger.error('Failed to kill the bastion EC2 instance(s): %s', name, exc_info=e) diff --git a/locals.tf b/locals.tf index 21bf242..a60f645 100644 --- a/locals.tf +++ b/locals.tf @@ -16,5 +16,6 @@ locals { bastion_instance_profile_name = var.instance["profile_name"] != "" ? var.instance["profile_name"] : module.instance_profile_role[0].iam_role_name panic_button_switch_off_lambda_source_file_name = "panic_button_switch_off.py" + panic_button_switch_off_lambda_zip_file_name = "panic_button_switch_off.zip" panic_button_switch_off_lambda_source = "${path.module}/lambda/${local.panic_button_switch_off_lambda_source_file_name}" } diff --git a/panic-button.tf b/panic-button.tf index 5aa6d5b..852d112 100644 --- a/panic-button.tf +++ b/panic-button.tf @@ -1,7 +1,6 @@ -resource "aws_iam_role" "lambda_switch_off" { +resource "aws_iam_role" "lambda_switch_off_execution" { name = "${var.resource_names.prefix}${var.resource_names.separator}panic-button-off" description = "Role for executing the bastion panic button switch off" - path = "/" assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json force_detach_policies = true @@ -23,10 +22,18 @@ data "aws_iam_policy_document" "lambda_assume_role" { } data "aws_iam_policy_document" "lambda_switch_off" { + statement { + sid = "ListInstances" + actions = [ + "ec2:DescribeInstances" + ] + resources = ["*"] + effect = "Allow" + } + statement { sid = "KillBastionHosts" actions = [ - "ec2:DescribeInstances", "ec2:StopInstances" ] resources = ["*"] @@ -37,6 +44,13 @@ data "aws_iam_policy_document" "lambda_switch_off" { } effect = "Allow" } + + statement { + sid="UpdateASG" + actions = ["autoscaling:UpdateAutoScalingGroup"] + resources = [var.instance.enable_spot ? aws_autoscaling_group.on_spot[0].arn : aws_autoscaling_group.on_demand[0].arn] + effect = "Allow" + } } resource "aws_iam_policy" "lambda_switch_off" { @@ -47,33 +61,43 @@ resource "aws_iam_policy" "lambda_switch_off" { } resource "aws_iam_role_policy_attachment" "lambda_switch_off" { - role = aws_iam_role.lambda_switch_off.name + role = aws_iam_role.lambda_switch_off_execution.name policy_arn = aws_iam_policy.lambda_switch_off.arn } -data "archive_file" "panic_button_lambda_switch_off" { +resource "aws_iam_role_policy_attachment" "gitlab_webhook_authorizer_execution_lambda_basic" { + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = aws_iam_role.lambda_switch_off_execution.id +} + +resource "aws_iam_role_policy_attachment" "gitlab_webhook_authorizer_execution_xray" { + policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess" + role = aws_iam_role.lambda_switch_off_execution.id +} + +data "archive_file" "panic_button_lambda_switch_off_package" { type = "zip" source_file = local.panic_button_switch_off_lambda_source - output_path = "builds/${local.panic_button_switch_off_lambda_source_file_name}.zip" + output_path = "${path.root}/builds/${local.panic_button_switch_off_lambda_source}.zip" } resource "aws_lambda_function" "panic_button_switch_off" { architectures = ["arm64"] description = "Terminates all bastion hosts forever" - filename = data.archive_file.panic_button_lambda_switch_off.output_path - source_code_hash = filebase64sha256(data.archive_file.panic_button_lambda_switch_off.output_path) + filename = data.archive_file.panic_button_lambda_switch_off_package.output_path + source_code_hash = data.archive_file.panic_button_lambda_switch_off_package.output_base64sha256 function_name = "${var.resource_names.prefix}${var.resource_names.separator}switch-off" handler = "panic_button_switch_off.handler" - memory_size = 128 - package_type = "Zip" - publish = true - role = aws_iam_role.lambda_switch_off.arn - runtime = "python3.8" timeout = 30 + memory_size = 256 + #package_type = "Zip" + publish = true + role = aws_iam_role.lambda_switch_off_execution.arn + runtime = "python3.9" environment { variables = { - AUTO_SCALING_GROUP_NAME = var.instance.enable_spot ? aws_autoscaling_group.on_demand[0].name : aws_autoscaling_group.on_demand[0].name + AUTO_SCALING_GROUP_NAME = var.instance.enable_spot ? aws_autoscaling_group.on_spot[0].name : aws_autoscaling_group.on_demand[0].name BASTION_HOST_NAME = local.bastion_host_name LOG_LEVEL = "info" @@ -81,7 +105,7 @@ resource "aws_lambda_function" "panic_button_switch_off" { } tracing_config { - mode = "Passthrough" + mode = "Active" } tags = var.tags diff --git a/provider.tf b/provider.tf index 9c6863c..0580dc8 100644 --- a/provider.tf +++ b/provider.tf @@ -3,7 +3,7 @@ terraform { archive = { source = "hashicorp/archive" version = ">= 2.0.0" - }, + } aws = { source = "hashicorp/aws" From e4866afcaf2e9a79bcb7c1d3454ac50aaa751eb8 Mon Sep 17 00:00:00 2001 From: kayma Date: Thu, 16 Feb 2023 15:58:10 +0100 Subject: [PATCH 08/13] add switch on --- autoscaling.tf | 36 +------ lambda/panic_button_switch_on.py | 53 +++++++++++ locals.tf | 8 +- panic-button.tf => panic-button-off.tf | 50 ++++++---- panic-button-on.tf | 124 +++++++++++++++++++++++++ 5 files changed, 219 insertions(+), 52 deletions(-) create mode 100644 lambda/panic_button_switch_on.py rename panic-button.tf => panic-button-off.tf (55%) create mode 100644 panic-button-on.tf diff --git a/autoscaling.tf b/autoscaling.tf index 6e43ce7..0d866ff 100644 --- a/autoscaling.tf +++ b/autoscaling.tf @@ -82,8 +82,8 @@ resource "aws_autoscaling_group" "on_spot" { } } -resource "aws_autoscaling_schedule" "on_demand_up" { - count = var.schedule != null && !var.instance.enable_spot ? 1 : 0 +resource "aws_autoscaling_schedule" "up" { + count = var.schedule != null ? 1 : 0 scheduled_action_name = "${local.resource_prefix_with_separator}start" recurrence = var.schedule["start"] @@ -92,10 +92,10 @@ resource "aws_autoscaling_schedule" "on_demand_up" { min_size = 1 max_size = var.instance.desired_capacity desired_capacity = var.instance.desired_capacity - autoscaling_group_name = aws_autoscaling_group.on_demand[0].name + autoscaling_group_name = local.auto_scaling_group.name } -resource "aws_autoscaling_schedule" "on_demand_down" { +resource "aws_autoscaling_schedule" "down" { count = var.schedule != null && !var.instance.enable_spot ? 1 : 0 scheduled_action_name = "${local.resource_prefix_with_separator}stop" @@ -105,31 +105,5 @@ resource "aws_autoscaling_schedule" "on_demand_down" { min_size = 0 max_size = 0 desired_capacity = 0 - autoscaling_group_name = aws_autoscaling_group.on_demand[0].name -} - -resource "aws_autoscaling_schedule" "on_spot_up" { - count = var.schedule != null && var.instance.enable_spot ? 1 : 0 - - scheduled_action_name = "${local.resource_prefix_with_separator}start" - recurrence = var.schedule["start"] - time_zone = var.schedule["time_zone"] - - min_size = 1 - max_size = var.instance.desired_capacity - desired_capacity = var.instance.desired_capacity - autoscaling_group_name = aws_autoscaling_group.on_spot[0].name -} - -resource "aws_autoscaling_schedule" "on_spot_down" { - count = var.schedule != null && var.instance.enable_spot ? 1 : 0 - - scheduled_action_name = "${local.resource_prefix_with_separator}stop" - recurrence = var.schedule["stop"] - time_zone = var.schedule["time_zone"] - - min_size = 0 - max_size = 0 - desired_capacity = 0 - autoscaling_group_name = aws_autoscaling_group.on_spot[0].name + autoscaling_group_name = local.auto_scaling_group.name } diff --git a/lambda/panic_button_switch_on.py b/lambda/panic_button_switch_on.py new file mode 100644 index 0000000..c78c289 --- /dev/null +++ b/lambda/panic_button_switch_on.py @@ -0,0 +1,53 @@ +import logging +import os +import boto3 +from botocore.exceptions import ClientError + +logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=os.environ.get('LOG_LEVEL', 'info')) + +logger = logging.getLogger(__name__) + + +def handler(event, context): + # change the ASG to disable automatic restart + disable_asg(os.environ['AUTO_SCALING_GROUP_NAME']) + + # find the EC2 instances and kill them + kill_running_bastion_hosts(os.environ['BASTION_HOST_NAME']) + + logger.info("Bastion host(s) switched off") + +def disable_asg(autoscaling_group_name): + asg = boto3.client('autoscaling') + + try: + asg.update_auto_scaling_group(AutoScalingGroupName=autoscaling_group_name, MinSize=0, MaxSize=0, + DesiredCapacity=0) + except ClientError as e: + logger.error('Failed to update the ASG %s', autoscaling_group_name, exc_info=e) + + raise + + +def kill_running_bastion_hosts(name): + ec2 = boto3.client('ec2') + + try: + instances = ec2.describe_instances(Filters=[{'Name': 'tag:Name', 'Values': [f'{name}']}, + {'Name': 'instance-state-name', 'Values': ['pending', 'running']}]) + + if 'Reservations' in instances: + instance_ids = [] + + for r in instances['Reservations']: + for i in r['Instances']: + instance_ids.append(i['InstanceId']) + + if instance_ids: + ec2.stop_instances(InstanceIds=instance_ids) + + logger.info("Bastion killed: %s", instance_ids) + except ClientError as e: + logger.error('Failed to kill the bastion EC2 instance(s): %s', name, exc_info=e) + + raise diff --git a/locals.tf b/locals.tf index a60f645..b0cef9f 100644 --- a/locals.tf +++ b/locals.tf @@ -16,6 +16,12 @@ locals { bastion_instance_profile_name = var.instance["profile_name"] != "" ? var.instance["profile_name"] : module.instance_profile_role[0].iam_role_name panic_button_switch_off_lambda_source_file_name = "panic_button_switch_off.py" - panic_button_switch_off_lambda_zip_file_name = "panic_button_switch_off.zip" panic_button_switch_off_lambda_source = "${path.module}/lambda/${local.panic_button_switch_off_lambda_source_file_name}" + panic_button_switch_off_lambda_name = "${var.resource_names.prefix}${var.resource_names.separator}panic-button-off" + + panic_button_switch_on_lambda_source_file_name = "panic_button_switch_on.py" + panic_button_switch_on_lambda_source = "${path.module}/lambda/${local.panic_button_switch_on_lambda_source_file_name}" + panic_button_switch_on_lambda_name = "${var.resource_names.prefix}${var.resource_names.separator}panic-button-on" + + auto_scaling_group = var.instance.enable_spot ? aws_autoscaling_group.on_spot[0] : aws_autoscaling_group.on_demand[0] } diff --git a/panic-button.tf b/panic-button-off.tf similarity index 55% rename from panic-button.tf rename to panic-button-off.tf index 852d112..5b4da60 100644 --- a/panic-button.tf +++ b/panic-button-off.tf @@ -1,13 +1,13 @@ -resource "aws_iam_role" "lambda_switch_off_execution" { +resource "aws_iam_role" "panic_button_off_execution" { name = "${var.resource_names.prefix}${var.resource_names.separator}panic-button-off" description = "Role for executing the bastion panic button switch off" - assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json + assume_role_policy = data.aws_iam_policy_document.panic_button_off_assume_role.json force_detach_policies = true tags = var.tags } -data "aws_iam_policy_document" "lambda_assume_role" { +data "aws_iam_policy_document" "panic_button_off_assume_role" { statement { actions = [ "sts:AssumeRole", @@ -21,7 +21,7 @@ data "aws_iam_policy_document" "lambda_assume_role" { } } -data "aws_iam_policy_document" "lambda_switch_off" { +data "aws_iam_policy_document" "panic_button_off" { statement { sid = "ListInstances" actions = [ @@ -53,51 +53,51 @@ data "aws_iam_policy_document" "lambda_switch_off" { } } -resource "aws_iam_policy" "lambda_switch_off" { +resource "aws_iam_policy" "panic_button_off" { name = "${var.resource_names.prefix}${var.resource_names.separator}switch-off" - policy = data.aws_iam_policy_document.lambda_switch_off.json + policy = data.aws_iam_policy_document.panic_button_off.json tags = var.tags } -resource "aws_iam_role_policy_attachment" "lambda_switch_off" { - role = aws_iam_role.lambda_switch_off_execution.name - policy_arn = aws_iam_policy.lambda_switch_off.arn +resource "aws_iam_role_policy_attachment" "panic_button_off" { + role = aws_iam_role.panic_button_off_execution.name + policy_arn = aws_iam_policy.panic_button_off.arn } -resource "aws_iam_role_policy_attachment" "gitlab_webhook_authorizer_execution_lambda_basic" { +resource "aws_iam_role_policy_attachment" "panic_button_off_basic_execution" { policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - role = aws_iam_role.lambda_switch_off_execution.id + role = aws_iam_role.panic_button_off_execution.id } -resource "aws_iam_role_policy_attachment" "gitlab_webhook_authorizer_execution_xray" { +resource "aws_iam_role_policy_attachment" "panic_button_off_x_ray" { policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess" - role = aws_iam_role.lambda_switch_off_execution.id + role = aws_iam_role.panic_button_off_execution.id } -data "archive_file" "panic_button_lambda_switch_off_package" { +data "archive_file" "panic_button_off_package" { type = "zip" source_file = local.panic_button_switch_off_lambda_source output_path = "${path.root}/builds/${local.panic_button_switch_off_lambda_source}.zip" } -resource "aws_lambda_function" "panic_button_switch_off" { +resource "aws_lambda_function" "panic_button_off" { architectures = ["arm64"] description = "Terminates all bastion hosts forever" - filename = data.archive_file.panic_button_lambda_switch_off_package.output_path - source_code_hash = data.archive_file.panic_button_lambda_switch_off_package.output_base64sha256 - function_name = "${var.resource_names.prefix}${var.resource_names.separator}switch-off" + filename = data.archive_file.panic_button_off_package.output_path + source_code_hash = data.archive_file.panic_button_off_package.output_base64sha256 + function_name = local.panic_button_switch_off_lambda_name handler = "panic_button_switch_off.handler" timeout = 30 memory_size = 256 #package_type = "Zip" publish = true - role = aws_iam_role.lambda_switch_off_execution.arn + role = aws_iam_role.panic_button_off_execution.arn runtime = "python3.9" environment { variables = { - AUTO_SCALING_GROUP_NAME = var.instance.enable_spot ? aws_autoscaling_group.on_spot[0].name : aws_autoscaling_group.on_demand[0].name + AUTO_SCALING_GROUP_NAME = local.auto_scaling_group.name BASTION_HOST_NAME = local.bastion_host_name LOG_LEVEL = "info" @@ -109,4 +109,14 @@ resource "aws_lambda_function" "panic_button_switch_off" { } tags = var.tags + + # otherwise the Lambda auto-creates the group which conflicts with Terraform + depends_on = [aws_cloudwatch_log_group.panic_button_off] +} + +resource "aws_cloudwatch_log_group" "panic_button_off" { + name = "/aws/lambda/${local.panic_button_switch_off_lambda_name}" + retention_in_days = 3 + + tags = var.tags } diff --git a/panic-button-on.tf b/panic-button-on.tf new file mode 100644 index 0000000..ee0292e --- /dev/null +++ b/panic-button-on.tf @@ -0,0 +1,124 @@ +resource "aws_iam_role" "panic_button_on_execution" { + name = "${var.resource_names.prefix}${var.resource_names.separator}panic-button-on" + description = "Role for executing the bastion panic button switch off" + assume_role_policy = data.aws_iam_policy_document.panic_button_on_assume_role.json + force_detach_policies = true + + tags = var.tags +} + +data "aws_iam_policy_document" "panic_button_on_assume_role" { + statement { + actions = [ + "sts:AssumeRole", + ] + effect = "Allow" + + principals { + identifiers = ["lambda.amazonaws.com"] + type = "Service" + } + } +} + +data "aws_iam_policy_document" "panic_button_on" { + statement { + sid = "ListInstances" + actions = [ + "ec2:DescribeInstances" + ] + resources = ["*"] + effect = "Allow" + } + + statement { + sid = "KillBastionHosts" + actions = [ + "ec2:StopInstances" + ] + resources = ["*"] + condition { + test = "StringEquals" + values = [local.bastion_host_name] + variable = "aws:ResourceTag/Name" + } + effect = "Allow" + } + + statement { + sid="UpdateASG" + actions = ["autoscaling:UpdateAutoScalingGroup"] + resources = [var.instance.enable_spot ? aws_autoscaling_group.on_spot[0].arn : aws_autoscaling_group.on_demand[0].arn] + effect = "Allow" + } +} + +resource "aws_iam_policy" "panic_button_on" { + name = "${var.resource_names.prefix}${var.resource_names.separator}switch-on" + policy = data.aws_iam_policy_document.panic_button_on.json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "panic_button_on" { + role = aws_iam_role.panic_button_on_execution.name + policy_arn = aws_iam_policy.panic_button_on.arn +} + +resource "aws_iam_role_policy_attachment" "panic_button_on_basic_execution" { + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = aws_iam_role.panic_button_on_execution.id +} + +resource "aws_iam_role_policy_attachment" "panic_button_on_x_ray" { + policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess" + role = aws_iam_role.panic_button_on_execution.id +} + +data "archive_file" "panic_button_on_package" { + type = "zip" + source_file = local.panic_button_switch_on_lambda_source + output_path = "${path.root}/builds/${local.panic_button_switch_on_lambda_source}.zip" +} + +resource "aws_lambda_function" "panic_button_on" { + architectures = ["arm64"] + description = "Start all bastion hosts immediately" + filename = data.archive_file.panic_button_on_package.output_path + source_code_hash = data.archive_file.panic_button_on_package.output_base64sha256 + function_name = local.panic_button_switch_on_lambda_name + handler = "panic_button_switch_on.handler" + timeout = 30 + memory_size = 256 + package_type = "Zip" + publish = true + role = aws_iam_role.panic_button_on_execution.arn + runtime = "python3.9" + + environment { + variables = { + AUTO_SCALING_GROUP_NAME = local.auto_scaling_group.name + AUTO_SCALING_MIN_SIZE = local.auto_scaling_group.min_size + AUTO_SCALING_MAX_SIZE = local.auto_scaling_group.max_size + AUTO_SCALING_DESIRED_CAPACITY = local.auto_scaling_group.desired_capacity + + LOG_LEVEL = "info" + } + } + + tracing_config { + mode = "Active" + } + + tags = var.tags + + # otherwise the Lambda auto-creates the group which conflicts with Terraform + depends_on = [aws_cloudwatch_log_group.panic_button_on] +} + +resource "aws_cloudwatch_log_group" "panic_button_on" { + name = "/aws/lambda/${local.panic_button_switch_on_lambda_name}" + retention_in_days = 3 + + tags = var.tags +} From 986cbcac7ebc5a743052b26b33971b0883165609 Mon Sep 17 00:00:00 2001 From: kayma Date: Thu, 16 Feb 2023 16:37:20 +0100 Subject: [PATCH 09/13] on button --- .gitignore | 2 + .../builds/panic_button_switch_off.py.zip | Bin 825 -> 0 bytes lambda/panic_button_switch_on.py | 48 +++++++----------- panic-button-off.tf | 4 +- panic-button-on.tf | 34 ++++--------- 5 files changed, 30 insertions(+), 58 deletions(-) delete mode 100644 examples/simple/builds/panic_button_switch_off.py.zip diff --git a/.gitignore b/.gitignore index f8e72db..4006a71 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ venv/ .terraform.lock.hcl *.tfstate* + +builds/ diff --git a/examples/simple/builds/panic_button_switch_off.py.zip b/examples/simple/builds/panic_button_switch_off.py.zip deleted file mode 100644 index 504fc2955d5bab38a680d4d34981c435454c6694..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 825 zcmWIWW@Zs#-~d7f2E{HQ0S8h*RzYH3W^#N|X-P?bUVL$RW=V2Je12M*UP0xO5Z~;_ zHUj&8hx62GZc;Q|`d;^e^DN)ZjV6(^woMmkQRmFaeDqCHgszFRo@l+ z3wK|PdO1m-wer=^Qxg^&AGv++O`*x$59=@Ad~6wZ+V9oPF8#dQOb@t^OkwyN;J40z zJy=BC^Yq1+0d+Z(7JYiK)O2>~;q(XI0i2QAlddSu?42sHd9sLDRKd5UXE`EYrp@JU zztjF{^QXf6$7j~mO8#o^Tw(ZoM>pRcZQEdb!`<%=?7hMInc>UNpG`}2p4Dy?3)(sT zja8;lbmjakDHcY#+x#Vq%8$PBTftSVKc#F*bBW&Gj{587?^M43K4Bp6q+hhl;KY~7 zOXq*+|MZOcU*Nr(6TbsfW}f#cR>{07^z)$L{gtN{F3tIO%I5tV?lYa6Zr_kiIKSwY z!bZI_+}~dPu1N0QzW?d(KQfHpUW7ksIjsGcuX}Ca^Ee;bghrul711j1*&b_s-+cD_ z_Y#qwK)%+E?G^#QJluXYw3KPjvcIL+EOvZv#kvzSzjFE?KQv|TqW_CqzRBCQ^v?e) zo#eG^?W%t(7REC&F#P`?;LXkv^j>|#Ph60p5&EBFu=yjVuRB+%T}M5yZls W)C0U(*+427fzSg;-v*k`zyJV{b#iS0 diff --git a/lambda/panic_button_switch_on.py b/lambda/panic_button_switch_on.py index c78c289..b671c31 100644 --- a/lambda/panic_button_switch_on.py +++ b/lambda/panic_button_switch_on.py @@ -9,45 +9,31 @@ def handler(event, context): - # change the ASG to disable automatic restart - disable_asg(os.environ['AUTO_SCALING_GROUP_NAME']) - - # find the EC2 instances and kill them - kill_running_bastion_hosts(os.environ['BASTION_HOST_NAME']) - - logger.info("Bastion host(s) switched off") - -def disable_asg(autoscaling_group_name): asg = boto3.client('autoscaling') - try: - asg.update_auto_scaling_group(AutoScalingGroupName=autoscaling_group_name, MinSize=0, MaxSize=0, - DesiredCapacity=0) - except ClientError as e: - logger.error('Failed to update the ASG %s', autoscaling_group_name, exc_info=e) - - raise - - -def kill_running_bastion_hosts(name): - ec2 = boto3.client('ec2') + auto_scaling_group_name = os.environ['AUTO_SCALING_GROUP_NAME'] try: - instances = ec2.describe_instances(Filters=[{'Name': 'tag:Name', 'Values': [f'{name}']}, - {'Name': 'instance-state-name', 'Values': ['pending', 'running']}]) + # set min/max/desired + asg.update_auto_scaling_group(AutoScalingGroupName=auto_scaling_group_name, + MinSize=int(os.environ['AUTO_SCALING_GROUP_MIN_SIZE']), + MaxSize=int(os.environ['AUTO_SCALING_GROUP_MAX_SIZE']), + DesiredCapacity=int(os.environ['AUTO_SCALING_GROUP_DESIRED_CAPACITY'])) - if 'Reservations' in instances: - instance_ids = [] + # remove all schedules + response = asg.describe_scheduled_actions(AutoScalingGroupName=auto_scaling_group_name); - for r in instances['Reservations']: - for i in r['Instances']: - instance_ids.append(i['InstanceId']) + schedule_names = [] - if instance_ids: - ec2.stop_instances(InstanceIds=instance_ids) + for schedule in response['ScheduledUpdateGroupActions']: + schedule_names.append(schedule['ScheduledActionName']) - logger.info("Bastion killed: %s", instance_ids) + if schedule_names: + asg.batch_delete_scheduled_action(AutoScalingGroupName=auto_scaling_group_name, + ScheduledActionNames=schedule_names) except ClientError as e: - logger.error('Failed to kill the bastion EC2 instance(s): %s', name, exc_info=e) + logger.error('Failed to update the ASG %s', auto_scaling_group_name, exc_info=e) raise + + logger.info("Bastion host(s) switched on") diff --git a/panic-button-off.tf b/panic-button-off.tf index 5b4da60..51a22be 100644 --- a/panic-button-off.tf +++ b/panic-button-off.tf @@ -48,7 +48,7 @@ data "aws_iam_policy_document" "panic_button_off" { statement { sid="UpdateASG" actions = ["autoscaling:UpdateAutoScalingGroup"] - resources = [var.instance.enable_spot ? aws_autoscaling_group.on_spot[0].arn : aws_autoscaling_group.on_demand[0].arn] + resources = [local.auto_scaling_group.arn] effect = "Allow" } } @@ -78,7 +78,7 @@ resource "aws_iam_role_policy_attachment" "panic_button_off_x_ray" { data "archive_file" "panic_button_off_package" { type = "zip" source_file = local.panic_button_switch_off_lambda_source - output_path = "${path.root}/builds/${local.panic_button_switch_off_lambda_source}.zip" + output_path = "${path.root}/builds/${local.panic_button_switch_off_lambda_source_file_name}.zip" } resource "aws_lambda_function" "panic_button_off" { diff --git a/panic-button-on.tf b/panic-button-on.tf index ee0292e..3fb8b5b 100644 --- a/panic-button-on.tf +++ b/panic-button-on.tf @@ -23,32 +23,16 @@ data "aws_iam_policy_document" "panic_button_on_assume_role" { data "aws_iam_policy_document" "panic_button_on" { statement { - sid = "ListInstances" - actions = [ - "ec2:DescribeInstances" - ] - resources = ["*"] + sid="UpdateASG" + actions = ["autoscaling:UpdateAutoScalingGroup", "autoscaling:DeleteScheduledAction"] + resources = [local.auto_scaling_group.arn] effect = "Allow" } statement { - sid = "KillBastionHosts" - actions = [ - "ec2:StopInstances" - ] + sid="DescribeASG" + actions = ["autoscaling:DescribeScheduledActions"] resources = ["*"] - condition { - test = "StringEquals" - values = [local.bastion_host_name] - variable = "aws:ResourceTag/Name" - } - effect = "Allow" - } - - statement { - sid="UpdateASG" - actions = ["autoscaling:UpdateAutoScalingGroup"] - resources = [var.instance.enable_spot ? aws_autoscaling_group.on_spot[0].arn : aws_autoscaling_group.on_demand[0].arn] effect = "Allow" } } @@ -78,7 +62,7 @@ resource "aws_iam_role_policy_attachment" "panic_button_on_x_ray" { data "archive_file" "panic_button_on_package" { type = "zip" source_file = local.panic_button_switch_on_lambda_source - output_path = "${path.root}/builds/${local.panic_button_switch_on_lambda_source}.zip" + output_path = "${path.root}/builds/${local.panic_button_switch_on_lambda_source_file_name}.zip" } resource "aws_lambda_function" "panic_button_on" { @@ -98,9 +82,9 @@ resource "aws_lambda_function" "panic_button_on" { environment { variables = { AUTO_SCALING_GROUP_NAME = local.auto_scaling_group.name - AUTO_SCALING_MIN_SIZE = local.auto_scaling_group.min_size - AUTO_SCALING_MAX_SIZE = local.auto_scaling_group.max_size - AUTO_SCALING_DESIRED_CAPACITY = local.auto_scaling_group.desired_capacity + AUTO_SCALING_GROUP_MIN_SIZE = local.auto_scaling_group.min_size + AUTO_SCALING_GROUP_MAX_SIZE = local.auto_scaling_group.max_size + AUTO_SCALING_GROUP_DESIRED_CAPACITY = local.auto_scaling_group.desired_capacity LOG_LEVEL = "info" } From 9d5c65f93916e1061b6e906f5f99f319a3934f76 Mon Sep 17 00:00:00 2001 From: kayma Date: Thu, 16 Feb 2023 16:41:33 +0100 Subject: [PATCH 10/13] add docs --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 015b9e5..fe209b0 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,15 @@ Check the `examples` directory for the module usage. - use spot instances to save some money - provide IAM role for easy access - provide a script to connect to the bastion from your local machine +- panic switch to enable the bastions or disable them immediately + +### Panic Switch + +Two lambda functions are provided. One to enable the bastion host, e.g. if you have to work at night and the bastion +hosts are deactivated. The second lambda function disables the bastion host immediately no matter what. + +As both functions are destructive (they modify the autoscaling group), you have to re-apply this module as soon as +possible to restore the auto scaling setting (especially the schedules). ### Keepass Support For IAM User Credentials From 85b5f1c3547886688eb099b0da7f0996f4d694aa Mon Sep 17 00:00:00 2001 From: kayma Date: Thu, 16 Feb 2023 16:49:15 +0100 Subject: [PATCH 11/13] costs --- README.md | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index fe209b0..9572a0c 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,38 @@ can be realized by the user by creating multiple connections to the bastion host Check the `examples` directory for the module usage. -## Cost Estimation (for version 1.9.1) +## Cost Estimation (for version 2.4.0) ```text - Name Monthly Qty Unit Monthly Cost - - module.bastion_host.aws_autoscaling_group.on_spot[0] +Name Monthly Qty Unit Monthly Cost +module.bastion_host.aws_autoscaling_group.on_spot[0] └─ module.bastion_host.aws_launch_template.manual_start - └─ Instance usage (Linux/UNIX, spot, t3.nano) 1,460 hours $2.63 - └─ root_block_device - └─ Storage (general purpose SSD, gp3) 16 GB $1.52 - - OVERALL TOTAL $4.15 + ├─ Instance usage (Linux/UNIX, on-demand, t3.nano) 730 hours $4.38 + └─ root_block_device + └─ Storage (general purpose SSD, gp3) 16 GB $1.52 + └─ Instance usage (Linux/UNIX, spot, t3.nano) 730 hours $1.31 + └─ root_block_device + └─ Storage (general purpose SSD, gp3) 16 GB $1.52 + + module.bastion_host.aws_cloudwatch_log_group.panic_button_off + ├─ Data ingested Monthly cost depends on usage: $0.63 per GB + ├─ Archival Storage Monthly cost depends on usage: $0.0324 per GB + └─ Insights queries data scanned Monthly cost depends on usage: $0.0063 per GB + + module.bastion_host.aws_cloudwatch_log_group.panic_button_on + ├─ Data ingested Monthly cost depends on usage: $0.63 per GB + ├─ Archival Storage Monthly cost depends on usage: $0.0324 per GB + └─ Insights queries data scanned Monthly cost depends on usage: $0.0063 per GB + + module.bastion_host.aws_lambda_function.panic_button_off + ├─ Requests Monthly cost depends on usage: $0.20 per 1M requests + └─ Duration Monthly cost depends on usage: $0.0000166667 per GB-seconds + + module.bastion_host.aws_lambda_function.panic_button_on + ├─ Requests Monthly cost depends on usage: $0.20 per 1M requests + └─ Duration Monthly cost depends on usage: $0.0000166667 per GB-seconds + + OVERALL TOTAL $8.73 ``` ## Features From d646fb6b80853df8386005ae91e9f4fe1ad6fff6 Mon Sep 17 00:00:00 2001 From: kayma Date: Thu, 16 Feb 2023 16:58:44 +0100 Subject: [PATCH 12/13] fix logging --- lambda/panic_button_switch_off.py | 4 +--- lambda/panic_button_switch_on.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lambda/panic_button_switch_off.py b/lambda/panic_button_switch_off.py index c78c289..79470fa 100644 --- a/lambda/panic_button_switch_off.py +++ b/lambda/panic_button_switch_off.py @@ -3,10 +3,8 @@ import boto3 from botocore.exceptions import ClientError -logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=os.environ.get('LOG_LEVEL', 'info')) - logger = logging.getLogger(__name__) - +logger.setLevel(os.environ.get('LOG_LEVEL', 'info').upper()) def handler(event, context): # change the ASG to disable automatic restart diff --git a/lambda/panic_button_switch_on.py b/lambda/panic_button_switch_on.py index b671c31..8266527 100644 --- a/lambda/panic_button_switch_on.py +++ b/lambda/panic_button_switch_on.py @@ -3,10 +3,8 @@ import boto3 from botocore.exceptions import ClientError -logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=os.environ.get('LOG_LEVEL', 'info')) - logger = logging.getLogger(__name__) - +logger.setLevel(os.environ.get('LOG_LEVEL', 'info').upper()) def handler(event, context): asg = boto3.client('autoscaling') From 53c81e514f9764da58d8b54e8c2871f60e032874 Mon Sep 17 00:00:00 2001 From: kayma Date: Thu, 16 Feb 2023 17:03:06 +0100 Subject: [PATCH 13/13] tfsec --- panic-button-off.tf | 4 ++++ panic-button-on.tf | 2 ++ 2 files changed, 6 insertions(+) diff --git a/panic-button-off.tf b/panic-button-off.tf index 51a22be..84396ed 100644 --- a/panic-button-off.tf +++ b/panic-button-off.tf @@ -36,6 +36,8 @@ data "aws_iam_policy_document" "panic_button_off" { actions = [ "ec2:StopInstances" ] + # we do not know the instances as they are created dynamically. But we use a condition to allow valid ones only + # tfsec:ignore:aws-iam-no-policy-wildcards resources = ["*"] condition { test = "StringEquals" @@ -118,5 +120,7 @@ resource "aws_cloudwatch_log_group" "panic_button_off" { name = "/aws/lambda/${local.panic_button_switch_off_lambda_name}" retention_in_days = 3 + kms_key_id = var.kms_key_arn + tags = var.tags } diff --git a/panic-button-on.tf b/panic-button-on.tf index 3fb8b5b..f2fd173 100644 --- a/panic-button-on.tf +++ b/panic-button-on.tf @@ -104,5 +104,7 @@ resource "aws_cloudwatch_log_group" "panic_button_on" { name = "/aws/lambda/${local.panic_button_switch_on_lambda_name}" retention_in_days = 3 + kms_key_id = var.kms_key_arn + tags = var.tags }