diff --git a/terraform/README.md b/terraform/README.md index 486f0a8..5342a20 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -20,7 +20,7 @@ Run terraform init. ``` make init -``` +``` Plan terraform changes, to verify. @@ -33,7 +33,7 @@ make plan ``` make plan -``` +``` verify your changes are executed as you'd like. @@ -48,3 +48,26 @@ follow the stream, and verify your changes have been applied using the AWS Conso ``` make ${your_action_here} ``` + +### Secrets + +Secrets in AWS are managed via KMS + +Each project should have a unique key + +#### Generate Key +``` +aws kms create-key \ + --profile hashbang \ + --description "some-app" \ + --region us-west-2 +``` + +#### Encrypt Secret +``` +aws kms encrypt \ + --profile hashbang \ + --key-id fe5d40fe-dcf6-1558-4080-096648020239 \ + --plaintext "$(pass Hashbang/someapp | head -n1)" \ + --output text --query CiphertextBlob +``` diff --git a/terraform/environments/ci/drone.tf b/terraform/environments/ci/drone.tf new file mode 100644 index 0000000..ae334a9 --- /dev/null +++ b/terraform/environments/ci/drone.tf @@ -0,0 +1,33 @@ +module "drone_balancer" { + source = "../../modules/route53_balancer" + domain = "ci.hashbang.sh" + name = "droneci-balancer" + asg = "${module.drone_asg.id}" + launch_topic = "${module.drone_asg.launch_topic}" + terminate_topic = "${module.drone_asg.terminate_topic}" +} + +module "drone_asg" { + source = "../../modules/coreos_asg" + name = "drone" + key_name = "admin" + subnets = "${module.vpc.public_subnets}" + vpc_id = "${module.vpc.vpc_id}" + kms_key_id = "8c2d565d-1998-4c82-baba-50c98fc2841e" + cloud_config = "${data.template_file.cloud_config.rendered}" +} + +data "template_file" "cloud_config" { + template = "${file("${path.module}/files/drone-cloud-config.yml")}" + vars { + aws_region = "us-west-2" + kms_drone_github_client = "AQICAHgdKTQKPCVSxRc0j0autqD7bCgQSjpitnlAfGw9dPM7RwFyqCRmVD71lSGwEOCO2EepAAAAcjBwBgkqhkiG9w0BBwagYzBhAgEAMFwGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM4Ct8bkQtrK2s1m4EAgEQgC8rNab4vAJuzTxrB6/2O3q+Z4QnDQ6YJGPMY1pIWP9RXPMZ45lPmDpwiouKO9TcPg==" + kms_drone_github_secret = "AQICAHgdKTQKPCVSxRc0j0autqD7bCgQSjpitnlAfGw9dPM7RwHuoOEi30zJqK76A1SsLI5aAAAAhzCBhAYJKoZIhvcNAQcGoHcwdQIBADBwBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDEI8ORtXn4gamHlUbgIBEIBD5GIJ//Fr8XRmvmy1Zht+04HIkXnNqnPwPJNlHEGPmop8K63WYxuDEZpZAtY7dY1c+E7omAUNCS5vKSPBQ1zePjDpzQ==" + kms_drone_secret = "AQICAHgdKTQKPCVSxRc0j0autqD7bCgQSjpitnlAfGw9dPM7RwEfn8AuPpq5mUHeHCd/Qe9SAAAAkjCBjwYJKoZIhvcNAQcGoIGBMH8CAQAwegYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAyxt5KAogGEsGdDiXwCARCATWFPSW/o0t5i9Qy72CeyYtYmTjM8Pq85VRJ3rc6jD52DAhYkp1lFgvMaV0UmPCU/awdQN55Kj+lQrvi6JWi7K75BvkVNDzBUP6F2vu+J" + drone_orgs = "hashbang" + decrypt_script = "${file("${path.module}/files/decrypt.sh")}" + efs_id = "${module.drone_asg.efs_id}" + domain = "ci.hashbang.sh" + drone_admins = "dpflug,daurnimator,deviavir,drgrove,kellerfuchs,lrvick,ryansquared,singlerider" + } +} diff --git a/terraform/environments/ci/files/decrypt.sh b/terraform/environments/ci/files/decrypt.sh new file mode 100644 index 0000000..9820243 --- /dev/null +++ b/terraform/environments/ci/files/decrypt.sh @@ -0,0 +1,19 @@ +#!/bin/bash +unset IFS +out_path=${1:-/out/secrets.env} +rm "$out_path" 2> /dev/null +for line in $(env | egrep '^KMS_'); do + kms_key="${line%%=*}" + key=${kms_key/KMS_/} + encrypted_value=${line#*=} + decrypted_value_base64=$( \ + aws kms decrypt \ + --ciphertext-blob fileb://<(echo "$encrypted_value" | base64 -d) \ + --query Plaintext \ + --output text + ) + decrypted_value=$(echo $decrypted_value_base64 | base64 -d) + echo "key=$key" + echo "encrypted_value=$encrypted_value" + echo "export $key=$decrypted_value" >> "$out_path" +done diff --git a/terraform/environments/ci/files/drone-cloud-config.yml b/terraform/environments/ci/files/drone-cloud-config.yml new file mode 100644 index 0000000..69efef5 --- /dev/null +++ b/terraform/environments/ci/files/drone-cloud-config.yml @@ -0,0 +1,122 @@ +#cloud-config +hostname: "drone" +coreos: + units: + - name: update-engine.service + command: stop + - name: locksmithd.service + command: stop + - name: mnt-shared.mount + command: start + content: | + [Mount] + What=${efs_id}.efs.${aws_region}.amazonaws.com:/ + Where=/mnt/shared + Type=nfs + - name: drone-server-secrets.service + enable: true + content: | + [Unit] + Description=DroneCI Server Secrets Writer + After=docker.service + Requires=docker.service + [Install] + WantedBy=multi-user.target + [Service] + TimeoutStartSec=0 + ExecStart=/usr/bin/docker run \ + --env "AWS_DEFAULT_REGION=${aws_region}" \ + --env "KMS_DRONE_GITHUB_CLIENT=${kms_drone_github_client}" \ + --env "KMS_DRONE_GITHUB_SECRET=${kms_drone_github_secret}" \ + --env "KMS_DRONE_SECRET=${kms_drone_secret}" \ + --volume drone-server-secrets:/secrets \ + --volume /usr/bin/bash:/usr/bin/bash \ + --volume /lib64:/lib64 \ + --volume /home/core/scripts/decrypt.sh:/usr/bin/decrypt.sh \ + --entrypoint /usr/bin/bash \ + quay.io/coreos/awscli decrypt.sh "/secrets/secrets.env" + - name: drone-agent-secrets.service + enable: true + content: | + [Unit] + Description=DroneCI Agent Secrets Writer + Requires=docker.service + After=docker.service + [Install] + WantedBy=multi-user.target + [Service] + TimeoutStartSec=0 + ExecStart=/usr/bin/docker run \ + --env "AWS_DEFAULT_REGION=${aws_region}" \ + --env "KMS_DRONE_SECRET=${kms_drone_secret}" \ + --volume drone-agent-secrets:/secrets \ + --volume /lib64:/lib64 \ + --volume /usr/bin/bash:/usr/bin/bash \ + --volume /home/core/scripts/decrypt.sh:/usr/bin/decrypt.sh \ + --entrypoint /usr/bin/bash \ + quay.io/coreos/awscli decrypt.sh "/secrets/secrets.env" + - name: drone-server.service + enable: true + command: start + content: | + [Unit] + Description=Drone CI Server + Requires=drone-server-secrets.service + After=drone-server-secrets.service + [Install] + WantedBy=multi-user.target + [Service] + Restart=always + TimeoutStartSec=0 + ExecStartPre=-/usr/bin/docker kill %p + ExecStartPre=-/usr/bin/docker rm %p + ExecStartPre=/usr/bin/docker pull drone/drone:0.6 + ExecStart=/usr/bin/docker run \ + --name %p \ + --volume /mnt/shared/drone:/var/lib/drone \ + --volume drone-server-secrets:/secrets/ \ + --volume /lib64:/lib64 \ + --volume /usr/bin/bash:/usr/bin/bash \ + --entrypoint="/usr/bin/bash" \ + --publish 80:80 \ + --publish 443:443 \ + --env DRONE_OPEN=true \ + --env DRONE_GITHUB=true \ + --env DRONE_HOST=https://${domain} \ + --env DRONE_ADMIN=${drone_admins} \ + --env DRONE_LETS_ENCRYPT=true \ + drone/drone -c 'source /secrets/secrets.env; /drone server' + ExecStop=/usr/bin/docker stop %p + - name: drone-agent.service + enable: true + command: start + content: | + [Unit] + Description=Drone CI Agent + Requires=drone-agent-secrets.service + After=drone-agent-secrets.service + [Install] + WantedBy=multi-user.target + [Service] + Restart=always + TimeoutStartSec=0 + ExecStartPre=-/usr/bin/docker kill %p + ExecStartPre=-/usr/bin/docker rm %p + ExecStartPre=/usr/bin/docker pull drone/drone:0.6 + ExecStart=/usr/bin/docker run \ + --name %p \ + --volumes-from drone-server \ + --volume drone-agent-secrets:/secrets/ \ + --volume /lib64:/lib64 \ + --volume /usr/bin/bash:/usr/bin/bash \ + --entrypoint="/usr/bin/bash" \ + --volume /var/run/docker.sock:/var/run/docker.sock \ + --env DRONE_SERVER=ws://drone-server:8000/ws/broker \ + drone/drone -c 'source /secrets/secrets.env; /drone agent' + ExecStop=/usr/bin/docker stop %p +write_files: + - path: "/home/core/scripts/decrypt.sh" + permissions: "0755" + owner: "core" + encoding: base64 + content: ${base64encode(decrypt_script)} diff --git a/terraform/environments/ci/main.tf b/terraform/environments/ci/main.tf new file mode 100644 index 0000000..7ed1436 --- /dev/null +++ b/terraform/environments/ci/main.tf @@ -0,0 +1,22 @@ +terraform { + backend "s3" { + bucket = "hashbang-terraform-ci" + key = "state.tfstate" + region = "us-west-2" + lock_table = "hashbang-terraform-ci" + } +} + +provider "aws" { + region = "us-west-2" +} + +module "vpc" { + source = "github.com/terraform-community-modules/tf_aws_vpc?ref=v1.0.6" + name = "ci" + cidr = "10.0.0.0/16" + public_subnets = ["10.0.101.0/24", "10.0.102.0/24"] + azs = ["us-west-2a", "us-west-2b"] + enable_dns_hostnames = "true" + enable_dns_support = "true" +} diff --git a/terraform/modules/r53/main.tf b/terraform/environments/production/dns.tf similarity index 100% rename from terraform/modules/r53/main.tf rename to terraform/environments/production/dns.tf diff --git a/terraform/environments/production/main.tf b/terraform/environments/production/main.tf new file mode 100644 index 0000000..69b5adb --- /dev/null +++ b/terraform/environments/production/main.tf @@ -0,0 +1,23 @@ +terraform { + required_version = "> 0.8.4" + backend "s3" { + bucket = "hashbang-terraform-production" + key = "state.tfstate" + region = "us-west-2" + lock_table = "hashbang-terraform-production" + } +} + +provider "aws" { + region = "us-west-2" +} + +module "vpc" { + source = "github.com/terraform-community-modules/tf_aws_vpc?ref=v1.0.6" + name = "production" + cidr = "10.0.0.0/16" + public_subnets = ["10.0.103.0/24", "10.0.104.0/24"] + azs = ["us-west-2a", "us-west-2b"] + enable_dns_hostnames = "true" + enable_dns_support = "true" +} diff --git a/terraform/main.tf b/terraform/main.tf deleted file mode 100644 index a4ccd7b..0000000 --- a/terraform/main.tf +++ /dev/null @@ -1,21 +0,0 @@ -terraform { - required_version = "> 0.8.4" - backend "s3" { - bucket = "hashbang-terraform" - key = "state.tfstate" - region = "us-west-2" - lock_table = "hashbang-terraform" - } -} - -variable "region" { - default = "us-west-2" -} - -provider "aws" { - region = "${var.region}" -} - -module "r53" { - source = "./modules/r53" -} diff --git a/terraform/modules/coreos_asg/main.tf b/terraform/modules/coreos_asg/main.tf new file mode 100644 index 0000000..0e174f7 --- /dev/null +++ b/terraform/modules/coreos_asg/main.tf @@ -0,0 +1,223 @@ +data "aws_region" "selected" { + current = true +} + +data "aws_caller_identity" "current" {} + +resource "aws_ecs_cluster" "cluster" { + name = "${var.name}" +} + +data "aws_ami" "coreos_stable" { + most_recent = true + owners = ["595879546273"] + filter { + name = "architecture" + values = ["x86_64"] + } + filter { + name = "virtualization-type" + values = ["hvm"] + } + filter { + name = "name" + values = ["CoreOS-stable-*"] + } +} + +module "ec2_security_group" { + source = "../security_group" + vpc_id = "${var.vpc_id}" + name = "${var.name}-ec2" + all_internal = true + all_outbound = true + all_inbound_ssh = true + all_inbound_http = true + all_inbound_https = true + group_inbound_nfs = true + group_inbound_nfs_id = "${module.efs_security_group.id}" +} + +module "efs_security_group" { + source = "../security_group" + vpc_id = "${var.vpc_id}" + name = "${var.name}-efs" + group_outbound_nfs = true + group_outbound_nfs_id = "${module.ec2_security_group.id}" + group_inbound_nfs = true + group_inbound_nfs_id = "${module.ec2_security_group.id}" +} + +data "template_file" "cloud_config" { + template = "${var.cloud_config}" + vars { + aws_region = "${data.aws_region.selected.id}" + efs_id = "${aws_efs_file_system.shared.id}" + log_group_name = "${aws_cloudwatch_log_group.default.name}" + } +} + +resource "aws_efs_file_system" "shared" { + creation_token = "persist" + tags { + Name = "${var.name}-shared" + } +} + +resource "aws_efs_mount_target" "nfs" { + count = "${length(var.subnets)}" + file_system_id = "${aws_efs_file_system.shared.id}" + subnet_id = "${element(var.subnets, count.index)}" + security_groups = ["${module.efs_security_group.id}"] +} + +resource "aws_autoscaling_group" "cluster" { + name = "${aws_launch_configuration.cluster.name}" + max_size = "${var.instances_max}" + min_size = "${var.instances_min}" + desired_capacity = "${var.instances_desired}" + launch_configuration = "${aws_launch_configuration.cluster.name}" + vpc_zone_identifier = ["${var.subnets}"] + tag { + key = "Name" + value = "${var.name}" + propagate_at_launch = true + } + lifecycle = { + create_before_destroy = true + } +} + +resource "aws_iam_role" "hook" { + name = "${var.name}-hook" + assume_role_policy = "${data.aws_iam_policy_document.hook_assume_role.json}" +} + +data "aws_iam_policy_document" "hook_assume_role" { + statement { + sid = "" + effect = "Allow" + principals { + type = "Service" + identifiers = ["autoscaling.amazonaws.com"] + } + actions = ["sts:AssumeRole"] + } +} + +resource "aws_iam_role_policy" "hook" { + name = "${var.name}-instance" + role = "${aws_iam_role.hook.name}" + policy = "${data.aws_iam_policy_document.hook_policy.json}" +} + +data "aws_iam_policy_document" "hook_policy" { + statement { + sid = "" + effect = "Allow" + resources = [ + "${aws_sns_topic.instance_launch.arn}", + "${aws_sns_topic.instance_terminate.arn}" + ] + actions = ["sns:Publish"] + } +} + +resource "aws_sns_topic" "instance_launch" { + name = "${var.name}-instance-launch" +} + +resource "aws_sns_topic" "instance_terminate" { + name = "${var.name}-instance-terminate" +} + +resource "aws_autoscaling_lifecycle_hook" "instance_launch" { + name = "${var.name}-launch" + autoscaling_group_name = "${aws_autoscaling_group.cluster.name}" + default_result = "CONTINUE" + heartbeat_timeout = "${var.launch_delay}" + lifecycle_transition = "autoscaling:EC2_INSTANCE_LAUNCHING" + notification_target_arn = "${aws_sns_topic.instance_launch.arn}" + role_arn = "${aws_iam_role.hook.arn}" +} + +resource "aws_autoscaling_lifecycle_hook" "instance_terminate" { + name = "${var.name}-terminate" + autoscaling_group_name = "${aws_autoscaling_group.cluster.name}" + default_result = "CONTINUE" + heartbeat_timeout = "${var.terminate_delay}" + lifecycle_transition = "autoscaling:EC2_INSTANCE_TERMINATING" + notification_target_arn = "${aws_sns_topic.instance_terminate.arn}" + role_arn = "${aws_iam_role.hook.arn}" +} + +resource "aws_launch_configuration" "cluster" { + name_prefix = "${var.name}" + instance_type = "${var.instance_type}" + image_id = "${data.aws_ami.coreos_stable.id}" + iam_instance_profile = "${aws_iam_instance_profile.instance.name}" + key_name = "${var.key_name}" + user_data = "${data.template_file.cloud_config.rendered}" + lifecycle = { + create_before_destroy = true + } + security_groups = [ + "${module.ec2_security_group.id}" + ] +} + +resource "aws_iam_instance_profile" "instance" { + name = "${var.name}-instance" + roles = ["${aws_iam_role.instance.name}"] +} + +resource "aws_iam_role" "instance" { + name = "${var.name}-instance" + assume_role_policy = "${data.aws_iam_policy_document.instance_assume_role.json}" +} + +data "aws_iam_policy_document" "instance_assume_role" { + statement { + sid = "" + effect = "Allow" + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + actions = ["sts:AssumeRole"] + } +} + +resource "aws_iam_role_policy" "instance" { + name = "${var.name}-instance" + role = "${aws_iam_role.instance.name}" + policy = "${data.aws_iam_policy_document.instance_role.json}" +} + +data "aws_iam_policy_document" "instance_role" { + statement { + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:DescribeKey" + ] + resources = [ + "arn:aws:kms:${data.aws_region.selected.id}:${data.aws_caller_identity.current.account_id}:key/${var.kms_key_id}", + ] + } + statement { + sid = "AllowLoggingToCloudWatch" + effect = "Allow" + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + resources = [ + "arn:aws:logs:${data.aws_region.selected.id}:${data.aws_caller_identity.current.account_id}:log-group:${var.name}/*" + ] + } +} + +resource "aws_cloudwatch_log_group" "default" { + name = "${var.name}" +} diff --git a/terraform/modules/coreos_asg/outputs.tf b/terraform/modules/coreos_asg/outputs.tf new file mode 100644 index 0000000..5300088 --- /dev/null +++ b/terraform/modules/coreos_asg/outputs.tf @@ -0,0 +1,27 @@ +output "name" { + value = "${var.name}" +} + +output "id" { + value = "${aws_autoscaling_group.cluster.id}" +} + +output "efs_id" { + value = "${aws_efs_file_system.shared.id}" +} + +output "instances_max" { + value = "${var.instances_max}" +} + +output "instances_min" { + value = "${var.instances_min}" +} + +output "launch_topic" { + value = "${aws_sns_topic.instance_launch.id}" +} + +output "terminate_topic" { + value = "${aws_sns_topic.instance_terminate.id}" +} diff --git a/terraform/modules/coreos_asg/variables.tf b/terraform/modules/coreos_asg/variables.tf new file mode 100644 index 0000000..9f84560 --- /dev/null +++ b/terraform/modules/coreos_asg/variables.tf @@ -0,0 +1,23 @@ +variable "name" {} + +variable "vpc_id" {} + +variable "key_name" {} + +variable "subnets" { type = "list" } + +variable "instances_min" { default = 1 } + +variable "instances_max" { default = 1 } + +variable "instances_desired" { default = 1 } + +variable "terminate_delay" { default = 600 } + +variable "launch_delay" { default = 600 } + +variable "instance_type" { default = "t2.nano" } + +variable "cloud_config" {} + +variable "kms_key_id" {} diff --git a/terraform/modules/route53_balancer/lambda/asg_ip_attach/index.py b/terraform/modules/route53_balancer/lambda/asg_ip_attach/index.py new file mode 100644 index 0000000..b22e161 --- /dev/null +++ b/terraform/modules/route53_balancer/lambda/asg_ip_attach/index.py @@ -0,0 +1,30 @@ +import os, boto3, json, re + +def handler(event, context): + ec2 = boto3.resource('ec2') + route53 = boto3.client('route53') + message = json.loads(event['Records'][0]['Sns']['Message']) + instance_id = message['EC2InstanceId'] + instance = ec2.Instance(instance_id) + ip = instance.public_ip_address + domain = os.environ['domain'] + + print("Adding record: {0} -> {1}".format(domain, ip)) + response = route53.change_resource_record_sets( + HostedZoneId = os.environ['hosted_zone'], + ChangeBatch = { 'Changes': [{ + 'Action': 'CREATE', + 'ResourceRecordSet': { + 'Name': "{0}.".format(domain), + 'Type': 'A', + 'SetIdentifier': instance_id, + 'Weight': 10, + 'ResourceRecords': [{ 'Value': ip }], + 'TTL': 600 + } + }]} + ) + + print({'status':response}) + + return {'status':response['ChangeInfo']['Status']} diff --git a/terraform/modules/route53_balancer/lambda/asg_ip_detach/index.py b/terraform/modules/route53_balancer/lambda/asg_ip_detach/index.py new file mode 100644 index 0000000..4ce2d8e --- /dev/null +++ b/terraform/modules/route53_balancer/lambda/asg_ip_detach/index.py @@ -0,0 +1,30 @@ +import os, boto3, json, re + +def handler(event, context): + ec2 = boto3.resource('ec2') + route53 = boto3.client('route53') + message = json.loads(event['Records'][0]['Sns']['Message']) + instance_id = message['EC2InstanceId'] + instance = ec2.Instance(instance_id) + ip = instance.public_ip_address + domain = os.environ['domain'] + + print("Removing record: {0} -> {1}".format(domain, ip)) + response = route53.change_resource_record_sets( + HostedZoneId = os.environ['hosted_zone'], + ChangeBatch = { 'Changes': [{ + 'Action': 'DELETE', + 'ResourceRecordSet': { + 'Name': "{0}.".format(domain), + 'Type': 'A', + 'SetIdentifier': instance_id, + 'Weight': 10, + 'ResourceRecords': [{ 'Value': ip }], + 'TTL': 600 + } + }]} + ) + + print({'status':response}) + + return {'status':response['ChangeInfo']['Status']} diff --git a/terraform/modules/route53_balancer/main.tf b/terraform/modules/route53_balancer/main.tf new file mode 100644 index 0000000..d9b4163 --- /dev/null +++ b/terraform/modules/route53_balancer/main.tf @@ -0,0 +1,141 @@ +data "aws_region" "selected" { + current = true +} + +data "aws_caller_identity" "current" {} + +data "aws_route53_zone" "selected" { + name = "${replace(var.domain, "/^.*\\.([^\\.]+)\\.([^\\.]+)$/", "$1.$2.")}" +} + +resource "aws_lambda_permission" "sns_instance_launch" { + statement_id = "AllowExecutionFromSNS" + action = "lambda:InvokeFunction" + function_name = "${aws_lambda_function.asg_ip_attach.function_name}" + principal = "sns.amazonaws.com" + source_arn = "${var.launch_topic}" +} + +resource "aws_lambda_permission" "sns_instance_terminate" { + statement_id = "AllowExecutionFromSNS" + action = "lambda:InvokeFunction" + function_name = "${aws_lambda_function.asg_ip_detach.function_name}" + principal = "sns.amazonaws.com" + source_arn = "${var.terminate_topic}" +} + +resource "aws_sns_topic_subscription" "asg_ip_attach" { + topic_arn = "${var.launch_topic}" + protocol = "lambda" + endpoint = "${aws_lambda_function.asg_ip_attach.arn}" +} + +resource "aws_sns_topic_subscription" "asg_ip_detach" { + topic_arn = "${var.terminate_topic}" + protocol = "lambda" + endpoint = "${aws_lambda_function.asg_ip_detach.arn}" +} + +resource "aws_lambda_function" "asg_ip_attach" { + filename = "${data.archive_file.asg_ip_attach.output_path}" + source_code_hash = "${data.archive_file.asg_ip_attach.output_base64sha256}" + role = "${aws_iam_role.asg_ip_manage.arn}" + timeout = "10" + function_name = "${var.name}-asg_ip_attach" + handler = "index.handler" + runtime = "python2.7" + environment { + variables = { + domain = "${var.domain}", + hosted_zone = "${data.aws_route53_zone.selected.id}", + asg = "${var.asg}", + region = "${data.aws_region.selected.id}" + } + } +} + +resource "aws_lambda_function" "asg_ip_detach" { + filename = "${data.archive_file.asg_ip_detach.output_path}" + source_code_hash = "${data.archive_file.asg_ip_detach.output_base64sha256}" + role = "${aws_iam_role.asg_ip_manage.arn}" + timeout = "10" + function_name = "${var.name}-asg_ip_detach" + handler = "index.handler" + runtime = "python2.7" + environment { + variables = { + domain = "${var.domain}", + hosted_zone = "${data.aws_route53_zone.selected.id}", + asg = "${var.asg}", + region = "${data.aws_region.selected.id}" + } + } +} + +data "archive_file" "asg_ip_attach" { + type = "zip" + source_dir = "${path.module}/lambda/asg_ip_attach/" + output_path = "${path.module}/.terraform/archive/lambda_asg_ip_attach.zip" +} + +data "archive_file" "asg_ip_detach" { + type = "zip" + source_dir = "${path.module}/lambda/asg_ip_detach/" + output_path = "${path.module}/.terraform/archive/lambda_asg_ip_detach.zip" +} + +resource "aws_iam_role_policy" "asg_ip_manage" { + name = "${var.name}-lambda-asg-ip-manage" + role = "${aws_iam_role.asg_ip_manage.id}" + policy = "${data.aws_iam_policy_document.asg_ip_manage_role_policy.json}" +} +data "aws_iam_policy_document" "asg_ip_manage_role_policy" { + statement { + effect = "Allow" + actions = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + resources = ["arn:aws:logs:${data.aws_region.selected.id}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/*"] + } + statement { + effect = "Allow" + actions = [ + "ec2:DescribeInstances", + ] + resources = ["*"] + } + statement { + effect = "Allow" + actions = [ + "route53:ChangeResourceRecordSets" + ] + resources = ["arn:aws:route53:::hostedzone/${data.aws_route53_zone.selected.id}"] + } + statement { + effect = "Allow" + actions = [ + "route53:GetChange", + ] + resources = ["arn:aws:route53:::change/*"] + } +} + +resource "aws_iam_role" "asg_ip_manage" { + name = "${var.name}-lambda-asg-ip-manage" + assume_role_policy = "${data.aws_iam_policy_document.asg_ip_manage_role.json}" +} + +data "aws_iam_policy_document" "asg_ip_manage_role" { + statement { + sid = "" + effect = "Allow" + principals { + type = "Service" + identifiers = ["lambda.amazonaws.com"] + } + actions = ["sts:AssumeRole"] + } +} + diff --git a/terraform/modules/route53_balancer/variables.tf b/terraform/modules/route53_balancer/variables.tf new file mode 100644 index 0000000..d9ff4d4 --- /dev/null +++ b/terraform/modules/route53_balancer/variables.tf @@ -0,0 +1,8 @@ +variable "domain" {} +variable "name" {} +variable "asg" {} +variable "health_type" { default = "HTTP" } +variable "health_port" { default = "443" } +variable "health_path" { default = "/" } +variable "launch_topic" { default = "" } +variable "terminate_topic" { default = "" } diff --git a/terraform/modules/security_group/main.tf b/terraform/modules/security_group/main.tf new file mode 100644 index 0000000..2c847f0 --- /dev/null +++ b/terraform/modules/security_group/main.tf @@ -0,0 +1,98 @@ +resource "aws_security_group" "sg" { + name_prefix = "${var.name}-${var.vpc_id}-" + description = " For ${var.name} in VPC ${var.vpc_id}" + vpc_id = "${var.vpc_id}" + tags { + Name = "${var.name}-${var.vpc_id}" + } +} + +resource "aws_security_group_rule" "all_internal_ingress" { + count = "${var.all_internal}" + security_group_id = "${aws_security_group.sg.id}" + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "tcp" + self = true +} + +resource "aws_security_group_rule" "all_internal_egress" { + count = "${var.all_internal}" + security_group_id = "${aws_security_group.sg.id}" + type = "egress" + from_port = 0 + to_port = 65535 + protocol = "tcp" + self = true +} + +resource "aws_security_group_rule" "all_inbound_ssh" { + count = "${var.all_inbound_ssh}" + security_group_id = "${aws_security_group.sg.id}" + type = "ingress" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "all_inbound_http" { + count = "${var.all_inbound_http}" + security_group_id = "${aws_security_group.sg.id}" + type = "ingress" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "all_inbound_https" { + count = "${var.all_inbound_https}" + security_group_id = "${aws_security_group.sg.id}" + type = "ingress" + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "all_outbound" { + count = "${var.all_outbound}" + security_group_id = "${aws_security_group.sg.id}" + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "all_inbound" { + count = "${var.all_inbound}" + security_group_id = "${aws_security_group.sg.id}" + type = "ingress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "group_inbound_nfs" { + count = "${var.group_inbound_nfs}" + security_group_id = "${aws_security_group.sg.id}" + type = "ingress" + from_port = 2049 + to_port = 2049 + protocol = "tcp" + source_security_group_id = "${var.group_inbound_nfs_id}" +} + +resource "aws_security_group_rule" "group_outbound_nfs" { + count = "${var.group_outbound_nfs}" + security_group_id = "${aws_security_group.sg.id}" + type = "egress" + from_port = 2049 + to_port = 2049 + protocol = "tcp" + source_security_group_id = "${var.group_outbound_nfs_id}" +} diff --git a/terraform/modules/security_group/outputs.tf b/terraform/modules/security_group/outputs.tf new file mode 100644 index 0000000..7f8de25 --- /dev/null +++ b/terraform/modules/security_group/outputs.tf @@ -0,0 +1,3 @@ +output "id" { + value = "${aws_security_group.sg.id}" +} diff --git a/terraform/modules/security_group/variables.tf b/terraform/modules/security_group/variables.tf new file mode 100644 index 0000000..c4b7197 --- /dev/null +++ b/terraform/modules/security_group/variables.tf @@ -0,0 +1,18 @@ +variable "name" { + description = "Unique name of group" +} + +variable "vpc_id" { + description = "ID of VPC to create security groups in" +} + +variable "all_internal" { default = false } +variable "all_inbound" { default = false } +variable "all_outbound" { default = false } +variable "all_inbound_ssh" { default = false } +variable "all_inbound_http" { default = false } +variable "all_inbound_https" { default = false } +variable "group_inbound_nfs" { default = false } +variable "group_inbound_nfs_id" { default = "" } +variable "group_outbound_nfs" { default = false } +variable "group_outbound_nfs_id" { default = "" }