From 378c1d9dadf656384492e0726887eb1738630335 Mon Sep 17 00:00:00 2001 From: joshuamkite Date: Tue, 4 Aug 2020 15:03:21 +0100 Subject: [PATCH] bugfixes, default container version and documentation updates (#38) --- CONTRIBUTING | 10 +- README.md | 142 ++++++++++++++-------- changelog.md | 8 ++ user_data/docker_setup.tpl | 6 +- user_data/iam-authorized-keys-command.tpl | 1 + variables.tf | 4 +- 6 files changed, 114 insertions(+), 57 deletions(-) diff --git a/CONTRIBUTING b/CONTRIBUTING index 1473b4a..d63055e 100644 --- a/CONTRIBUTING +++ b/CONTRIBUTING @@ -1,5 +1,13 @@ # Contributing to this module -I am always happy to consider contributions to the code offered here. Please feel free to raise issues or pull requests. All proposed code changes must be tested before submission! +I am always happy to consider contributions to the code offered here. Please feel free to raise issues or pull requests. If a feature was useful enough for you to add then it may be useful to others! + +I generally try to avoid breaking changes and changes to default behaviour except e.g. where versions have become deprecated + +All proposed code changes must be tested before submission! I don't have a formal test suite so I'm afraid it is a case of testing changes manually with deployment, log in successfully, etc. + +## Current 'missing features' that would be especially welcomed: + +* Support for Debian Buster 10.x and Ubuntu Focal 20.04 hosts Thanks \ No newline at end of file diff --git a/README.md b/README.md index 77ce6db..e737e56 100755 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ This Terraform deploys a stateless containerised sshd bastion service on AWS with IAM based authentication: =================================== -Updated to Terraform 0.12/HCL2. **This is a Breaking change** + This module requires Terraform 0.12/HCL2. -**For Terraform 0.11. Pin module version to ~> v4.0** +**Terraform 0.11.x was *previously* supported with pinning module version to ~> v4.0** **N.B. If you are using a newer version of this module when you have an older version deployed, please review the changelog!** @@ -11,6 +11,8 @@ Updated to Terraform 0.12/HCL2. **This is a Breaking change** This plan provides socket-activated sshd-containers with one container instantiated per connection and destroyed on connection termination or else after 12 hours- to deter things like reverse tunnels etc. The host assumes an IAM role, inherited by the containers, allowing it to query IAM users and request their ssh public keys lodged with AWS. +**It is essential to limit incoming service traffic to whitelisted ports.** If you do not then internet background noise will exhaust the host resources and/ or lead to rate limiting from amazon on the IAM identity calls- resulting in denial of service. + **It is possible to replace the components in userdata and the base AMI with components of your own choosing. The following describes deployment with all sections as provided by module defaults.** The actual call for public keys is made with a [GO binary](https://github.com/Fullscreen/iam-authorized-keys-command), which is built during host instance intial launch and made available via shared volume in the docker image. In use the Docker container queries AWS for users with ssh keys at runtime, creates local linux user accounts for them and handles their login. The users who may access the bastion service may be restricted to membership of a defined AWS IAM group which is not set up or managed by this plan. When the connection is closed the container exits. This means that users log in _as themselves_ and manage their own ssh keys using the AWS web console or CLI. For any given session they will arrive in a vanilla Ubuntu container with passwordless sudo and can install whatever applications and frameworks might be required for that session. Because the IAM identity checking and user account population is done at container run time and the containers are called on demand, there is no delay between creating an account with a public ssh key on AWS and being able to access the bastion. If users have more than one ssh public key then their account will be set up so that any of them may be used- AWS allows up to 5 keys per user. Aside from the resources provided by AWS and remote public repositories this plan is entirely self contained. There is no reliance on registries, build chains etc. @@ -89,7 +91,30 @@ It is considered normal to see very highly incremented counters if the load blan **It is essential to limit incoming service traffic to whitelisted ports.** If you do not then internet background noise will exhaust the host resources and/ or lead to rate limiting from amazon on the IAM identity calls- resulting in denial of service. -The host is set to run the latest patch release at deployment of Debian Stretch - unless you specify a custom AMI. Debian was chosen because the socket activation requires systemd but Ubuntu 16.04 did not automatically set up DHCP for additional elastic network interfaces (see version 1 series). **The login username is 'admin'**. The host sshd is available on port 2222 and uses standard ec2 ssh keying. If you do not whitelist any access to this port directly from the outside world (plan default) then it may be convenient to access from a container during development, e.g. with +The host is set to run the latest patch release at deployment of Debian Stretch - unless you specify a custom AMI. This Terraform has also been tested working with Ubuntu 18.04 'latest' host, e.g.: + +## For Ubuntu 18.04 'latest' host + +```bash +data "aws_ami" "ubuntu" { + most_recent = true + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"] + } + filter { + name = "virtualization-type" + values = ["hvm"] + } + owners = ["099720109477"] # Canonical +} + +custom_ami_id = data.aws_ami.ubuntu.id +``` + +Debian was chosen originally because the socket activation requires systemd but Ubuntu 16.04 did not automatically set up DHCP for additional elastic network interfaces (see version 1 series). Both Debian 10.x 'Buster' and Ubuntu 20.04 'Focal' have breaking changes around repository/package naming and Golang behaviour - look for support for these in a future release - contributions very welcome! + +The host sshd is available on port 2222 and uses standard ec2 ssh keying. **The default login username for Debian AMI's is 'admin'**. If you do not whitelist any access to this port directly from the outside world (plan default) then it may be convenient to access from a container during development, e.g. with sudo apt install -y curl; ssh -p2222 admin@`curl -s http://169.254.169.254/latest/meta-data/local-ipv4` @@ -220,59 +245,74 @@ The DNS entry (if created) for the service is also displayed as an output of the These have been generated with [terraform-docs](https://github.com/segmentio/terraform-docs) +## Requirements + +| Name | Version | +|------|---------| +| terraform | >= 0.12 | + +## Providers + +| Name | Version | +|------|---------| +| aws | n/a | +| null | n/a | +| template | n/a | + ## Inputs | Name | Description | Type | Default | Required | -|------|-------------|:----:|:-----:|:-----:| -| asg_desired | Desired numbers of bastion-service hosts in ASG | string | `1` | no | -| asg_max | Max numbers of bastion-service hosts in ASG | string | `2` | no | -| asg_min | Min numbers of bastion-service hosts in ASG | string | `1` | no | -| assume_role_arn | arn for role to assume in separate identity account if used | string | `` | no | -| aws_profile | | string | - | yes | -| aws_region | | string | - | yes | -| bastion_allowed_iam_group | Name IAM group, members of this group will be able to ssh into bastion instances if they have provided ssh key in their profile | string | `` | no | -| bastion_host_name | The hostname to give to the bastion instance | string | `` | no | -| bastion_instance_type | The virtual hardware to be used for the bastion service host | string | `t2.micro` | no | -| bastion_service_host_key_name | AWS ssh key *.pem to be used for ssh access to the bastion service host | string | `` | no | -| bastion_vpc_name | define the last part of the hostname, by default this is the vpc ID with magic default value of 'vpc_id' but you can pass a custom string, or an empty value to omit this | string | `vpc_id` | no | -| cidr_blocks_whitelist_host | range(s) of incoming IP addresses to whitelist for the HOST | list | `` | no | -| cidr_blocks_whitelist_service | range(s) of incoming IP addresses to whitelist for the SERVICE | list | `` | no | -| container_ubuntu_version | ubuntu version to use for service container. Tested with 16.04 and 18.04 | string | `16.04` | no | -| custom_ami_id | id for custom ami if used | string | `` | no | -| custom_authorized_keys_command | any value excludes default Go binary iam-authorized-keys built from source from userdata | string | `` | no | -| custom_docker_setup | any value excludes default docker installation and container build from userdata | string | `` | no | -| custom_ssh_populate | any value excludes default ssh_populate script used on container launch from userdata | string | `` | no | -| custom_systemd | any value excludes default systemd and hostname change from userdata | string | `` | no | -| dns_domain | The domain used for Route53 records | string | `` | no | -| environment_name | the name of the environment that we are deploying to, used in tagging. Overwritten if var.service_name and var.bastion_host_name values are changed | `staging` | no | -| extra_user_data_content | Extra user-data to add to the default built-in | string | `` | no | -| extra_user_data_content_type | What format is content in - eg 'text/cloud-config' or 'text/x-shellscript' | string | `text/x-shellscript` | no | -| extra_user_data_merge_type | Control how cloud-init merges user-data sections | string | `str(append)` | no | -| lb_healthcheck_port | TCP port to conduct lb target group healthchecks. Acceptable values are 22 or 2222 | string | `2222` | no | -| lb_healthy_threshold | Healthy threshold for lb target group | string | `2` | no | -| lb_interval | interval for lb target group health check | string | `30` | no | -| lb_is_internal | whether the lb will be internal | string | `false` | no | -| lb_unhealthy_threshold | Unhealthy threshold for lb target group | string | `2` | no | -| public_ip | Associate a public IP with the host instance when launching | string | `false` | no | -| route53_fqdn | If creating a public DNS entry with this module then you may override the default constructed DNS entry by supplying a fully qualified domain name here which will be used verbatim | string | `` | no | -| route53_zone_id | Route53 zoneId | string | `` | no | -| security_groups_additional | additional security group IDs to attach to host instance | list | `` | no | -| service_name | Unique name per vpc for associated resources- set to some non-default value for multiple deployments per vpc | string | `bastion-service` | no | -| subnets_asg | list of subnets for autoscaling group - availability zones must match subnets_lb | list | `` | no | -| subnets_lb | list of subnets for load balancer - availability zones must match subnets_asg | list | `` | no | -| tags | AWS tags that should be associated with created resources | map | `` | no | -| vpc | ID for Virtual Private Cloud to apply security policy and deploy stack to | string | - | yes | +|------|-------------|------|---------|:--------:| +| asg\_desired | Desired numbers of bastion-service hosts in ASG | `string` | `"1"` | no | +| asg\_max | Max numbers of bastion-service hosts in ASG | `string` | `"2"` | no | +| asg\_min | Min numbers of bastion-service hosts in ASG | `string` | `"1"` | no | +| assume\_role\_arn | arn for role to assume in separate identity account if used | `string` | `""` | no | +| aws\_profile | n/a | `any` | n/a | yes | +| aws\_region | n/a | `any` | n/a | yes | +| bastion\_allowed\_iam\_group | Name IAM group, members of this group will be able to ssh into bastion instances if they have provided ssh key in their profile | `string` | `""` | no | +| bastion\_host\_name | The hostname to give to the bastion instance | `string` | `""` | no | +| bastion\_instance\_type | The virtual hardware to be used for the bastion service host | `string` | `"t2.micro"` | no | +| bastion\_service\_host\_key\_name | AWS ssh key \*.pem to be used for ssh access to the bastion service host | `string` | `""` | no | +| bastion\_vpc\_name | define the last part of the hostname, by default this is the vpc ID with magic default value of 'vpc\_id' but you can pass a custom string, or an empty value to omit this | `string` | `"vpc_id"` | no | +| cidr\_blocks\_whitelist\_host | range(s) of incoming IP addresses to whitelist for the HOST | `list(string)` | `[]` | no | +| cidr\_blocks\_whitelist\_service | range(s) of incoming IP addresses to whitelist for the SERVICE | `list(string)` | `[]` | no | +| container\_ubuntu\_version | ubuntu version to use for service container. Tested with 16.04; 18.04; 20.04 | `string` | `"20.04"` | no | +| custom\_ami\_id | id for custom ami if used | `string` | `""` | no | +| custom\_authorized\_keys\_command | any value excludes default Go binary iam-authorized-keys built from source from userdata | `string` | `""` | no | +| custom\_docker\_setup | any value excludes default docker installation and container build from userdata | `string` | `""` | no | +| custom\_ssh\_populate | any value excludes default ssh\_populate script used on container launch from userdata | `string` | `""` | no | +| custom\_systemd | any value excludes default systemd and hostname change from userdata | `string` | `""` | no | +| dns\_domain | The domain used for Route53 records | `string` | `""` | no | +| environment\_name | the name of the environment that we are deploying to, used in tagging. Overwritten if var.service\_name and var.bastion\_host\_name values are changed | `string` | `"staging"` | no | +| extra\_user\_data\_content | Extra user-data to add to the default built-in | `string` | `""` | no | +| extra\_user\_data\_content\_type | What format is content in - eg 'text/cloud-config' or 'text/x-shellscript' | `string` | `"text/x-shellscript"` | no | +| extra\_user\_data\_merge\_type | Control how cloud-init merges user-data sections | `string` | `"str(append)"` | no | +| lb\_healthcheck\_port | TCP port to conduct lb target group healthchecks. Acceptable values are 22 or 2222 | `string` | `"2222"` | no | +| lb\_healthy\_threshold | Healthy threshold for lb target group | `string` | `"2"` | no | +| lb\_interval | interval for lb target group health check | `string` | `"30"` | no | +| lb\_is\_internal | whether the lb will be internal | `string` | `false` | no | +| lb\_unhealthy\_threshold | Unhealthy threshold for lb target group | `string` | `"2"` | no | +| public\_ip | Associate a public IP with the host instance when launching | `bool` | `false` | no | +| route53\_fqdn | If creating a public DNS entry with this module then you may override the default constructed DNS entry by supplying a fully qualified domain name here which will be used verbatim | `string` | `""` | no | +| route53\_zone\_id | Route53 zoneId | `string` | `""` | no | +| security\_groups\_additional | additional security group IDs to attach to host instance | `list(string)` | `[]` | no | +| service\_name | Unique name per vpc for associated resources- set to some non-default value for multiple deployments per vpc | `string` | `"bastion-service"` | no | +| subnets\_asg | list of subnets for autoscaling group - availability zones must match subnets\_lb | `list(string)` | `[]` | no | +| subnets\_lb | list of subnets for load balancer - availability zones must match subnets\_asg | `list(string)` | `[]` | no | +| tags | AWS tags that should be associated with created resources | `map(string)` | `{}` | no | +| vpc | ID for Virtual Private Cloud to apply security policy and deploy stack to | `any` | n/a | yes | ## Outputs | Name | Description | |------|-------------| -| bastion_service_assume_role_name | role created for service host asg - if created with assume role | -| bastion_service_role_name | role created for service host asg - if created without assume role | -| bastion_sg_id | Security Group id of the bastion host | -| lb_arn | aws load balancer arn | -| lb_dns_name | aws load balancer dns | -| lb_zone_id | | -| policy_example_for_parent_account_empty_if_not_used | You must apply an IAM policy with trust relationship identical or compatible with this in your other AWS account for IAM lookups to function there with STS:AssumeRole and allow users to login | -| service_dns_entry | dns-registered url for service and host | -| target_group_arn | aws load balancer target group arn | \ No newline at end of file +| bastion\_service\_assume\_role\_name | role created for service host asg - if created with assume role | +| bastion\_service\_role\_name | role created for service host asg - if created without assume role | +| bastion\_sg\_id | Security Group id of the bastion host | +| lb\_arn | aws load balancer arn | +| lb\_dns\_name | aws load balancer dns | +| lb\_zone\_id | n/a | +| policy\_example\_for\_parent\_account\_empty\_if\_not\_used | You must apply an IAM policy with trust relationship identical or compatible with this in your other AWS account for IAM lookups to function there with STS:AssumeRole and allow users to login | +| service\_dns\_entry | dns-registered url for service and host | +| target\_group\_arn | aws load balancer target group arn | + diff --git a/changelog.md b/changelog.md index 7350ac3..a376dd6 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ **N.B.** +# 5.1 + +**Bugfix:** Change all `apt` => `apt-get`; Prefix `apt-get install` with `DEBIAN_FRONTEND=noninteractive` so that prompts are automatically accepted during cloud-init run (Thanks @DavidBennettUK) + +**Change:** Default ubuntu container version updated `16.04` => `20.04` + +**Feature:** Update README.md + # 5.0 **Change:** Updated to Terraform 0.12/HCL2. **This is a Breaking change** diff --git a/user_data/docker_setup.tpl b/user_data/docker_setup.tpl index 5671db6..cd7d7d9 100644 --- a/user_data/docker_setup.tpl +++ b/user_data/docker_setup.tpl @@ -1,17 +1,17 @@ #!/bin/bash #debian specific set up for docker https://docs.docker.com/install/linux/docker-ce/debian/#install-using-the-repository -apt install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common +DEBIAN_FRONTEND=noninteractive apt-get install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | apt-key add - add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") $(lsb_release -cs) stable" apt update -apt install -y docker-ce +DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce systemctl start docker mkdir -p /opt/sshd_worker #Write out Dockerfile cat << EOF > /opt/sshd_worker/Dockerfile FROM ubuntu:${container_ubuntu_version} -RUN apt-get update && apt-get install -y openssh-server sudo awscli && echo '\033[1;31mI am a one-time Ubuntu container with passwordless sudo. \033[1;37;41mI will terminate after 12 hours or else on exit\033[0m' > /etc/motd && mkdir /var/run/sshd +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-server sudo awscli && echo '\033[1;31mI am a one-time Ubuntu container with passwordless sudo. \033[1;37;41mI will terminate after 12 hours or else on exit\033[0m' > /etc/motd && mkdir /var/run/sshd EXPOSE 22 CMD ["/opt/ssh_populate.sh"] diff --git a/user_data/iam-authorized-keys-command.tpl b/user_data/iam-authorized-keys-command.tpl index 5b1518e..0a05dcb 100644 --- a/user_data/iam-authorized-keys-command.tpl +++ b/user_data/iam-authorized-keys-command.tpl @@ -3,6 +3,7 @@ mkdir -p /opt/golang/src/iam-authorized-keys-command/ cat << EOF > /opt/golang/src/iam-authorized-keys-command/main.go ${authorized_command_code} EOF +DEBIAN_FRONTEND=noninteractive sudo apt-get install -y golang export GOPATH=/opt/golang diff --git a/variables.tf b/variables.tf index 7aa29b9..f72ba25 100755 --- a/variables.tf +++ b/variables.tf @@ -141,8 +141,8 @@ variable "bastion_vpc_name" { } variable "container_ubuntu_version" { - description = "ubuntu version to use for service container. Tested with 16.04 and 18.04" - default = "16.04" + description = "ubuntu version to use for service container. Tested with 16.04; 18.04; 20.04" + default = "20.04" } variable "extra_user_data_content" {