├── .github ├── CODEOWNERS ├── release-drafter.yml └── workflows │ ├── backups.yml │ ├── linting.yml │ ├── release-drafter.yml │ └── terraform-docs.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── client ├── ssh_terminal └── ssh_tunnel ├── cloud_init.init ├── data.tf ├── examples └── simple │ ├── README.md │ └── main.tf ├── iam.tf ├── kms.tf ├── main.tf ├── outputs.tf ├── variables.tf └── versions.tf /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @Flaconi/devops 2 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Release Drafter: https://github.com/toolmantim/release-drafter 2 | name-template: 'v$NEXT_MINOR_VERSION 🌈' 3 | tag-template: 'v$NEXT_MINOR_VERSION' 4 | categories: 5 | - title: '🚀 Features' 6 | labels: 7 | - feature 8 | - enhancement 9 | - title: '🐛 Bug Fixes' 10 | labels: 11 | - fix 12 | - bugfix 13 | - bug 14 | - title: '🧰 Maintenance' 15 | labels: 16 | - chore 17 | - dependencies 18 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 19 | branches: 20 | - master 21 | template: | 22 | ## What's Changed 23 | 24 | $CHANGES 25 | -------------------------------------------------------------------------------- /.github/workflows/backups.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Backup Repository 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | backup: 11 | uses: Flaconi/github-reusable-workflow/.github/workflows/backups.yml@v1 12 | with: 13 | enabled: True 14 | region: eu-central-1 15 | secrets: 16 | iam_role_arn: ${{ secrets.BACKUP_REPO_IAM_ROLE }} 17 | bucket_name: ${{ secrets.BACKUP_REPO_BUCKET }} 18 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Lints all generic and json files in the whole git repository 5 | ### 6 | 7 | name: linting 8 | on: 9 | pull_request: 10 | push: 11 | branches: 12 | - master 13 | tags: 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@master 21 | 22 | - name: Terraform lint 23 | uses: actionshub/terraform-lint@main 24 | 25 | - name: Files lint 26 | run: | 27 | make "lint-files" 28 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - master 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | update_release_draft: 14 | permissions: 15 | contents: write 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: release-drafter/release-drafter@v5 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /.github/workflows/terraform-docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Checks terraform-docs generation 5 | ### 6 | 7 | name: terraform-docs 8 | on: [pull_request] 9 | 10 | jobs: 11 | docs: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@master 16 | 17 | - name: terraform-docs 18 | run: | 19 | make terraform-docs 20 | git diff --quiet || { echo "Build Changes"; git diff; git status; false; } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # .tfvars files 9 | *.tfvars 10 | 11 | .terraform.lock.hcl 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Flaconi GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifneq (,) 2 | .error This Makefile requires GNU Make. 3 | endif 4 | 5 | # ------------------------------------------------------------------------------------------------- 6 | # Default configuration 7 | # ------------------------------------------------------------------------------------------------- 8 | .PHONY: help lint lint-files terraform-docs terraform-fmt _pull-tf _pull-tfdocs 9 | CURRENT_DIR = $(PWD) 10 | 11 | 12 | # ------------------------------------------------------------------------------------------------- 13 | # Docker image versions 14 | # ------------------------------------------------------------------------------------------------- 15 | TF_VERSION = 1.3.9 16 | FL_VERSION = latest-0.8 17 | 18 | FL_IGNORE_PATHS = .git/,.github/,.idea/ 19 | 20 | # ------------------------------------------------------------------------------------------------- 21 | # Terraform-docs configuration 22 | # ------------------------------------------------------------------------------------------------- 23 | TFDOCS_VERSION = 0.16.0-0.34 24 | 25 | # Adjust your delimiter here or overwrite via make arguments 26 | TFDOCS_DELIM_START = 27 | TFDOCS_DELIM_CLOSE = 28 | 29 | # ------------------------------------------------------------------------------------------------- 30 | # Meta Targets 31 | # ------------------------------------------------------------------------------------------------- 32 | 33 | help: 34 | @echo 35 | @echo "Meta targets" 36 | @echo "--------------------------------------------------------------------------------" 37 | @echo " help Show this help screen" 38 | @echo 39 | @echo "Read-only targets" 40 | @echo "--------------------------------------------------------------------------------" 41 | @echo " lint Lint basics as well as *.tf and *.tfvars files" 42 | @echo " lint-files Lint basics" 43 | @echo 44 | @echo "Writing targets" 45 | @echo "--------------------------------------------------------------------------------" 46 | @echo " terraform-docs Run terraform-docs against all README.md" 47 | @echo " terraform-fmt Run terraform-fmt against *.tf and *.tfvars files" 48 | 49 | 50 | # ------------------------------------------------------------------------------------------------- 51 | # Read-only Targets 52 | # ------------------------------------------------------------------------------------------------- 53 | 54 | lint: 55 | @$(MAKE) --no-print-directory terraform-fmt _WRITE=false 56 | @$(MAKE) --no-print-directory lint-files 57 | 58 | lint-files: 59 | @echo "################################################################################" 60 | @echo "# file-lint" 61 | @echo "################################################################################" 62 | @docker run --rm -v $(PWD):/data cytopia/file-lint:$(FL_VERSION) file-cr --text --ignore '$(FL_IGNORE_PATHS)' --path . 63 | @docker run --rm -v $(PWD):/data cytopia/file-lint:$(FL_VERSION) file-crlf --text --ignore '$(FL_IGNORE_PATHS)' --path . 64 | @docker run --rm -v $(PWD):/data cytopia/file-lint:$(FL_VERSION) file-trailing-single-newline --text --ignore '$(FL_IGNORE_PATHS)' --path . 65 | @docker run --rm -v $(PWD):/data cytopia/file-lint:$(FL_VERSION) file-trailing-space --text --ignore '$(FL_IGNORE_PATHS)' --path . 66 | @docker run --rm -v $(PWD):/data cytopia/file-lint:$(FL_VERSION) file-utf8 --text --ignore '$(FL_IGNORE_PATHS)' --path . 67 | @docker run --rm -v $(PWD):/data cytopia/file-lint:$(FL_VERSION) file-utf8-bom --text --ignore '$(FL_IGNORE_PATHS)' --path . 68 | 69 | 70 | # ------------------------------------------------------------------------------------------------- 71 | # Writing Targets 72 | # ------------------------------------------------------------------------------------------------- 73 | 74 | terraform-docs: _pull-tfdocs 75 | @echo "################################################################################" 76 | @echo "# Terraform-docs generate" 77 | @echo "################################################################################" 78 | @echo 79 | @if docker run --rm $$(tty -s && echo "-it" || echo) \ 80 | -v "$(CURRENT_DIR):/data" \ 81 | -e TFDOCS_DELIM_START='$(TFDOCS_DELIM_START)' \ 82 | -e TFDOCS_DELIM_CLOSE='$(TFDOCS_DELIM_CLOSE)' \ 83 | cytopia/terraform-docs:$(TFDOCS_VERSION) \ 84 | terraform-docs-replace --sort-by required markdown README.md; then \ 85 | echo "OK"; \ 86 | else \ 87 | echo "Failed"; \ 88 | exit 1; \ 89 | fi; 90 | @echo 91 | 92 | terraform-fmt: _WRITE=true 93 | terraform-fmt: _pull-tf 94 | @# Lint all Terraform files 95 | @echo "################################################################################" 96 | @echo "# Terraform fmt" 97 | @echo "################################################################################" 98 | @echo 99 | @echo "------------------------------------------------------------" 100 | @echo "# *.tf files" 101 | @echo "------------------------------------------------------------" 102 | @if docker run $$(tty -s && echo "-it" || echo) --rm \ 103 | -v "$(PWD):/data" hashicorp/terraform:$(TF_VERSION) fmt \ 104 | $$(test "$(_WRITE)" = "false" && echo "-check" || echo "-write=true") \ 105 | -diff \ 106 | -list=true \ 107 | -recursive \ 108 | /data; then \ 109 | echo "OK"; \ 110 | else \ 111 | echo "Failed"; \ 112 | exit 1; \ 113 | fi; 114 | @echo 115 | @echo "------------------------------------------------------------" 116 | @echo "# *.tfvars files" 117 | @echo "------------------------------------------------------------" 118 | @if docker run $$(tty -s && echo "-it" || echo) --rm --entrypoint=/bin/sh \ 119 | -v "$(PWD):/data" hashicorp/terraform:$(TF_VERSION) \ 120 | -c "find . -not \( -path './*/.terragrunt-cache/*' -o -path './*/.terraform/*' \) \ 121 | -name '*.tfvars' -type f -print0 \ 122 | | xargs -0 -n1 terraform fmt \ 123 | $$(test '$(_WRITE)' = 'false' && echo '-check' || echo '-write=true') \ 124 | -diff \ 125 | -list=true"; then \ 126 | echo "OK"; \ 127 | else \ 128 | echo "Failed"; \ 129 | exit 1; \ 130 | fi; 131 | @echo 132 | 133 | 134 | # ------------------------------------------------------------------------------------------------- 135 | # Helper Targets 136 | # ------------------------------------------------------------------------------------------------- 137 | 138 | # Ensure to always have the latest Terraform version 139 | _pull-tf: 140 | docker pull hashicorp/terraform:$(TF_VERSION) 141 | 142 | _pull-tfdocs: 143 | docker pull cytopia/terraform-docs:$(TFDOCS_VERSION) 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Bastion SSM IAM 2 | 3 | [](https://github.com/Flaconi/terraform-aws-bastion-ssm-iam/actions/workflows/linting.yml) 4 | [](https://github.com/Flaconi/terraform-aws-bastion-ssm-iam/actions/workflows/terraform-docs.yml) 5 | [](https://github.com/Flaconi/terraform-aws-bastion-ssm-iam/releases) 6 | [](http://opensource.org/licenses/MIT) 7 | 8 | Terraform module which provides a Bastion for AWS utilizing 9 | * Autoscaling group of min/max 1 for resiliency 10 | * AWS SSM Session Manager, this allows users to start a Terminal Session or Tunnel to an instance without the need of public internet access 11 | * ec2-instance-connect, for the creation of temporary ssh keys on the instance 12 | 13 | __NOTE__ Important, this module managed the SSM Document SSM-SessionManagerRunShell, in some cases it already exists. To make sure Terraform is used to maintain this Document please execute: `aws ssm delete-document --name SSM-SessionManagerRunShell`. In case you do not want to overwrite SSM-SessionManagerRunShell, you can use the module directive `create_new_ssm_document` to create a different document name. This document needs to be refered to as follows: `SSM_DOCUMENT_NAME="SSM-SessionManagerRunShell-JKURx" ./ssh_terminal` 14 | 15 | __NOTE__ For this to work you need to install the session manager plugin for the AWSCLI, click (here)[https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html] for more information. 16 | 17 | ## Examples 18 | 19 | Check the [examples](examples) directory for installation. 20 | 21 | 22 | ## Client 23 | ### SSH Terminal 24 | The bash script `client/ssh_terminal` provides a simplified way to ssh to the IAM Bastion, it uses a recent `awscli`-client with ssm terminal support. 25 | 26 | 27 | ### SSH Tunnel 28 | The bash script `client/ssh_tunnel` creates an SSH tunnel using the BASTION, it uses a recent `awscli`-client with ssm terminal support and ec2-instance-connect for uploading the SSH Public key to AWS. Make sure the 29 | BASTION has access to the resources it needs access to by modifying the Security Group of the resouce. 30 | 31 | By default the public key file `$HOME/.ssh/id_rsa.pub` will be used for temporary access. The ENVIRONMENT variable `SSH_PUB_KEY_FILE` can be used to set a different public key, as of now AWS does not support ed25519 public keys. 32 | By default the ENVIRONMENT variable AWS_REGION will be used for the `awscli`-tool, if you are using awscli profiles, please provide the correct region by setting the `AWS_REGION`-variable. 33 | If `DEV_LOCAL_PORT` is specified, the ssh tunnel will be created with `DEV_LOCAL_PORT` as local port to connect to, if not a RANDOM port will be used. 34 | 35 | Example: 36 | ```bash 37 | ./ssh_tunnel private_subnet.isdfjsdf.eu-central-1.rds.amazonaws.com:3306 38 | ``` 39 | 40 | 41 | 42 | 43 | ## Requirements 44 | 45 | | Name | Version | 46 | |------|---------| 47 | | [terraform](#requirement\_terraform) | >= 1.3 | 48 | | [aws](#requirement\_aws) | >= 3 | 49 | | [random](#requirement\_random) | >= 3.1 | 50 | 51 | ## Providers 52 | 53 | | Name | Version | 54 | |------|---------| 55 | | [aws](#provider\_aws) | >= 3 | 56 | | [random](#provider\_random) | >= 3.1 | 57 | 58 | ## Modules 59 | 60 | No modules. 61 | 62 | ## Resources 63 | 64 | | Name | Type | 65 | |------|------| 66 | | [aws_autoscaling_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group) | resource | 67 | | [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | 68 | | [aws_iam_instance_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | 69 | | [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | 70 | | [aws_iam_role_policy.kms](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | 71 | | [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | 72 | | [aws_kms_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | 73 | | [aws_launch_template.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template) | resource | 74 | | [aws_security_group.allow_egress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | 75 | | [aws_ssm_document.session_manager_prefs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_document) | resource | 76 | | [random_string.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource | 77 | | [aws_ami.amazon_linux_2](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | 78 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | 79 | | [aws_iam_policy_document.kms_key_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 80 | | [aws_iam_policy_document.kms_key_policy_iam_profile](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 81 | | [aws_iam_policy_document.trust_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 82 | | [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | 83 | 84 | ## Inputs 85 | 86 | | Name | Description | Type | Default | Required | 87 | |------|-------------|------|---------|:--------:| 88 | | [subnet\_ids](#input\_subnet\_ids) | The subnets where the Bastion can reside in, they can be private | `list(string)` | n/a | yes | 89 | | [vpc\_id](#input\_vpc\_id) | The VPC-ID | `string` | n/a | yes | 90 | | [create\_new\_ssm\_document](#input\_create\_new\_ssm\_document) | This module can create a new SSM document for the SSH Terminal | `bool` | `false` | no | 91 | | [create\_security\_group](#input\_create\_security\_group) | This module can create a security group for the bastion instance by default | `bool` | `true` | no | 92 | | [image\_id](#input\_image\_id) | AMI to be used. If blank, latest amazon linux 2 will be used | `string` | `""` | no | 93 | | [instance\_type](#input\_instance\_type) | The instance type of the bastion | `string` | `"t3.nano"` | no | 94 | | [log\_retention](#input\_log\_retention) | The amount of days the logs need to be kept | `number` | `30` | no | 95 | | [name](#input\_name) | The name to be interpolated, defaults to bastion-ssm-iam | `string` | `"bastion-ssm-iam"` | no | 96 | | [security\_group\_ids](#input\_security\_group\_ids) | The security group ids which can be given to the bastion instance, defaults to empty | `list(string)` | `[]` | no | 97 | | [tags](#input\_tags) | Tags to be added to the launch configuration for the bastion host, additionally to name tag |
list(object({| `[]` | no | 98 | 99 | ## Outputs 100 | 101 | | Name | Description | 102 | |------|-------------| 103 | | [instance\_profile\_name](#output\_instance\_profile\_name) | The instance profile name of SSM | 104 | | [security\_group\_id](#output\_security\_group\_id) | The security group id of the bastion server | 105 | | [ssm\_document\_name](#output\_ssm\_document\_name) | The document name of SSM | 106 | 107 | 108 | 109 | ## License 110 | 111 | [MIT](LICENSE) 112 | 113 | Copyright (c) 2021 [Flaconi GmbH](https://github.com/Flaconi) 114 | -------------------------------------------------------------------------------- /client/ssh_terminal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | REGION="${AWS_REGION:-eu-central-1}" 6 | NAME="${NAME:-bastion-ssm-iam}" 7 | SSM_DOCUMENT_NAME="${SSM_DOCUMENT_NAME:-SSM-SessionManagerRunShell}" 8 | 9 | function print_error { 10 | read line file <<<$(caller) 11 | echo "An error occurred in line $line of file $file:" >&2 12 | sed "${line}q;d" "$file" >&2 13 | exit 1 14 | } 15 | 16 | if ! [ -x "$(command -v jq)" ]; then 17 | trap print_error 'The command jq is required' 18 | fi 19 | 20 | if ! [ -x "$(command -v aws)" ]; then 21 | trap print_error 'The command aws is required' 22 | fi 23 | 24 | if ! [ -x "$(command -v session-manager-plugin)" ]; then 25 | trap print_error 'The command session-manager-plugin is required' 26 | fi 27 | 28 | INSTANCE_ID=$(aws ec2 describe-instances \ 29 | --region "${REGION}" \ 30 | --filter "Name=tag:Name,Values=$NAME" \ 31 | --query "Reservations[].Instances[?State.Name == 'running'].InstanceId[]" \ 32 | --output text) 33 | 34 | if [ "${INSTANCE_ID}" == "" ];then 35 | trap print_error "No instance with ssm running could be found" 36 | fi 37 | 38 | exec aws ssm start-session --target "${INSTANCE_ID}" --region "${REGION}" --document-name "${SSM_DOCUMENT_NAME}" 39 | -------------------------------------------------------------------------------- /client/ssh_tunnel: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | SSH_PUB_KEY_FILE="${SSH_PUB_KEY_FILE:-$HOME/.ssh/id_rsa.pub}" 6 | REGION="${AWS_REGION:-eu-central-1}" 7 | NAME="${NAME:-bastion-ssm-iam}" 8 | RANDOM_PORT=$(( ( RANDOM % 60000 ) + 1024 )) 9 | DEV_LOCAL_PORT="${DEV_LOCAL_PORT:-$RANDOM_PORT}" 10 | FORWARD_TO=$1 11 | 12 | function print_error { 13 | read line file <<<$(caller) 14 | echo "An error occurred in line $line of file $file:" >&2 15 | sed "${line}q;d" "$file" >&2 16 | exit 1 17 | } 18 | 19 | if ! [ -x "$(command -v jq)" ]; then 20 | trap print_error 'The command jq is required' 21 | fi 22 | 23 | if ! [ -x "$(command -v aws)" ]; then 24 | trap print_error 'The command aws is required' 25 | fi 26 | 27 | if ! [ -x "$(command -v session-manager-plugin)" ]; then 28 | trap print_error 'The command session-manager-plugin is required' 29 | fi 30 | 31 | if [[ $# -eq 0 ]] ; then 32 | trap print_error "Please provide the hostname:port as argument, for example $0 host_inside_vpc:3306" 33 | fi 34 | 35 | if [ ! -f "$SSH_PUB_KEY_FILE" ] ; then 36 | trap print_error "The ssh public key does not exist" 37 | fi 38 | 39 | 40 | INSTANCE_ID=$(aws ec2 describe-instances \ 41 | --region "${REGION}" \ 42 | --filter "Name=tag:Name,Values=$NAME" \ 43 | --query "Reservations[].Instances[?State.Name == 'running'].InstanceId[]" \ 44 | --output text) 45 | 46 | AZ=$(aws ec2 describe-instances --region "${REGION}" --filter "Name=tag:Name,Values=$NAME" "Name=instance-state-code,Values=16" --output json| jq -r '.Reservations[].Instances[].Placement.AvailabilityZone') 47 | 48 | if [ "${INSTANCE_ID}" == "" ];then 49 | trap print_error "No instance with ssm running could be found" 50 | fi 51 | 52 | echo -n "Provisioning Bastion SSH Instance with temporary key .... " 53 | aws ec2-instance-connect send-ssh-public-key --region $REGION --instance-id "${INSTANCE_ID}" --availability-zone "${AZ}" --instance-os-user ec2-user --ssh-public-key file://$SSH_PUB_KEY_FILE 2>&1 > /dev/null || trap print_error "Failed" 54 | echo "Success!" 55 | 56 | echo "127.0.0.1:$DEV_LOCAL_PORT is forwarding to $FORWARD_TO" 57 | echo . 58 | echo "Press ctrl-c to exit." 59 | ssh -nNT -o "ProxyCommand=sh -c \"/usr/local/bin/aws ssm --region $REGION start-session --target %h --document-name AWS-StartSSHSession\"" "ec2-user@${INSTANCE_ID}" -L $DEV_LOCAL_PORT:$FORWARD_TO 60 | -------------------------------------------------------------------------------- /cloud_init.init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm 4 | sudo yum install -y ec2-instance-connect 5 | sudo systemctl enable amazon-ssm-agent 6 | sudo systemctl start amazon-ssm-agent 7 | -------------------------------------------------------------------------------- /data.tf: -------------------------------------------------------------------------------- 1 | data "aws_region" "current" {} 2 | data "aws_caller_identity" "current" {} 3 | 4 | data "aws_ami" "amazon_linux_2" { 5 | most_recent = true 6 | 7 | filter { 8 | name = "owner-alias" 9 | values = ["amazon"] 10 | } 11 | 12 | owners = ["amazon"] 13 | 14 | filter { 15 | name = "name" 16 | values = ["amzn2-ami-hvm*"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/simple/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | 4 | 5 | 6 | 7 | 8 | ## License 9 | 10 | [MIT](LICENSE) 11 | 12 | Copyright (c) 2019 [Flaconi GmbH](https://github.com/Flaconi) 13 | -------------------------------------------------------------------------------- /examples/simple/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-central-1" 3 | } 4 | 5 | module "vpc" { 6 | source = "terraform-aws-modules/vpc/aws" 7 | 8 | name = "simple-example" 9 | 10 | cidr = "10.0.0.0/16" 11 | 12 | azs = ["eu-central-1a"] 13 | private_subnets = ["10.0.1.0/24"] 14 | public_subnets = ["10.0.2.0/24"] 15 | 16 | enable_ipv6 = false 17 | 18 | enable_nat_gateway = true 19 | single_nat_gateway = false 20 | 21 | tags = { 22 | Owner = "user" 23 | Environment = "dev" 24 | } 25 | 26 | vpc_tags = { 27 | Name = "vpc-name" 28 | } 29 | } 30 | 31 | module "terraform-aws-bastion-ssm-iam" { 32 | source = "../../" 33 | 34 | # The name used to interpolate in the resources, defaults to bastion-ssm-iam 35 | # name = "bastion-ssm-iam" 36 | 37 | # The vpc id 38 | vpc_id = module.vpc.vpc_id 39 | 40 | # subnet_ids designates the subnets where the bastion can reside 41 | subnet_ids = module.vpc.private_subnets 42 | 43 | # The module creates a security group for the bastion by default 44 | # create_security_group = true 45 | 46 | # The module can create a diffent ssm document for this deployment, to allow 47 | # different security models per BASTION deployment 48 | # create_new_ssm_document = false 49 | 50 | # It is possible to attach other security groups to the bastion. 51 | # security_group_ids = [] 52 | } 53 | -------------------------------------------------------------------------------- /iam.tf: -------------------------------------------------------------------------------- 1 | data "aws_iam_policy_document" "trust_policy" { 2 | statement { 3 | effect = "Allow" 4 | 5 | principals { 6 | type = "Service" 7 | 8 | identifiers = [ 9 | "ec2.amazonaws.com", 10 | "ssm.amazonaws.com", 11 | ] 12 | } 13 | 14 | actions = ["sts:AssumeRole"] 15 | } 16 | } 17 | 18 | resource "aws_iam_role" "this" { 19 | name = local.name 20 | assume_role_policy = data.aws_iam_policy_document.trust_policy.json 21 | } 22 | 23 | resource "aws_iam_role_policy_attachment" "this" { 24 | for_each = toset(["arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore", "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"]) 25 | role = aws_iam_role.this.name 26 | policy_arn = each.key 27 | } 28 | 29 | data "aws_iam_policy_document" "kms_key_policy_iam_profile" { 30 | statement { 31 | effect = "Allow" 32 | 33 | actions = [ 34 | "kms:Decrypt" 35 | ] 36 | resources = [aws_kms_key.this.arn] 37 | } 38 | } 39 | 40 | resource "aws_iam_role_policy" "kms" { 41 | role = aws_iam_role.this.name 42 | name = "inline-policy-kms-access" 43 | policy = data.aws_iam_policy_document.kms_key_policy_iam_profile.json 44 | } 45 | 46 | resource "aws_iam_instance_profile" "this" { 47 | name = local.name 48 | role = aws_iam_role.this.name 49 | } 50 | -------------------------------------------------------------------------------- /kms.tf: -------------------------------------------------------------------------------- 1 | resource "aws_kms_key" "this" { 2 | description = "KMS Key for cloudwatch SSM logging" 3 | deletion_window_in_days = 10 4 | enable_key_rotation = true 5 | policy = data.aws_iam_policy_document.kms_key_policy.json 6 | } 7 | 8 | data "aws_iam_policy_document" "kms_key_policy" { 9 | statement { 10 | effect = "Allow" 11 | 12 | principals { 13 | type = "AWS" 14 | 15 | identifiers = [ 16 | "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" 17 | ] 18 | } 19 | 20 | actions = [ 21 | "kms:*", 22 | ] 23 | resources = ["*"] 24 | } 25 | statement { 26 | effect = "Allow" 27 | 28 | principals { 29 | type = "Service" 30 | 31 | identifiers = [ 32 | "logs.${data.aws_region.current.name}.amazonaws.com" 33 | ] 34 | } 35 | 36 | actions = [ 37 | "kms:Encrypt*", 38 | "kms:Decrypt*", 39 | "kms:ReEncrypt*", 40 | "kms:GenerateDataKey*", 41 | "kms:Describe*" 42 | ] 43 | resources = ["*"] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | name = "${var.name}-${random_string.this.result}" 3 | cloudwatch_prepend = "/aws/ec2/" 4 | # We use basename of the id to ensure dependency-order 5 | cloudwatch_loggroup_name = "${local.cloudwatch_prepend}${basename(aws_cloudwatch_log_group.this.id)}" 6 | ssm_document_name = var.create_new_ssm_document ? "SSM-SessionManagerRunShell-${random_string.this.result}" : "SSM-SessionManagerRunShell" 7 | } 8 | 9 | # Creating a random string for name interpolation 10 | resource "random_string" "this" { 11 | length = 5 12 | special = false 13 | } 14 | 15 | 16 | resource "aws_cloudwatch_log_group" "this" { 17 | name = "${local.cloudwatch_prepend}${local.name}" 18 | retention_in_days = var.log_retention 19 | kms_key_id = aws_kms_key.this.arn 20 | } 21 | 22 | resource "aws_ssm_document" "session_manager_prefs" { 23 | name = local.ssm_document_name 24 | document_type = "Session" 25 | document_format = "JSON" 26 | 27 | content = <
key = string
value = string
propagate_at_launch = optional(bool, true)
}))