Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add panic switch (on/off) #148

Merged
merged 14 commits into from
Feb 16, 2023
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,14 @@ way you can access the database, Redis cluster, ... directly from your localhost

| Name | Version |
|------|---------|
| <a name="provider_archive"></a> [archive](#provider\_archive) | 2.3.0 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | 4.24.0 |

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_instance_profile_role"></a> [instance\_profile\_role](#module\_instance\_profile\_role) | terraform-aws-modules/iam/aws//modules/iam-assumable-role | 5.2.0 |
| <a name="module_instance_profile_role"></a> [instance\_profile\_role](#module\_instance\_profile\_role) | terraform-aws-modules/iam/aws//modules/iam-assumable-role | 5.11.1 |

## Resources

Expand All @@ -217,26 +218,36 @@ 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_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 |
| [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_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

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_ami_name_filter"></a> [ami\_name\_filter](#input\_ami\_name\_filter) | The search filter string for the bastion AMI. | `string` | `"amzn2-ami-hvm-*-x86_64-ebs"` | no |
| <a name="input_bastion_access_tag_value"></a> [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 |
| <a name="input_egress_open_tcp_ports"></a> [egress\_open\_tcp\_ports](#input\_egress\_open\_tcp\_ports) | The list of TCP ports to open for outgoing traffic. | `list(number)` | n/a | yes |
| <a name="input_iam_role_path"></a> [iam\_role\_path](#input\_iam\_role\_path) | Role path for the created bastion instance profile. Must end with '/' | `string` | `"/"` | no |
| <a name="input_iam_user_arns"></a> [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 |
| <a name="input_instance"></a> [instance](#input\_instance) | Defines the basic parameters for the EC2 instance used as Bastion host | <pre>object({<br> type = string # EC2 instance type<br> desired_capacity = number # number of EC2 instances to run<br> root_volume_size = number # in GB<br> enable_monitoring = bool<br><br> enable_spot = bool<br> })</pre> | <pre>{<br> "desired_capacity": 1,<br> "enable_monitoring": false,<br> "enable_spot": false,<br> "root_volume_size": 8,<br> "type": "t3.nano"<br>}</pre> | no |
| <a name="input_iam_role_path"></a> [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 |
| <a name="input_iam_user_arns"></a> [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 |
| <a name="input_instance"></a> [instance](#input\_instance) | Defines the basic parameters for the EC2 instance used as Bastion host | <pre>object({<br> type = string # EC2 instance type<br> desired_capacity = number # number of EC2 instances to run<br> root_volume_size = number # in GB<br> enable_monitoring = bool<br><br> enable_spot = bool<br><br> profile_name = string<br> })</pre> | <pre>{<br> "desired_capacity": 1,<br> "enable_monitoring": false,<br> "enable_spot": false,<br> "profile_name": "",<br> "root_volume_size": 8,<br> "type": "t3.nano"<br>}</pre> | no |
| <a name="input_instances_distribution"></a> [instances\_distribution](#input\_instances\_distribution) | Defines the parameters for mixed instances policy auto scaling | <pre>object({<br> on_demand_base_capacity = number # absolute minimum amount of on_demand instances<br> on_demand_percentage_above_base_capacity = number # percentage split between on-demand and Spot instances<br> spot_allocation_strategy = string<br> })</pre> | <pre>{<br> "on_demand_base_capacity": 0,<br> "on_demand_percentage_above_base_capacity": 0,<br> "spot_allocation_strategy": "lowest-price"<br>}</pre> | no |
| <a name="input_kms_key_arn"></a> [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key used to encrypt the resources. | `string` | `null` | no |
| <a name="input_resource_names"></a> [resource\_names](#input\_resource\_names) | Settings for generating resource names. Set the prefix and the separator according to your company style guide. | <pre>object({<br> prefix = string<br> separator = string<br> })</pre> | <pre>{<br> "prefix": "bastion",<br> "separator": "-"<br>}</pre> | no |
| <a name="input_schedule"></a> [schedule](#input\_schedule) | Defines when to start and stop the instances. Use 'start' and 'stop' with a cron expression and add the 'time\_zone'. | <pre>object({<br> start = string<br> stop = string<br> time_zone = string<br> })</pre> | `null` | no |
Expand Down
22 changes: 22 additions & 0 deletions lambda/panic_button_switch_off.py
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
80 changes: 80 additions & 0 deletions panic-button.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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 = "/"
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 "aws_iam_policy_document" "lambda_switch_off" {
statement {
sid = "KillBastionHosts"
actions = [
"ec2:DescribeInstances",
"ec2:StopInstances"
]
resources = ["*"]
effect = "Allow"
}
}
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved

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
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_switch_off.arn
runtime = "python3.8"
timeout = 30

environment {
variables = {
BASTION_HOST_NAME = local.bastion_host_name
}
}

tracing_config {
mode = "Passthrough"
}

tags = var.tags
}