├── .config ├── .checkov.yml ├── .mdlrc ├── .terraform-docs.yaml ├── .tflint.hcl ├── .tfsec.yml ├── .tfsec │ ├── launch_configuration_imdsv2_tfchecks.json │ ├── launch_template_imdsv2_tfchecks.json │ ├── no_launch_config_tfchecks.json │ ├── sg_no_embedded_egress_rules_tfchecks.json │ └── sg_no_embedded_ingress_rules_tfchecks.json ├── functional_tests │ ├── post-entrypoint-helpers.sh │ └── pre-entrypoint-helpers.sh └── static_tests │ ├── post-entrypoint-helpers.sh │ └── pre-entrypoint-helpers.sh ├── .copier-answers.yml ├── .gitignore ├── .header.md ├── .pre-commit-config.yaml ├── .project_automation ├── deprecation │ └── entrypoint.sh ├── deprovision │ └── entrypoint.sh ├── functional_tests │ ├── Dockerfile │ ├── entrypoint.sh │ └── functional_tests.sh ├── init │ └── noop.sh ├── provision │ └── entrypoint.sh ├── publication │ ├── Dockerfile │ └── entrypoint.sh ├── static_tests │ ├── Dockerfile │ ├── entrypoint.sh │ └── static_tests.sh └── update │ └── noop.sh ├── .project_config.yml ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE.txt ├── README.md ├── VERSION ├── examples ├── linux │ ├── .header.md │ ├── .terraform-docs.yaml │ ├── README.md │ ├── linuxbuild.yaml │ ├── linuxtest.yaml │ ├── main.tf │ ├── providers.tf │ └── scripts │ │ ├── HelloWorld.sh │ │ └── tests │ │ └── run-tests.sh └── windows │ ├── .header.md │ ├── .terraform-docs.yaml │ ├── README.md │ ├── main.tf │ ├── providers.tf │ ├── scripts │ ├── .DS_Store │ ├── HelloWorld.ps1 │ └── tests │ │ ├── HelloWorld.Tests.ps1 │ │ └── run-tests.ps1 │ ├── win2022build.yaml │ └── win2022test.yaml ├── images ├── ec2imagebuildertfmodule.png └── outputresources.png ├── main.tf ├── outputs.tf ├── providers.tf ├── tests ├── 01_mandatory.tftest.hcl └── 02_linux.tftest.hcl └── variables.tf /.config/.checkov.yml: -------------------------------------------------------------------------------- 1 | download-external-modules: False 2 | evaluate-variables: true 3 | directory: 4 | - ./ 5 | framework: 6 | - terraform 7 | skip-check: 8 | - CKV2_GCP* 9 | - CKV_AZURE* 10 | - CKV2_AZURE* 11 | - CKV_TF_1 # default to Terraform registry instead of Git 12 | summary-position: bottom 13 | output: 'cli' 14 | compact: True 15 | quiet: True -------------------------------------------------------------------------------- /.config/.mdlrc: -------------------------------------------------------------------------------- 1 | # Ignoring the following rules 2 | # MD007 Unordered list indentation 3 | # MD013 Line length 4 | # MD029 Ordered list item prefix 5 | rules "~MD007", "~MD013", "~MD029" -------------------------------------------------------------------------------- /.config/.terraform-docs.yaml: -------------------------------------------------------------------------------- 1 | formatter: markdown 2 | header-from: .header.md 3 | settings: 4 | anchor: true 5 | color: true 6 | default: true 7 | escape: true 8 | html: true 9 | indent: 2 10 | required: true 11 | sensitive: true 12 | type: true 13 | 14 | sort: 15 | enabled: true 16 | by: required 17 | 18 | output: 19 | file: README.md 20 | mode: replace 21 | -------------------------------------------------------------------------------- /.config/.tflint.hcl: -------------------------------------------------------------------------------- 1 | # https://github.com/terraform-linters/tflint/blob/master/docs/user-guide/module-inspection.md 2 | # borrowed & modified indefinitely from https://github.com/ksatirli/building-infrastructure-you-can-mostly-trust/blob/main/.tflint.hcl 3 | 4 | plugin "aws" { 5 | enabled = true 6 | version = "0.22.1" 7 | source = "github.com/terraform-linters/tflint-ruleset-aws" 8 | } 9 | 10 | config { 11 | module = true 12 | force = false 13 | } 14 | 15 | rule "terraform_required_providers" { 16 | enabled = true 17 | } 18 | 19 | rule "terraform_required_version" { 20 | enabled = true 21 | } 22 | 23 | rule "terraform_naming_convention" { 24 | enabled = true 25 | format = "snake_case" 26 | } 27 | 28 | rule "terraform_typed_variables" { 29 | enabled = true 30 | } 31 | 32 | rule "terraform_unused_declarations" { 33 | enabled = true 34 | } 35 | 36 | rule "terraform_comment_syntax" { 37 | enabled = true 38 | } 39 | 40 | rule "terraform_deprecated_index" { 41 | enabled = true 42 | } 43 | 44 | rule "terraform_deprecated_interpolation" { 45 | enabled = true 46 | } 47 | 48 | rule "terraform_documented_outputs" { 49 | enabled = true 50 | } 51 | 52 | rule "terraform_documented_variables" { 53 | enabled = true 54 | } 55 | 56 | rule "terraform_module_pinned_source" { 57 | enabled = true 58 | } 59 | 60 | rule "terraform_standard_module_structure" { 61 | enabled = true 62 | } 63 | 64 | rule "terraform_workspace_remote" { 65 | enabled = true 66 | } 67 | -------------------------------------------------------------------------------- /.config/.tfsec.yml: -------------------------------------------------------------------------------- 1 | { 2 | "minimum_severity": "MEDIUM" 3 | } -------------------------------------------------------------------------------- /.config/.tfsec/launch_configuration_imdsv2_tfchecks.json: -------------------------------------------------------------------------------- 1 | { 2 | "checks": [ 3 | { 4 | "code": "CUS002", 5 | "description": "Check to IMDSv2 is required on EC2 instances created by this Launch Template", 6 | "impact": "Instance metadata service can be interacted with freely", 7 | "resolution": "Enable HTTP token requirement for IMDS", 8 | "requiredTypes": [ 9 | "resource" 10 | ], 11 | "requiredLabels": [ 12 | "aws_launch_configuration" 13 | ], 14 | "severity": "CRITICAL", 15 | "matchSpec": { 16 | "action": "isPresent", 17 | "name": "metadata_options", 18 | "subMatch": { 19 | "action": "and", 20 | "predicateMatchSpec": [ 21 | { 22 | "action": "equals", 23 | "name": "http_tokens", 24 | "value": "required" 25 | 26 | } 27 | ] 28 | } 29 | }, 30 | 31 | "errorMessage": "is missing `metadata_options` block - it is required with `http_tokens` set to `required` to make Instance Metadata Service more secure.", 32 | "relatedLinks": [ 33 | "https://tfsec.dev/docs/aws/ec2/enforce-http-token-imds#aws/ec2", 34 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_configuration#metadata-options", 35 | "https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service" 36 | ] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.config/.tfsec/launch_template_imdsv2_tfchecks.json: -------------------------------------------------------------------------------- 1 | { 2 | "checks": [ 3 | { 4 | "code": "CUS001", 5 | "description": "Check to IMDSv2 is required on EC2 instances created by this Launch Template", 6 | "impact": "Instance metadata service can be interacted with freely", 7 | "resolution": "Enable HTTP token requirement for IMDS", 8 | "requiredTypes": [ 9 | "resource" 10 | ], 11 | "requiredLabels": [ 12 | "aws_launch_template" 13 | ], 14 | "severity": "CRITICAL", 15 | "matchSpec": { 16 | "action": "isPresent", 17 | "name": "metadata_options", 18 | "subMatch": { 19 | "action": "and", 20 | "predicateMatchSpec": [ 21 | { 22 | "action": "equals", 23 | "name": "http_tokens", 24 | "value": "required" 25 | 26 | } 27 | ] 28 | } 29 | }, 30 | 31 | "errorMessage": "is missing `metadata_options` block - it is required with `http_tokens` set to `required` to make Instance Metadata Service more secure.", 32 | "relatedLinks": [ 33 | "https://tfsec.dev/docs/aws/ec2/enforce-http-token-imds#aws/ec2", 34 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template#metadata-options", 35 | "https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service" 36 | ] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.config/.tfsec/no_launch_config_tfchecks.json: -------------------------------------------------------------------------------- 1 | { 2 | "checks": [ 3 | { 4 | "code": "CUS003", 5 | "description": "Use `aws_launch_template` over `aws_launch_configuration", 6 | "impact": "Launch configurations are not capable of versions", 7 | "resolution": "Convert resource type and attributes to `aws_launch_template`", 8 | "requiredTypes": [ 9 | "resource" 10 | ], 11 | "requiredLabels": [ 12 | "aws_launch_configuration" 13 | ], 14 | "severity": "MEDIUM", 15 | "matchSpec": { 16 | "action": "notPresent", 17 | "name": "image_id" 18 | }, 19 | 20 | "errorMessage": "should be changed to `aws_launch_template` since the functionality is the same but templates can be versioned.", 21 | "relatedLinks": [ 22 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template", 23 | "https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service" 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.config/.tfsec/sg_no_embedded_egress_rules_tfchecks.json: -------------------------------------------------------------------------------- 1 | { 2 | "checks": [ 3 | { 4 | "code": "CUS005", 5 | "description": "Security group rules should be defined with `aws_security_group_rule` instead of embedded.", 6 | "impact": "Embedded security group rules can cause issues during configuration updates.", 7 | "resolution": "Move `egress` rules to `aws_security_group_rule` and attach to `aws_security_group`.", 8 | "requiredTypes": [ 9 | "resource" 10 | ], 11 | "requiredLabels": [ 12 | "aws_security_group" 13 | ], 14 | "severity": "MEDIUM", 15 | "matchSpec": { 16 | "action": "notPresent", 17 | "name": "egress" 18 | }, 19 | 20 | "errorMessage": "`egress` rules should be moved to `aws_security_group_rule` and attached to `aws_security_group` instead of embedded.", 21 | "relatedLinks": [ 22 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule", 23 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group" 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.config/.tfsec/sg_no_embedded_ingress_rules_tfchecks.json: -------------------------------------------------------------------------------- 1 | { 2 | "checks": [ 3 | { 4 | "code": "CUS004", 5 | "description": "Security group rules should be defined with `aws_security_group_rule` instead of embedded.", 6 | "impact": "Embedded security group rules can cause issues during configuration updates.", 7 | "resolution": "Move `ingress` rules to `aws_security_group_rule` and attach to `aws_security_group`.", 8 | "requiredTypes": [ 9 | "resource" 10 | ], 11 | "requiredLabels": [ 12 | "aws_security_group" 13 | ], 14 | "severity": "MEDIUM", 15 | "matchSpec": { 16 | "action": "notPresent", 17 | "name": "ingress" 18 | }, 19 | 20 | "errorMessage": "`ingress` rules should be moved to `aws_security_group_rule` and attached to `aws_security_group` instead of embedded.", 21 | "relatedLinks": [ 22 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule", 23 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group" 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.config/functional_tests/post-entrypoint-helpers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## NOTE: this script runs at the end of functional test 3 | ## Use this to load any configurations after the functional test 4 | ## TIPS: avoid modifying the .project_automation/functional_test/entrypoint.sh 5 | ## migrate any customization you did on entrypoint.sh to this helper script 6 | echo "Executing Post-Entrypoint Helpers" -------------------------------------------------------------------------------- /.config/functional_tests/pre-entrypoint-helpers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## NOTE: this script runs at the start of functional test 3 | ## use this to load any configuration before the functional test 4 | ## TIPS: avoid modifying the .project_automation/functional_test/entrypoint.sh 5 | ## migrate any customization you did on entrypoint.sh to this helper script 6 | echo "Executing Pre-Entrypoint Helpers" -------------------------------------------------------------------------------- /.config/static_tests/post-entrypoint-helpers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## NOTE: this script runs at the end of static test 3 | ## Use this to load any configurations after the static test 4 | ## TIPS: avoid modifying the .project_automation/static_test/entrypoint.sh 5 | ## migrate any customization you did on entrypoint.sh to this helper script 6 | echo "Executing Post-Entrypoint Helpers" -------------------------------------------------------------------------------- /.config/static_tests/pre-entrypoint-helpers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## NOTE: this script runs at the start of static test 3 | ## use this to load any configuration before the static test 4 | ## TIPS: avoid modifying the .project_automation/static_test/entrypoint.sh 5 | ## migrate any customization you did on entrypoint.sh to this helper script 6 | echo "Executing Pre-Entrypoint Helpers" -------------------------------------------------------------------------------- /.copier-answers.yml: -------------------------------------------------------------------------------- 1 | # This file is auto-generated, changes will be overwritten 2 | _commit: v0.1.4 3 | _src_path: /task/e7a1f322-f68d-11ee-b0d0-1a8eb7bb45c9/projecttype 4 | starting_version: v0.0.0 5 | version_file: VERSION 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | plan.out 3 | plan.out.json 4 | 5 | # Local .terraform directories 6 | **/.terraform/* 7 | 8 | # .tfstate files 9 | *.tfstate 10 | *.tfstate.* 11 | 12 | # Crash log files 13 | crash.log 14 | 15 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as 16 | # password, private keys, and other secrets. These should not be part of version 17 | # control as they are data points which are potentially sensitive and subject 18 | # to change depending on the environment. 19 | # 20 | *.tfvars 21 | 22 | # Ignore override files as they are usually used to override resources locally and so 23 | # are not checked in 24 | override.tf 25 | override.tf.json 26 | *_override.tf 27 | *_override.tf.json 28 | 29 | # Include override files you do wish to add to version control using negated pattern 30 | # 31 | # !example_override.tf 32 | 33 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 34 | # example: *tfplan* 35 | 36 | # Ignore CLI configuration files 37 | .terraformrc 38 | terraform.rc 39 | .terraform.lock.hcl 40 | 41 | go.mod 42 | go.sum 43 | -------------------------------------------------------------------------------- /.header.md: -------------------------------------------------------------------------------- 1 | # AWS EC2 Image Builder Module 2 | 3 | This terraform module can be used to deploy [AWS EC2 Image Builder](https://aws.amazon.com/image-builder/). 4 | 5 | ✅ Deployment examples can be found under [examples](https://github.com/aws-ia/terraform-aws-ec2-image-builder/tree/main/examples) folder. 6 | 7 | ✅ AWS EC2 image builder documentation for more details about [AWS EC2 Image Builder](https://docs.aws.amazon.com/imagebuilder/) 8 | 9 | ## AWS EC2 Image Builder Architecture 10 | 11 | ![Resources provisioned by the EC2 Image Builder Terraform Module](./images/ec2imagebuildertfmodule.png){ width=100% } 12 | 13 | ## Usage 14 | 15 | The example below builds an EC2 Image using EC2 Image Builder in an existing VPC and Subnets. 16 | EC2 Image Builder simplifies the building, testing, and deployment of Virtual Machine and container images for use on AWS or on-premises. 17 | EC2 Image Builder supporting resources EC2 Key Pair, IAM role and Security groups are created by this module by default. 18 | This module allows you to bring your own EC2 Key Pair, additional IAM Policy and Security group. 19 | 20 | The following is a basic example, see examples folder for more complete examples: 21 | 22 | ```hcl 23 | module "ec2-image-builder" { 24 | source = "aws-ia/ec2-image-builder/aws" 25 | name = "basic-ec2-image" 26 | vpc_id = "" 27 | subnet_id = "" 28 | aws_region = "" 29 | source_cidr = [""] 30 | create_security_group = true 31 | create_key_pair = true 32 | instance_types = ["c5.large"] 33 | source_ami_name = "" # e.g.: "Windows_Server-2022-English-Core-Base-*" 34 | ami_name = "" # e.g.: "Windows 2022 core AMI" 35 | ami_description = "" # e.g.: "Windows 2022 core AMI provided by AWS" 36 | recipe_version = "0.0.1" 37 | build_component_arn = [""] # e.g.: arn:aws:imagebuilder:ap-southeast-2:XXXXXXXXXXX:component/win2022build/0.0.1/1 38 | test_component_arn = [""] # e.g.: arn:aws:imagebuilder:ap-southeast-2:XXXXXXXXXXXX:component/win2022test/0.0.1/1 39 | s3_bucket_name = "" 40 | custom_policy_arn = "" 41 | platform = "" 42 | tags = "" 43 | 44 | managed_components = "" 45 | # e.g.: 46 | #managed_components = [{ 47 | # name = "powershell-windows", 48 | # version = "7.2.10" 49 | # }, 50 | # { 51 | # name = "chocolatey", 52 | # version = "1.0.0" 53 | #}] 54 | 55 | target_account_ids = [ 56 | "" 57 | ] 58 | 59 | ami_regions_kms_key = { 60 | "" = "", 61 | "us-west-2" = "arn:aws:kms:us-west-2:XXXXXXX:key/mrk-XXXXXX", 62 | "us-east-1" = "arn:aws:kms:us-east-1:XXXXXX:key/mrk-XXXX", 63 | } 64 | 65 | } 66 | 67 | ``` 68 | 69 | ## Security 70 | 71 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 72 | 73 | ## License 74 | 75 | Apache-2.0 Licensed. See [LICENSE](https://github.com/aws-ia/terraform-aws-ec2-image-builder/blob/main/LICENSE). 76 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fail_fast: false 3 | minimum_pre_commit_version: "2.6.0" 4 | repos: 5 | - 6 | repo: https://github.com/terraform-docs/terraform-docs 7 | # To update run: 8 | # pre-commit autoupdate --freeze 9 | rev: 212db41760d7fc45d736d5eb94a483d0d2a12049 # frozen: v0.16.0 10 | hooks: 11 | - id: terraform-docs-go 12 | args: 13 | - "--config=.config/.terraform-docs.yaml" 14 | - "--lockfile=false" 15 | - "--recursive" 16 | - "--recursive-path=examples/" 17 | - "./" -------------------------------------------------------------------------------- /.project_automation/deprecation/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | ## NOTE: paths may differ when running in a managed task. To ensure behavior is consistent between 4 | # managed and local tasks always use these variables for the project and project type path 5 | PROJECT_PATH=${BASE_PATH}/project 6 | PROJECT_TYPE_PATH=${BASE_PATH}/projecttype 7 | -------------------------------------------------------------------------------- /.project_automation/deprovision/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | ## NOTE: paths may differ when running in a managed task. To ensure behavior is consistent between 4 | # managed and local tasks always use these variables for the project and project type path 5 | PROJECT_PATH=${BASE_PATH}/project 6 | PROJECT_TYPE_PATH=${BASE_PATH}/projecttype 7 | -------------------------------------------------------------------------------- /.project_automation/functional_tests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/codebuild/amazonlinux2-x86_64-standard:4.0 2 | ENV TERRAFORM_VERSION=1.7.4 3 | RUN cd /tmp && \ 4 | wget https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \ 5 | unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip -d /usr/local/bin && chmod 755 /usr/local/bin/terraform -------------------------------------------------------------------------------- /.project_automation/functional_tests/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## WARNING: DO NOT modify the content of entrypoint.sh 4 | # Use ./config/functional_tests/pre-entrypoint-helpers.sh or ./config/functional_tests/post-entrypoint-helpers.sh 5 | # to load any customizations or additional configurations 6 | 7 | ## NOTE: paths may differ when running in a managed task. To ensure behavior is consistent between 8 | # managed and local tasks always use these variables for the project and project type path 9 | PROJECT_PATH=${BASE_PATH}/project 10 | PROJECT_TYPE_PATH=${BASE_PATH}/projecttype 11 | 12 | #********** helper functions ************* 13 | pre_entrypoint() { 14 | if [ -f ${PROJECT_PATH}/.config/functional_tests/pre-entrypoint-helpers.sh ]; then 15 | echo "Pre-entrypoint helper found" 16 | source ${PROJECT_PATH}/.config/functional_tests/pre-entrypoint-helpers.sh 17 | echo "Pre-entrypoint helper loaded" 18 | else 19 | echo "Pre-entrypoint helper not found - skipped" 20 | fi 21 | } 22 | post_entrypoint() { 23 | if [ -f ${PROJECT_PATH}/.config/functional_tests/post-entrypoint-helpers.sh ]; then 24 | echo "Post-entrypoint helper found" 25 | source ${PROJECT_PATH}/.config/functional_tests/post-entrypoint-helpers.sh 26 | echo "Post-entrypoint helper loaded" 27 | else 28 | echo "Post-entrypoint helper not found - skipped" 29 | fi 30 | } 31 | 32 | #********** Pre-entrypoint helper ************* 33 | pre_entrypoint 34 | 35 | #********** Functional Test ************* 36 | /bin/bash ${PROJECT_PATH}/.project_automation/functional_tests/functional_tests.sh 37 | if [ $? -eq 0 ] 38 | then 39 | echo "Functional test completed" 40 | EXIT_CODE=0 41 | else 42 | echo "Functional test failed" 43 | EXIT_CODE=1 44 | fi 45 | 46 | #********** Post-entrypoint helper ************* 47 | post_entrypoint 48 | 49 | #********** Exit Code ************* 50 | exit $EXIT_CODE 51 | -------------------------------------------------------------------------------- /.project_automation/functional_tests/functional_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## NOTE: paths may differ when running in a managed task. To ensure behavior is consistent between 4 | # managed and local tasks always use these variables for the project and project type path 5 | PROJECT_PATH=${BASE_PATH}/project 6 | PROJECT_TYPE_PATH=${BASE_PATH}/projecttype 7 | 8 | echo "Starting Functional Tests" 9 | cd ${PROJECT_PATH} 10 | 11 | #********** Terraform Test ********** 12 | 13 | # Look up the mandatory test file 14 | MANDATORY_TEST_PATH="./tests/01_mandatory.tftest.hcl" 15 | if test -f ${MANDATORY_TEST_PATH}; then 16 | echo "File ${MANDATORY_TEST_PATH} is found, resuming test" 17 | # Run Terraform test 18 | terraform init 19 | terraform test 20 | else 21 | echo "File ${MANDATORY_TEST_PATH} not found. You must include at least one test run in file ${MANDATORY_TEST_PATH}" 22 | (exit 1) 23 | fi 24 | 25 | if [ $? -eq 0 ]; then 26 | echo "Terraform Test Successfull" 27 | else 28 | echo "Terraform Test Failed" 29 | exit 1 30 | fi 31 | 32 | echo "End of Functional Tests" -------------------------------------------------------------------------------- /.project_automation/init/noop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Not Supported!" 3 | -------------------------------------------------------------------------------- /.project_automation/provision/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | ## NOTE: paths may differ when running in a managed task. To ensure behavior is consistent between 4 | # managed and local tasks always use these variables for the project and project type path 5 | PROJECT_PATH=${BASE_PATH}/project 6 | PROJECT_TYPE_PATH=${BASE_PATH}/projecttype 7 | -------------------------------------------------------------------------------- /.project_automation/publication/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/codebuild/amazonlinux2-x86_64-standard:4.0 2 | RUN yum install -y yum-utils && yum-config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && yum install -y gh 3 | RUN pip install awscli 4 | -------------------------------------------------------------------------------- /.project_automation/publication/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | ## NOTE: paths may differ when running in a managed task. To ensure behavior is consistent between 4 | # managed and local tasks always use these variables for the project and project type path 5 | PROJECT_PATH=${BASE_PATH}/project 6 | PROJECT_TYPE_PATH=${BASE_PATH}/projecttype 7 | 8 | echo "[STAGE: Publication]" 9 | VERSION=$(cat VERSION) 10 | echo $VERSION 11 | BRANCH=main 12 | EXISTING_GIT_VERSION="$(git tag -l)" 13 | 14 | if [[ $(echo $EXISTING_GIT_VERSION | grep $VERSION) ]] 15 | then 16 | echo "version exists skipping release creation hint: Bump version in VERSION file" 17 | else 18 | echo "creating new version" 19 | gh release create ${VERSION} --target ${BRANCH} --generate-notes 20 | fi 21 | -------------------------------------------------------------------------------- /.project_automation/static_tests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/codebuild/amazonlinux2-x86_64-standard:4.0 2 | ENV TERRAFORM_VERSION=1.7.4 3 | RUN cd /tmp && \ 4 | wget https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \ 5 | unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip -d /usr/local/bin && chmod 755 /usr/local/bin/terraform 6 | 7 | ENV TFLINT_VERSION=v0.45.0 8 | 9 | RUN cd /tmp && \ 10 | wget https://github.com/terraform-linters/tflint/releases/download/${TFLINT_VERSION}/tflint_linux_amd64.zip && \ 11 | unzip tflint_linux_amd64.zip -d /usr/local/bin && chmod 755 /usr/local/bin/tflint 12 | 13 | RUN mkdir -p ~/.tflint.d/plugins 14 | 15 | ENV TFLINT_VERSION=v0.23.0 16 | 17 | RUN wget -O /tmp/tflint-ruleset-aws.zip https://github.com/terraform-linters/tflint-ruleset-aws/releases/download/${TFLINT_VERSION}/tflint-ruleset-aws_darwin_arm64.zip \ 18 | && unzip /tmp/tflint-ruleset-aws.zip -d ~/.tflint.d/plugins \ 19 | && rm /tmp/tflint-ruleset-aws.zip 20 | 21 | RUN curl -s https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash 22 | 23 | RUN pip3 install checkov 24 | 25 | RUN gem install mdl 26 | 27 | ENV TERRAFORM_DOCS_VERSION=v0.16.0 28 | RUN wget https://github.com/terraform-docs/terraform-docs/releases/download/${TERRAFORM_DOCS_VERSION}/terraform-docs-${TERRAFORM_DOCS_VERSION}-linux-amd64.tar.gz && \ 29 | tar -C /usr/local/bin -xzf terraform-docs-${TERRAFORM_DOCS_VERSION}-linux-amd64.tar.gz && chmod +x /usr/local/bin/terraform-docs -------------------------------------------------------------------------------- /.project_automation/static_tests/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## WARNING: DO NOT modify the content of entrypoint.sh 4 | # Use ./config/static_tests/pre-entrypoint-helpers.sh or ./config/static_tests/post-entrypoint-helpers.sh 5 | # to load any customizations or additional configurations 6 | 7 | ## NOTE: paths may differ when running in a managed task. To ensure behavior is consistent between 8 | # managed and local tasks always use these variables for the project and project type path 9 | PROJECT_PATH=${BASE_PATH}/project 10 | PROJECT_TYPE_PATH=${BASE_PATH}/projecttype 11 | 12 | #********** helper functions ************* 13 | pre_entrypoint() { 14 | if [ -f ${PROJECT_PATH}/.config/static_tests/pre-entrypoint-helpers.sh ]; then 15 | echo "Pre-entrypoint helper found" 16 | source ${PROJECT_PATH}/.config/static_tests/pre-entrypoint-helpers.sh 17 | echo "Pre-entrypoint helper loaded" 18 | else 19 | echo "Pre-entrypoint helper not found - skipped" 20 | fi 21 | } 22 | post_entrypoint() { 23 | if [ -f ${PROJECT_PATH}/.config/static_tests/post-entrypoint-helpers.sh ]; then 24 | echo "Post-entrypoint helper found" 25 | source ${PROJECT_PATH}/.config/static_tests/post-entrypoint-helpers.sh 26 | echo "Post-entrypoint helper loaded" 27 | else 28 | echo "Post-entrypoint helper not found - skipped" 29 | fi 30 | } 31 | 32 | #********** Pre-entrypoint helper ************* 33 | pre_entrypoint 34 | 35 | #********** Static Test ************* 36 | /bin/bash ${PROJECT_PATH}/.project_automation/static_tests/static_tests.sh 37 | if [ $? -eq 0 ] 38 | then 39 | echo "Static test completed" 40 | EXIT_CODE=0 41 | else 42 | echo "Static test failed" 43 | EXIT_CODE=1 44 | fi 45 | 46 | #********** Post-entrypoint helper ************* 47 | post_entrypoint 48 | 49 | #********** Exit Code ************* 50 | exit $EXIT_CODE -------------------------------------------------------------------------------- /.project_automation/static_tests/static_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## NOTE: paths may differ when running in a managed task. To ensure behavior is consistent between 4 | # managed and local tasks always use these variables for the project and project type path 5 | PROJECT_PATH=${BASE_PATH}/project 6 | PROJECT_TYPE_PATH=${BASE_PATH}/projecttype 7 | 8 | echo "Starting Static Tests" 9 | 10 | #********** Terraform Validate ************* 11 | cd ${PROJECT_PATH} 12 | terraform init 13 | terraform validate 14 | if [ $? -eq 0 ] 15 | then 16 | echo "Success - Terraform validate" 17 | else 18 | echo "Failure - Terraform validate" 19 | exit 1 20 | fi 21 | 22 | #********** tflint ******************** 23 | echo 'Starting tflint' 24 | tflint --init --config ${PROJECT_PATH}/.config/.tflint.hcl 25 | MYLINT=$(tflint --force --config ${PROJECT_PATH}/.config/.tflint.hcl) 26 | if [ -z "$MYLINT" ] 27 | then 28 | echo "Success - tflint found no linting issues!" 29 | else 30 | echo "Failure - tflint found linting issues!" 31 | echo "$MYLINT" 32 | exit 1 33 | fi 34 | 35 | #********** tfsec ********************* 36 | echo 'Starting tfsec' 37 | MYTFSEC=$(tfsec . --config-file ${PROJECT_PATH}/.config/.tfsec.yml --custom-check-dir ${PROJECT_PATH}/.config/.tfsec) 38 | if [[ $MYTFSEC == *"No problems detected!"* ]]; 39 | then 40 | echo "Success - tfsec found no security issues!" 41 | echo "$MYTFSEC" 42 | else 43 | echo "Failure - tfsec found security issues!" 44 | echo "$MYTFSEC" 45 | exit 1 46 | fi 47 | 48 | #********** Checkov Analysis ************* 49 | echo "Running Checkov Analysis" 50 | checkov --config-file ${PROJECT_PATH}/.config/.checkov.yml 51 | if [ $? -eq 0 ] 52 | then 53 | echo "Success - Checkov found no issues!" 54 | else 55 | echo "Failure - Checkov found issues!" 56 | exit 1 57 | fi 58 | 59 | #********** Markdown Lint ************** 60 | echo 'Starting markdown lint' 61 | MYMDL=$(mdl --config ${PROJECT_PATH}/.config/.mdlrc .header.md examples/*/.header.md) 62 | if [ -z "$MYMDL" ] 63 | then 64 | echo "Success - markdown lint found no linting issues!" 65 | else 66 | echo "Failure - markdown lint found linting issues!" 67 | echo "$MYMDL" 68 | exit 1 69 | fi 70 | 71 | #********** Terraform Docs ************* 72 | echo 'Starting terraform-docs' 73 | TDOCS="$(terraform-docs --config ${PROJECT_PATH}/.config/.terraform-docs.yaml --lockfile=false ./)" 74 | git add -N README.md 75 | GDIFF="$(git diff --compact-summary)" 76 | if [ -z "$GDIFF" ] 77 | then 78 | echo "Success - Terraform Docs creation verified!" 79 | else 80 | echo "Failure - Terraform Docs creation failed, ensure you have precommit installed and running before submitting the Pull Request. TIPS: false error may occur if you have unstaged files in your repo" 81 | echo "$GDIFF" 82 | exit 1 83 | fi 84 | 85 | #*************************************** 86 | echo "End of Static Tests" -------------------------------------------------------------------------------- /.project_automation/update/noop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Not Supported!" 3 | -------------------------------------------------------------------------------- /.project_config.yml: -------------------------------------------------------------------------------- 1 | version: "1.0.0" 2 | 3 | init: 4 | entrypoint: .project_automation/init/noop.sh 5 | update: 6 | entrypoint: .project_automation/update/noop.sh 7 | static_tests: 8 | dockerfile: .project_automation/static_tests/Dockerfile 9 | entrypoint: .project_automation/static_tests/entrypoint.sh 10 | functional_tests: 11 | github_permissions: 12 | contents: write 13 | dockerfile: .project_automation/functional_tests/Dockerfile 14 | entrypoint: .project_automation/functional_tests/entrypoint.sh 15 | publication: 16 | github_permissions: 17 | contents: write 18 | dockerfile: .project_automation/publication/Dockerfile 19 | entrypoint: .project_automation/publication/entrypoint.sh 20 | deprecation: 21 | entrypoint: .project_automation/deprecation/entrypoint.sh 22 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @aws-ia/aws-ia -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Creating modules for Terraform 2 | 3 | This repository contains code for an application that is published using the Application Builder Platform (ABP). 4 | 5 | ## Module Standards 6 | 7 | For best practices and information on developing with Terraform, see the [I&A Module Standards](https://aws-ia.github.io/standards-terraform/) 8 | 9 | ## Contributing Code 10 | 11 | In order to contibute code to this repository, you must submit a *[Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request)*. To do so, you must *[fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo)* this repostiory, make your changes in your forked version and submit a *Pull Request*. 12 | 13 | ## Writing Documentation 14 | 15 | > :bangbang: **Do not manually update README.md**. 16 | 17 | README.md is automatically generated by pulling in content from other files. For instructions, including a fill-in-the-blank content template, see [Create readmes for Terraform-based Partner Solutions.](https://aws-ia-us-west-2.s3.us-west-2.amazonaws.com/docs/content/index.html#/lessons/8rpYWWL59M7dcS-NsjYmaISUu-L_UqEv) 18 | 19 | 20 | ## Checks and Validation 21 | 22 | Pull Requests (PRs) submitted against this repository undergo a series of static and functional checks. 23 | 24 | > :exclamation: Note: Failures during funtional or static checks will prevent a pull request from being accepted. 25 | 26 | It is a best practice to perform these checks locally prior to submitting a pull request. 27 | 28 | ## Checks Performed 29 | - TFLint 30 | - tfsec 31 | - Markdown Lint 32 | - Checkov 33 | - Terraform test 34 | 35 | > :bangbang: The readme.md file will be created after all checks have completed successfuly, it is recommended that you install terraform-docs locally in order to preview your readme.md file prior to publication. 36 | 37 | ## Install the required tools 38 | 39 | Prerequisites: 40 | - [Python](https://docs.python.org/3/using/index.html) 41 | - [Pip](https://pip.pypa.io/en/stable/installation/) 42 | - [golang](https://go.dev/doc/install) (for macos you can use `brew`) 43 | - [tflint](https://github.com/terraform-linters/tflint) 44 | - [tfsec](https://aquasecurity.github.io/tfsec/v1.0.11/) 45 | - [Markdown Lint](https://github.com/markdownlint/markdownlint) 46 | - [Checkov](https://www.checkov.io/2.Basics/Installing%20Checkov.html) 47 | - [terraform-docs](https://github.com/terraform-docs/terraform-docs) 48 | - [coreutils](https://www.gnu.org/software/coreutils/) 49 | 50 | ## Performing Checks manually 51 | 52 | Preparation 53 | ``` 54 | terraform init 55 | terraform validate 56 | ``` 57 | ## Checks 58 | 59 | ### tflint 60 | ``` 61 | tflint --init 62 | tflint 63 | ``` 64 | ### tfsec 65 | ``` 66 | tfsec . 67 | ``` 68 | ### Markdown Lint 69 | ``` 70 | mdl .header.md 71 | ``` 72 | ### Checkov 73 | ``` 74 | terraform init 75 | terraform plan -out tf.plan 76 | terraform show -json tf.plan > tf.json 77 | checkov 78 | ``` 79 | 80 | ## Documentation 81 | 82 | ### terraform-docs 83 | 84 | ``` 85 | # from the root of the repository 86 | terraform-docs --lockfile=false ./ 87 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2016-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | 5 | http://aws.amazon.com/apache2.0/ 6 | 7 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # AWS EC2 Image Builder Module 3 | 4 | This terraform module can be used to deploy [AWS EC2 Image Builder](https://aws.amazon.com/image-builder/). 5 | 6 | ✅ Deployment examples can be found under [examples](https://github.com/aws-ia/terraform-aws-ec2-image-builder/tree/main/examples) folder. 7 | 8 | ✅ AWS EC2 image builder documentation for more details about [AWS EC2 Image Builder](https://docs.aws.amazon.com/imagebuilder/) 9 | 10 | ## AWS EC2 Image Builder Architecture 11 | 12 | ![Resources provisioned by the EC2 Image Builder Terraform Module](./images/ec2imagebuildertfmodule.png){ width=100% } 13 | 14 | ## Usage 15 | 16 | The example below builds an EC2 Image using EC2 Image Builder in an existing VPC and Subnets. 17 | EC2 Image Builder simplifies the building, testing, and deployment of Virtual Machine and container images for use on AWS or on-premises. 18 | EC2 Image Builder supporting resources EC2 Key Pair, IAM role and Security groups are created by this module by default. 19 | This module allows you to bring your own EC2 Key Pair, additional IAM Policy and Security group. 20 | 21 | The following is a basic example, see examples folder for more complete examples: 22 | 23 | ```hcl 24 | module "ec2-image-builder" { 25 | source = "aws-ia/ec2-image-builder/aws" 26 | name = "basic-ec2-image" 27 | vpc_id = "" 28 | subnet_id = "" 29 | aws_region = "" 30 | source_cidr = [""] 31 | create_security_group = true 32 | create_key_pair = true 33 | instance_types = ["c5.large"] 34 | source_ami_name = "" # e.g.: "Windows_Server-2022-English-Core-Base-*" 35 | ami_name = "" # e.g.: "Windows 2022 core AMI" 36 | ami_description = "" # e.g.: "Windows 2022 core AMI provided by AWS" 37 | recipe_version = "0.0.1" 38 | build_component_arn = [""] # e.g.: arn:aws:imagebuilder:ap-southeast-2:XXXXXXXXXXX:component/win2022build/0.0.1/1 39 | test_component_arn = [""] # e.g.: arn:aws:imagebuilder:ap-southeast-2:XXXXXXXXXXXX:component/win2022test/0.0.1/1 40 | s3_bucket_name = "" 41 | custom_policy_arn = "" 42 | platform = "" 43 | tags = "" 44 | 45 | managed_components = "" 46 | # e.g.: 47 | #managed_components = [{ 48 | # name = "powershell-windows", 49 | # version = "7.2.10" 50 | # }, 51 | # { 52 | # name = "chocolatey", 53 | # version = "1.0.0" 54 | #}] 55 | 56 | target_account_ids = [ 57 | "" 58 | ] 59 | 60 | ami_regions_kms_key = { 61 | "" = "", 62 | "us-west-2" = "arn:aws:kms:us-west-2:XXXXXXX:key/mrk-XXXXXX", 63 | "us-east-1" = "arn:aws:kms:us-east-1:XXXXXX:key/mrk-XXXX", 64 | } 65 | 66 | } 67 | 68 | ``` 69 | 70 | ## Security 71 | 72 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 73 | 74 | ## License 75 | 76 | Apache-2.0 Licensed. See [LICENSE](https://github.com/aws-ia/terraform-aws-ec2-image-builder/blob/main/LICENSE). 77 | 78 | ## Requirements 79 | 80 | | Name | Version | 81 | |------|---------| 82 | | [terraform](#requirement\_terraform) | >= 1.4.0 | 83 | | [aws](#requirement\_aws) | >= 5.0.0 | 84 | 85 | ## Providers 86 | 87 | | Name | Version | 88 | |------|---------| 89 | | [aws](#provider\_aws) | >= 5.0.0 | 90 | 91 | ## Modules 92 | 93 | No modules. 94 | 95 | ## Resources 96 | 97 | | Name | Type | 98 | |------|------| 99 | | [aws_iam_instance_profile.iam_instance_profile](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | 100 | | [aws_iam_role.awsserviceroleforimagebuilder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | 101 | | [aws_iam_role_policy.aws_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | 102 | | [aws_iam_role_policy_attachment.custom_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | 103 | | [aws_iam_role_policy_attachment.imagebuilder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | 104 | | [aws_iam_role_policy_attachment.ssm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | 105 | | [aws_imagebuilder_distribution_configuration.imagebuilder_distribution_configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_distribution_configuration) | resource | 106 | | [aws_imagebuilder_image.imagebuilder_image](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_image) | resource | 107 | | [aws_imagebuilder_image_pipeline.imagebuilder_image_pipeline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_image_pipeline) | resource | 108 | | [aws_imagebuilder_image_recipe.imagebuilder_image_recipe](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_image_recipe) | resource | 109 | | [aws_imagebuilder_infrastructure_configuration.imagebuilder_infrastructure_configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_infrastructure_configuration) | resource | 110 | | [aws_security_group.security_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | 111 | | [aws_security_group_rule.sg_https_ingress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | 112 | | [aws_security_group_rule.sg_internet_egress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | 113 | | [aws_security_group_rule.sg_rdp_ingress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | 114 | | [aws_ami.source_ami](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | 115 | | [aws_iam_policy_document.assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 116 | | [aws_iam_policy_document.aws_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 117 | | [aws_imagebuilder_components.managed_components](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/imagebuilder_components) | data source | 118 | | [aws_vpc.selected](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | 119 | 120 | ## Inputs 121 | 122 | | Name | Description | Type | Default | Required | 123 | |------|-------------|------|---------|:--------:| 124 | | [ami\_description](#input\_ami\_description) | (Required) Choose a description for the AMI | `string` | n/a | yes | 125 | | [ami\_name](#input\_ami\_name) | (Required) Choose a name for the AMI | `string` | n/a | yes | 126 | | [aws\_region](#input\_aws\_region) | (Required) AWS Region to deploy the resources | `string` | n/a | yes | 127 | | [name](#input\_name) | (Required) Choose a name for the project which will be the prefix for every resource | `string` | n/a | yes | 128 | | [platform](#input\_platform) | (Required) OS: Windows or Linux | `string` | n/a | yes | 129 | | [source\_ami\_name](#input\_source\_ami\_name) | (Required) Source AMI name, e.g: Windows\_Server-2022-English-Core-Base-* | `string` | n/a | yes | 130 | | [subnet\_id](#input\_subnet\_id) | (Required) Subnet ID to deploy the EC2 Image Builder Environment. | `string` | n/a | yes | 131 | | [vpc\_id](#input\_vpc\_id) | (Required) VPC ID to deploy the EC2 Image Builder Environment. | `string` | n/a | yes | 132 | | [ami\_regions\_kms\_key](#input\_ami\_regions\_kms\_key) | (Optional) A list of AWS Regions to share the AMI with and also target KMS Key in each region | `map(string)` | `{}` | no | 133 | | [attach\_custom\_policy](#input\_attach\_custom\_policy) | (Required) Attach custom policy to the EC2 Instance Profile, if true, ARN of the custom policy needs to be specified on the variable custom\_policy\_arn | `bool` | `false` | no | 134 | | [build\_component\_arn](#input\_build\_component\_arn) | (Required) List of ARNs for the Build EC2 Image Builder Build Components | `list(string)` | `[]` | no | 135 | | [create\_security\_group](#input\_create\_security\_group) | (Optional) Create security group for EC2 Image Builder instances. Please note this security group will be created with default egress rule to 0.0.0.0/0 CIDR Block. In case you want to have a more restrict set of rules, please provide your own security group id on security\_group\_ids variable | `bool` | `true` | no | 136 | | [custom\_policy\_arn](#input\_custom\_policy\_arn) | (Optional) ARN of the custom policy to be attached to the EC2 Instance Profile | `string` | `null` | no | 137 | | [imagebuilder\_image\_recipe\_kms\_key\_arn](#input\_imagebuilder\_image\_recipe\_kms\_key\_arn) | (Required) KMS Key ARN(CMK) for encrypting Imagebuilder Image Recipe Block Device Mapping | `string` | `null` | no | 138 | | [instance\_key\_pair](#input\_instance\_key\_pair) | (Optional) EC2 key pair to add to the default user on the builder(In case existent EC2 Key Pair is provided) | `string` | `null` | no | 139 | | [instance\_types](#input\_instance\_types) | (Optional) Instance type for the EC2 Image Builder Instances.
Will be set by default to c5.large. Please check the AWS Pricing for more information about the instance types. | `list(string)` |
[
"c5.large"
]
| no | 140 | | [managed\_components](#input\_managed\_components) | (Optional) Specify the name and version of the AWS managed components that are going to be part of the image recipe |
list(object({
name = string,
version = string
}))
| `[]` | no | 141 | | [recipe\_version](#input\_recipe\_version) | (Required) The semantic version of the image recipe. This version follows the semantic version syntax. e.g.: 0.0.1 | `string` | `"0.0.1"` | no | 142 | | [recipe\_volume\_size](#input\_recipe\_volume\_size) | (Optional) Volume Size of Imagebuilder Image Recipe Block Device Mapping | `string` | `100` | no | 143 | | [recipe\_volume\_type](#input\_recipe\_volume\_type) | (Optional) Volume Type of Imagebuilder Image Recipe Block Device Mapping | `string` | `"gp3"` | no | 144 | | [s3\_bucket\_name](#input\_s3\_bucket\_name) | (Required) S3 Bucket Name which will store EC2 Image Builder TOE logs and is storing the build/test YAML files | `string` | `""` | no | 145 | | [schedule\_expression](#input\_schedule\_expression) | "(Optional) pipeline\_execution\_start\_condition = The condition configures when the pipeline should trigger a new image build.
Valid Values: EXPRESSION\_MATCH\_ONLY \| EXPRESSION\_MATCH\_AND\_DEPENDENCY\_UPDATES\_AVAILABLE
scheduleExpression = The cron expression determines how often EC2 Image Builder evaluates your pipelineExecutionStartCondition.
e.g.: "cron(0 0 * * ? *)" |
list(object({
pipeline_execution_start_condition = string,
scheduleExpression = string
}))
| `[]` | no | 146 | | [security\_group\_ids](#input\_security\_group\_ids) | (Optional) Security group IDs for EC2 Image Builder instances(In case existent Security Group is provided) | `list(string)` | `[]` | no | 147 | | [source\_ami\_owner](#input\_source\_ami\_owner) | (Optional) Owner of the AMI , default: amazon | `string` | `"amazon"` | no | 148 | | [source\_cidr](#input\_source\_cidr) | (Required) Source CIDR block which will be allowed to RDP or SSH to EC2 Image Builder Instances | `list(string)` | `[]` | no | 149 | | [tags](#input\_tags) | (Optional) A map of resource tags to associate with the resource | `map(string)` | `{}` | no | 150 | | [target\_account\_ids](#input\_target\_account\_ids) | (Optional) A list of target accounts to share the AMI with | `list(string)` | `[]` | no | 151 | | [terminate\_on\_failure](#input\_terminate\_on\_failure) | (Optional) Change to false if you want to connect to a builder for debugging after failure | `bool` | `true` | no | 152 | | [test\_component\_arn](#input\_test\_component\_arn) | (Required) List of ARNs for the Build EC2 Image Builder Test Components | `list(string)` | `[]` | no | 153 | | [timeout](#input\_timeout) | (Optional) Number of hours before image time out. Defaults to 2h. | `string` | `"2h"` | no | 154 | 155 | ## Outputs 156 | 157 | | Name | Description | 158 | |------|-------------| 159 | | [ami](#output\_ami) | AMI created by Terraform | 160 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | v0.0.1 2 | -------------------------------------------------------------------------------- /examples/linux/.header.md: -------------------------------------------------------------------------------- 1 | 2 | # Using the EC2 Image Builder Terraform Module to create an Linux AMI 3 | 4 | This example will use the EC2 Image Builder Terraform Module to create an Amazon Linux 2023 Custom AMI. 5 | 6 | The build components are currently all in a single file: linuxbuild.yaml 7 | The tests components are currently all in a single file: linuxtest.yaml 8 | 9 | However the module supports providing multiple files, given the parameters build_component_arn and test_component_arn are of list of strings type. 10 | 11 | The module cover the creation of the core components of the EC2 Image Builder, like Image Pipeline, Image Recipe, Distribution Configuration, 12 | Infrastructure Configuration, etc, but there are components which needs to be created outside of the module and provide as input, which are basically 13 | what it is going to be executed during the build and test process, and also custom IAM Policy with permissions required to interact with 14 | AWS services related to your build and test components. 15 | 16 | This example creates the following resources: 17 | 18 | - S3 Bucket 19 | - EC2 Image Builder Build Component, which executes the hello world powershell script 20 | - EC2 Image Builder Test Component, which executes the pester script which tests the output of the powershell hello world script 21 | - S3 Objects, which uploads the build and Test YAML Files to S3 22 | - IAM Policy with permissions to read/write to the S3 Bucket 23 | - VPC/Subnets/Route Tables/Nat Gateway/etc 24 | 25 | ## How to Deploy 26 | 27 | ### Prerequisites 28 | 29 | Ensure that you have installed the following tools in your Mac or Windows Laptop before start working with this module and run Terraform Plan and Apply 30 | 31 | 1. [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) 32 | 2. [Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli) 33 | 34 | ### Deployment Steps 35 | 36 | #### Step 1: Clone the repo using the command below 37 | 38 | ```sh 39 | git clone https://github.com/aws-ia/terraform-aws-ec2-image-builder.git 40 | ``` 41 | 42 | #### Step 2: Run Terraform INIT 43 | 44 | Initialize a working directory with configuration files 45 | 46 | ```sh 47 | cd examples/linux/ 48 | 49 | terraform init 50 | ``` 51 | 52 | #### Step 3: Run Terraform PLAN 53 | 54 | Verify the resources created by this execution 55 | 56 | ```sh 57 | export AWS_REGION= # default set to `us-west-2` 58 | terraform plan 59 | ``` 60 | 61 | #### Step 4: Finally, Terraform APPLY 62 | 63 | Create the resources 64 | 65 | ```sh 66 | terraform apply 67 | ``` 68 | 69 | Enter `yes` to apply. 70 | 71 | #### Step 5: Verify the AMI on AWS Console 72 | 73 | Output of Terraform apply should look similar 74 | 75 | ```sh 76 | module.ec2-image-builder.aws_imagebuilder_image.imagebuilder_image[0]: Creation complete after 1h3m56s [id=arn:aws:imagebuilder:ap-southeast-2:XXXXXXXX:image/myfirstpipeline-image-recipe/0.0.2/1] 77 | ``` 78 | 79 | Login to AWS Console, go to the AWS Region where resources are deployed, and go to the location showed on the Terraform output, for example: 80 | 81 | EC2 Image Builder > Images > "myfirstpipeline-image-recipe 0.0.2" > "myfirstpipeline-image-recipe 0.0.2/1" 82 | 83 | You can see the AMI ID on the output resources: 84 | 85 | ![AMI generated by EC2 Image Builder](../../images/outputresources.png) 86 | 87 | ## Cleanup 88 | 89 | To clean up your environment, destroy the Terraform module. 90 | 91 | *NOTE:* Empty the S3 bucket created by this module before executing the `terraform destroy` 92 | 93 | ```sh 94 | terraform destroy -auto-approve 95 | ``` 96 | -------------------------------------------------------------------------------- /examples/linux/.terraform-docs.yaml: -------------------------------------------------------------------------------- 1 | formatter: markdown 2 | header-from: .header.md 3 | settings: 4 | anchor: true 5 | color: true 6 | default: true 7 | escape: true 8 | html: true 9 | indent: 2 10 | required: true 11 | sensitive: true 12 | type: true 13 | lockfile: false 14 | 15 | sort: 16 | enabled: true 17 | by: required 18 | 19 | output: 20 | file: README.md 21 | mode: replace 22 | -------------------------------------------------------------------------------- /examples/linux/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Using the EC2 Image Builder Terraform Module to create an Linux AMI 4 | 5 | This example will use the EC2 Image Builder Terraform Module to create an Amazon Linux 2023 Custom AMI. 6 | 7 | The build components are currently all in a single file: linuxbuild.yaml 8 | The tests components are currently all in a single file: linuxtest.yaml 9 | 10 | However the module supports providing multiple files, given the parameters build\_component\_arn and test\_component\_arn are of list of strings type. 11 | 12 | The module cover the creation of the core components of the EC2 Image Builder, like Image Pipeline, Image Recipe, Distribution Configuration, 13 | Infrastructure Configuration, etc, but there are components which needs to be created outside of the module and provide as input, which are basically 14 | what it is going to be executed during the build and test process, and also custom IAM Policy with permissions required to interact with 15 | AWS services related to your build and test components. 16 | 17 | This example creates the following resources: 18 | 19 | - S3 Bucket 20 | - EC2 Image Builder Build Component, which executes the hello world powershell script 21 | - EC2 Image Builder Test Component, which executes the pester script which tests the output of the powershell hello world script 22 | - S3 Objects, which uploads the build and Test YAML Files to S3 23 | - IAM Policy with permissions to read/write to the S3 Bucket 24 | - VPC/Subnets/Route Tables/Nat Gateway/etc 25 | 26 | ## How to Deploy 27 | 28 | ### Prerequisites 29 | 30 | Ensure that you have installed the following tools in your Mac or Windows Laptop before start working with this module and run Terraform Plan and Apply 31 | 32 | 1. [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) 33 | 2. [Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli) 34 | 35 | ### Deployment Steps 36 | 37 | #### Step 1: Clone the repo using the command below 38 | 39 | ```sh 40 | git clone https://github.com/aws-ia/terraform-aws-ec2-image-builder.git 41 | ``` 42 | 43 | #### Step 2: Run Terraform INIT 44 | 45 | Initialize a working directory with configuration files 46 | 47 | ```sh 48 | cd examples/linux/ 49 | 50 | terraform init 51 | ``` 52 | 53 | #### Step 3: Run Terraform PLAN 54 | 55 | Verify the resources created by this execution 56 | 57 | ```sh 58 | export AWS_REGION= # default set to `us-west-2` 59 | terraform plan 60 | ``` 61 | 62 | #### Step 4: Finally, Terraform APPLY 63 | 64 | Create the resources 65 | 66 | ```sh 67 | terraform apply 68 | ``` 69 | 70 | Enter `yes` to apply. 71 | 72 | #### Step 5: Verify the AMI on AWS Console 73 | 74 | Output of Terraform apply should look similar 75 | 76 | ```sh 77 | module.ec2-image-builder.aws_imagebuilder_image.imagebuilder_image[0]: Creation complete after 1h3m56s [id=arn:aws:imagebuilder:ap-southeast-2:XXXXXXXX:image/myfirstpipeline-image-recipe/0.0.2/1] 78 | ``` 79 | 80 | Login to AWS Console, go to the AWS Region where resources are deployed, and go to the location showed on the Terraform output, for example: 81 | 82 | EC2 Image Builder > Images > "myfirstpipeline-image-recipe 0.0.2" > "myfirstpipeline-image-recipe 0.0.2/1" 83 | 84 | You can see the AMI ID on the output resources: 85 | 86 | ![AMI generated by EC2 Image Builder](../../images/outputresources.png) 87 | 88 | ## Cleanup 89 | 90 | To clean up your environment, destroy the Terraform module. 91 | 92 | *NOTE:* Empty the S3 bucket created by this module before executing the `terraform destroy` 93 | 94 | ```sh 95 | terraform destroy -auto-approve 96 | ``` 97 | 98 | ## Requirements 99 | 100 | | Name | Version | 101 | |------|---------| 102 | | [terraform](#requirement\_terraform) | >= 1.4.0 | 103 | | [random](#requirement\_random) | >= 3.0.0, < 4.0.0 | 104 | | [tls](#requirement\_tls) | >= 4.0.0, < 5.0.0 | 105 | 106 | ## Providers 107 | 108 | | Name | Version | 109 | |------|---------| 110 | | [aws](#provider\_aws) | n/a | 111 | | [random](#provider\_random) | >= 3.0.0, < 4.0.0 | 112 | | [tls](#provider\_tls) | >= 4.0.0, < 5.0.0 | 113 | 114 | ## Modules 115 | 116 | | Name | Source | Version | 117 | |------|--------|---------| 118 | | [ec2-image-builder](#module\_ec2-image-builder) | ../.. | n/a | 119 | | [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | 120 | 121 | ## Resources 122 | 123 | | Name | Type | 124 | |------|------| 125 | | [aws_iam_policy.policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | 126 | | [aws_imagebuilder_component.linuxbuild](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_component) | resource | 127 | | [aws_imagebuilder_component.linuxtest](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_component) | resource | 128 | | [aws_key_pair.imagebuilder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/key_pair) | resource | 129 | | [aws_kms_key.aws_imagebuilder_component_kms_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | 130 | | [aws_kms_key.aws_s3_bucket_kms_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | 131 | | [aws_kms_key.aws_ssm_parameter_kms_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | 132 | | [aws_kms_key.imagebuilder_image_recipe_kms_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | 133 | | [aws_s3_bucket.ec2_image_builder_components](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | 134 | | [aws_s3_bucket_policy.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | 135 | | [aws_s3_bucket_public_access_block.block_public_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | 136 | | [aws_s3_bucket_server_side_encryption_configuration.ec2_image_builder_components_encryption](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | 137 | | [aws_s3_bucket_versioning.ec2_image_builder_components_versioning](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | 138 | | [aws_s3_object.linuxbuild](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource | 139 | | [aws_s3_object.linuxtest](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource | 140 | | [aws_s3_object.upload_scripts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource | 141 | | [aws_ssm_parameter.imagebuilder_ssh_private_key_pem](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | 142 | | [aws_ssm_parameter.imagebuilder_ssh_public_key_openssh](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | 143 | | [aws_ssm_parameter.imagebuilder_ssh_public_key_pem](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | 144 | | [random_uuid.random_uuid](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/uuid) | resource | 145 | | [tls_private_key.imagebuilder](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | 146 | | [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | 147 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | 148 | | [aws_iam_policy_document.iam_policy_document](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 149 | 150 | ## Inputs 151 | 152 | No inputs. 153 | 154 | ## Outputs 155 | 156 | No outputs. 157 | -------------------------------------------------------------------------------- /examples/linux/linuxbuild.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | description: 'This document will install all available Windows updates ane run a sample shell hello-world.' 3 | schemaVersion: 1.0 4 | parameters: 5 | - S3BucketName: 6 | type: string 7 | description: S3 Bucket Name where the scripts are located 8 | phases: 9 | - name: build 10 | steps: 11 | - name: CreatingTempFolder 12 | action: CreateFolder 13 | inputs: 14 | - path: /temp 15 | 16 | - name: DownloadScripts 17 | action: S3Download 18 | timeoutSeconds: 60 19 | onFailure: Abort 20 | maxAttempts: 3 21 | inputs: 22 | - source: 's3://{{ S3BucketName }}/scripts/*' 23 | destination: /temp 24 | 25 | - name: RunScript 26 | action: ExecuteBash 27 | timeoutSeconds: 120 28 | onFailure: Abort 29 | maxAttempts: 3 30 | inputs: 31 | commands: 32 | - | 33 | bash /temp/HelloWorld.sh 34 | 35 | - name: InstallLinuxUpdates 36 | action: UpdateOS 37 | 38 | - name: RebootAfterConfigApplied 39 | action: Reboot 40 | inputs: 41 | delaySeconds: 60 42 | -------------------------------------------------------------------------------- /examples/linux/linuxtest.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | description: 'This document will perform the test using shell script' 3 | schemaVersion: 1.0 4 | parameters: 5 | - S3BucketName: 6 | type: string 7 | description: S3 Bucket Name where the scripts are located 8 | phases: 9 | - name: test 10 | steps: 11 | - name: CreatingScriptsFolder 12 | action: CreateFolder 13 | inputs: 14 | - path: /temp 15 | 16 | - name: CreatingTestsFolder 17 | action: CreateFolder 18 | inputs: 19 | - path: /temp/tests 20 | 21 | - name: DownloadScripts 22 | action: S3Download 23 | timeoutSeconds: 60 24 | onFailure: Abort 25 | maxAttempts: 3 26 | inputs: 27 | - source: 's3://{{ S3BucketName }}/scripts/*' 28 | destination: /temp 29 | 30 | - name: DownloadTestScripts 31 | action: S3Download 32 | timeoutSeconds: 60 33 | onFailure: Abort 34 | maxAttempts: 3 35 | inputs: 36 | - source: 's3://{{ S3BucketName }}/scripts/tests/*' 37 | destination: /temp/tests 38 | 39 | - name: RunTests 40 | action: ExecuteBash 41 | timeoutSeconds: 300 42 | onFailure: Abort 43 | maxAttempts: 3 44 | inputs: 45 | commands: 46 | - | 47 | bash /temp/tests/run-tests.sh 48 | -------------------------------------------------------------------------------- /examples/linux/main.tf: -------------------------------------------------------------------------------- 1 | ##################################################################################### 2 | # Terraform module examples are meant to show an _example_ on how to use a module 3 | # per use-case. The code below should not be copied directly but referenced in order 4 | # to build your own root module that invokes this module 5 | ##################################################################################### 6 | 7 | data "aws_caller_identity" "current" {} 8 | data "aws_availability_zones" "available" {} 9 | 10 | locals { 11 | name = "linuxexample" 12 | vpc_cidr = "10.0.0.0/16" 13 | aws_region = "us-west-2" 14 | azs = slice(data.aws_availability_zones.available.names, 0, 1) 15 | build_version = "0.0.1" 16 | test_version = "0.0.1" 17 | build_file_name = "linuxbuild.yaml" 18 | test_file_name = "linuxtest.yaml" 19 | tags = { 20 | #description = "Tags applied to the role and AWS Resources" 21 | created-by = "Terraform" 22 | dataclassification = "internal" 23 | owner = "Test" 24 | } 25 | } 26 | 27 | module "ec2-image-builder" { 28 | # source = "aws-ia/ec2-image-builder/aws" 29 | source = "../.." 30 | name = local.name 31 | aws_region = local.aws_region 32 | vpc_id = module.vpc.vpc_id 33 | subnet_id = module.vpc.private_subnets[0] 34 | source_cidr = [local.vpc_cidr] #["]" 35 | create_security_group = true 36 | instance_types = ["c5.large"] 37 | instance_key_pair = aws_key_pair.imagebuilder.key_name 38 | source_ami_name = "al2023-ami-2023.6.20250203.1-kernel-6.1-x86_64" 39 | ami_name = "Amazon Linux 2023 AMI" 40 | ami_description = "Amazon Linux 2023 AMI provided by AWS" 41 | recipe_version = "0.0.1" 42 | build_component_arn = [aws_imagebuilder_component.linuxbuild.arn] 43 | test_component_arn = [aws_imagebuilder_component.linuxtest.arn] 44 | s3_bucket_name = aws_s3_bucket.ec2_image_builder_components.id 45 | attach_custom_policy = true 46 | custom_policy_arn = aws_iam_policy.policy.arn 47 | platform = "Linux" 48 | imagebuilder_image_recipe_kms_key_arn = aws_kms_key.imagebuilder_image_recipe_kms_key.arn 49 | tags = local.tags 50 | 51 | managed_components = [{ 52 | name = "amazon-cloudwatch-agent-linux", 53 | version = "1.0.1" 54 | }, 55 | { 56 | name = "hello-world-linux", 57 | version = "1.0.0" 58 | }] 59 | 60 | target_account_ids = [] #"" 61 | 62 | #For Unencrypted AMI 63 | ami_regions_kms_key = {} # "" = "" 64 | 65 | #or 66 | #for Encrypt AMI 67 | #ami_regions_kms_key = { 68 | # "" = "" 69 | #} 70 | 71 | } 72 | 73 | resource "aws_kms_key" "imagebuilder_image_recipe_kms_key" { 74 | description = "Imagebuilder Image Recipe KMS key" 75 | enable_key_rotation = true 76 | policy = jsonencode( 77 | { 78 | "Version" : "2012-10-17", 79 | "Id" : "default", 80 | "Statement" : [ 81 | { 82 | "Sid" : "DefaultAllow", 83 | "Effect" : "Allow", 84 | "Principal" : { 85 | "AWS" : "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" 86 | }, 87 | "Action" : "kms:*", 88 | "Resource" : "*" 89 | } 90 | ] 91 | } 92 | ) 93 | } 94 | 95 | resource "aws_s3_object" "upload_scripts" { 96 | for_each = fileset("${path.module}/scripts/", "**/*") 97 | 98 | bucket = aws_s3_bucket.ec2_image_builder_components.id 99 | key = "./scripts/${each.value}" 100 | source = "${path.module}/scripts/${each.value}" 101 | etag = filemd5("${path.module}/scripts/${each.value}") 102 | tags = local.tags 103 | } 104 | 105 | resource "aws_kms_key" "aws_imagebuilder_component_kms_key" { 106 | description = "Imagebuilder Component KMS key" 107 | enable_key_rotation = true 108 | policy = jsonencode( 109 | { 110 | "Version" : "2012-10-17", 111 | "Id" : "default", 112 | "Statement" : [ 113 | { 114 | "Sid" : "DefaultAllow", 115 | "Effect" : "Allow", 116 | "Principal" : { 117 | "AWS" : "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" 118 | }, 119 | "Action" : "kms:*", 120 | "Resource" : "*" 121 | } 122 | ] 123 | } 124 | ) 125 | } 126 | 127 | resource "aws_imagebuilder_component" "linuxbuild" { 128 | 129 | name = "linuxbuild" 130 | version = local.build_version 131 | kms_key_id = aws_kms_key.aws_imagebuilder_component_kms_key.arn 132 | platform = "Linux" 133 | uri = "s3://${aws_s3_bucket.ec2_image_builder_components.id}/${local.build_file_name}" 134 | 135 | lifecycle { 136 | create_before_destroy = true 137 | } 138 | depends_on = [ 139 | aws_s3_object.linuxbuild 140 | ] 141 | tags = local.tags 142 | } 143 | 144 | resource "aws_s3_object" "linuxbuild" { 145 | bucket = aws_s3_bucket.ec2_image_builder_components.id 146 | key = local.build_file_name 147 | source = "${path.module}/${local.build_file_name}" 148 | etag = filemd5("${path.module}/${local.build_file_name}") 149 | tags = local.tags 150 | } 151 | 152 | resource "aws_imagebuilder_component" "linuxtest" { 153 | 154 | name = "linuxtest" 155 | version = local.test_version 156 | kms_key_id = aws_kms_key.aws_imagebuilder_component_kms_key.arn 157 | platform = "Linux" 158 | uri = "s3://${aws_s3_bucket.ec2_image_builder_components.id}/${local.test_file_name}" 159 | 160 | lifecycle { 161 | create_before_destroy = true 162 | } 163 | 164 | depends_on = [ 165 | aws_s3_object.linuxtest 166 | ] 167 | } 168 | 169 | resource "aws_s3_object" "linuxtest" { 170 | bucket = aws_s3_bucket.ec2_image_builder_components.id 171 | key = local.test_file_name 172 | source = "${path.module}/${local.test_file_name}" 173 | etag = filemd5("${path.module}/${local.test_file_name}") 174 | tags = local.tags 175 | } 176 | 177 | resource "aws_iam_policy" "policy" { 178 | 179 | name = "custom_policy_linux" 180 | path = "/" 181 | description = "My custom policy" 182 | policy = data.aws_iam_policy_document.iam_policy_document.json 183 | } 184 | 185 | #tfsec:ignore:aws-iam-no-policy-wildcards 186 | data "aws_iam_policy_document" "iam_policy_document" { 187 | statement { 188 | effect = "Allow" 189 | actions = [ 190 | "s3:GetObject", 191 | "s3:PutObject", 192 | "s3:DeleteObject", 193 | "s3:PutObjectAcl", 194 | ] 195 | resources = [aws_s3_bucket.ec2_image_builder_components.arn, 196 | "${aws_s3_bucket.ec2_image_builder_components.arn}/*"] 197 | } 198 | statement { 199 | effect = "Allow" 200 | actions = [ 201 | "s3:ListBucket" 202 | ] 203 | resources = [aws_s3_bucket.ec2_image_builder_components.arn] 204 | } 205 | 206 | statement { 207 | effect = "Allow" 208 | actions = [ 209 | "kms:GenerateDataKey" 210 | ] 211 | resources = [aws_kms_key.aws_imagebuilder_component_kms_key.arn, 212 | aws_kms_key.aws_s3_bucket_kms_key.arn] 213 | } 214 | } 215 | 216 | resource "aws_s3_bucket_policy" "bucket_policy" { 217 | bucket = aws_s3_bucket.ec2_image_builder_components.id 218 | 219 | policy = jsonencode( 220 | { 221 | "Version" : "2012-10-17", 222 | "Statement" : [ 223 | { 224 | "Effect" : "Allow", 225 | "Principal" : { 226 | "AWS" : "${data.aws_caller_identity.current.account_id}" 227 | }, 228 | "Action" : ["s3:*"], 229 | "Resource" : [ 230 | "${aws_s3_bucket.ec2_image_builder_components.arn}", 231 | "${aws_s3_bucket.ec2_image_builder_components.arn}/*" 232 | ] 233 | }, 234 | { 235 | "Sid" : "Deny non-HTTPS access", 236 | "Effect" : "Deny", 237 | "Principal" : "*", 238 | "Action" : ["s3:*"], 239 | "Resource" : "${aws_s3_bucket.ec2_image_builder_components.arn}/*", 240 | "Condition" : { 241 | "Bool" : { 242 | "aws:SecureTransport" : "false" 243 | } 244 | } 245 | } 246 | ] 247 | } 248 | ) 249 | } 250 | 251 | resource "random_uuid" "random_uuid" { 252 | } 253 | 254 | resource "aws_kms_key" "aws_s3_bucket_kms_key" { 255 | description = "S3 Bucket KMS key" 256 | enable_key_rotation = true 257 | policy = jsonencode( 258 | { 259 | "Version" : "2012-10-17", 260 | "Id" : "default", 261 | "Statement" : [ 262 | { 263 | "Sid" : "DefaultAllow", 264 | "Effect" : "Allow", 265 | "Principal" : { 266 | "AWS" : "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" 267 | }, 268 | "Action" : "kms:*", 269 | "Resource" : "*" 270 | } 271 | ] 272 | } 273 | ) 274 | } 275 | 276 | #tfsec:ignore:aws-s3-enable-bucket-logging 277 | resource "aws_s3_bucket" "ec2_image_builder_components" { 278 | bucket = "${local.name}-components-${random_uuid.random_uuid.result}" 279 | force_destroy = true 280 | #checkov:skip=CKV2_AWS_61:No Lifecycle configuration for this example 281 | #checkov:skip=CKV_AWS_18:No access logging configured for this example 282 | #checkov:skip=CKV2_AWS_62:No event notifications configured for this example 283 | #checkov:skip=CKV_AWS_144:No cross-region replication configured for this example 284 | lifecycle { 285 | ignore_changes = [ 286 | grant 287 | ] 288 | } 289 | tags = local.tags 290 | } 291 | 292 | 293 | #tfsec:ignore:aws-s3-encryption-customer-key 294 | resource "aws_s3_bucket_server_side_encryption_configuration" "ec2_image_builder_components_encryption" { 295 | bucket = aws_s3_bucket.ec2_image_builder_components.id 296 | 297 | rule { 298 | apply_server_side_encryption_by_default { 299 | sse_algorithm = "aws:kms" 300 | kms_master_key_id = aws_kms_key.aws_s3_bucket_kms_key.arn 301 | } 302 | } 303 | } 304 | 305 | resource "aws_s3_bucket_versioning" "ec2_image_builder_components_versioning" { 306 | bucket = aws_s3_bucket.ec2_image_builder_components.id 307 | versioning_configuration { 308 | status = "Enabled" 309 | } 310 | } 311 | 312 | resource "aws_s3_bucket_public_access_block" "block_public_access" { 313 | bucket = aws_s3_bucket.ec2_image_builder_components.id 314 | 315 | block_public_acls = true 316 | block_public_policy = true 317 | restrict_public_buckets = true 318 | ignore_public_acls = true 319 | 320 | } 321 | 322 | # --------------------------------------------------------------------------------------------------------------------- 323 | # EC2 Key Pair 324 | # --------------------------------------------------------------------------------------------------------------------- 325 | resource "aws_kms_key" "aws_ssm_parameter_kms_key" { 326 | description = "SSM Parameter KMS key" 327 | enable_key_rotation = true 328 | policy = jsonencode( 329 | { 330 | "Version" : "2012-10-17", 331 | "Id" : "default", 332 | "Statement" : [ 333 | { 334 | "Sid" : "DefaultAllow", 335 | "Effect" : "Allow", 336 | "Principal" : { 337 | "AWS" : "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" 338 | }, 339 | "Action" : "kms:*", 340 | "Resource" : "*" 341 | } 342 | ] 343 | } 344 | ) 345 | } 346 | 347 | resource "tls_private_key" "imagebuilder" { 348 | algorithm = "RSA" 349 | } 350 | 351 | resource "aws_key_pair" "imagebuilder" { 352 | key_name = "${local.name}-key-pair" 353 | public_key = tls_private_key.imagebuilder.public_key_openssh 354 | } 355 | 356 | resource "aws_ssm_parameter" "imagebuilder_ssh_private_key_pem" { 357 | name = "/${local.name}/imagebuilder_ssh_private_key_pem" 358 | type = "SecureString" 359 | value = tls_private_key.imagebuilder.private_key_pem 360 | key_id = aws_kms_key.aws_ssm_parameter_kms_key.arn 361 | } 362 | 363 | resource "aws_ssm_parameter" "imagebuilder_ssh_public_key_pem" { 364 | name = "/${local.name}/imagebuilder_ssh_public_key_pem" 365 | type = "SecureString" 366 | value = tls_private_key.imagebuilder.public_key_pem 367 | key_id = aws_kms_key.aws_ssm_parameter_kms_key.arn 368 | } 369 | 370 | resource "aws_ssm_parameter" "imagebuilder_ssh_public_key_openssh" { 371 | name = "/${local.name}/imagebuilder_ssh_public_key_openssh" 372 | type = "SecureString" 373 | value = tls_private_key.imagebuilder.public_key_openssh 374 | key_id = aws_kms_key.aws_ssm_parameter_kms_key.arn 375 | } 376 | 377 | ################################################################################ 378 | # Supporting Resources 379 | ################################################################################ 380 | 381 | module "vpc" { 382 | source = "terraform-aws-modules/vpc/aws" 383 | version = "~> 5.0" 384 | 385 | name = local.name 386 | cidr = local.vpc_cidr 387 | 388 | azs = local.azs 389 | private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] 390 | public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] 391 | 392 | enable_nat_gateway = true 393 | single_nat_gateway = true 394 | 395 | tags = local.tags 396 | } 397 | -------------------------------------------------------------------------------- /examples/linux/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.4.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | } 7 | tls = { 8 | source = "hashicorp/tls" 9 | version = ">= 4.0.0, < 5.0.0" 10 | } 11 | random = { 12 | source = "hashicorp/random" 13 | version = ">= 3.0.0, < 4.0.0" 14 | } 15 | } 16 | } 17 | 18 | provider "aws" { 19 | region = local.aws_region 20 | } 21 | -------------------------------------------------------------------------------- /examples/linux/scripts/HelloWorld.sh: -------------------------------------------------------------------------------- 1 | echo 'Hello, World!' -------------------------------------------------------------------------------- /examples/linux/scripts/tests/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FILE_PATH="/temp/HelloWorld.sh" 4 | EXPECTED_CONTENT="echo 'Hello, World!'" 5 | 6 | if [ -f "$FILE_PATH" ]; then 7 | FILE_CONTENT=$(cat "$FILE_PATH") 8 | if [ "$FILE_CONTENT" == "$EXPECTED_CONTENT" ]; then 9 | echo "The file $FILE_PATH contains the expected content." 10 | else 11 | echo "The file $FILE_PATH does not contain the expected content." 12 | fi 13 | else 14 | echo "The file $FILE_PATH does not exist." 15 | fi 16 | -------------------------------------------------------------------------------- /examples/windows/.header.md: -------------------------------------------------------------------------------- 1 | # Using the EC2 Image Builder Terraform Module to create an Windows AMI 2 | 3 | This example will use the EC2 Image Builder Terraform Module to create an Windows 2022 Core AMI. 4 | 5 | The build components are currently all in a single file: win2022build.yaml 6 | The tests components are currently all in a single file: win2022test.yaml 7 | 8 | However the module supports providing multiple files, given the parameters build_component_arn and test_component_arn are of list of strings type. 9 | 10 | The module cover the creation of the core components of the EC2 Image Builder, like Image Pipeline, Image Recipe, Distribution Configuration, 11 | Infrastructure Configuration, etc, but there are components which needs to be created outside of the module and provide as input, which are basically 12 | what it is going to be executed during the build and test process, and also custom IAM Policy with permissions required to interact with 13 | AWS services related to your build and test components. 14 | 15 | This example creates the following resources: 16 | 17 | - S3 Bucket 18 | - EC2 Image Builder Build Component, which executes the hello world powershell script 19 | - EC2 Image Builder Test Component, which executes the pester script which tests the output of the powershell hello world script 20 | - S3 Objects, which uploads the build and Test YAML Files to S3 21 | - IAM Policy with permissions to read/write to the S3 Bucket 22 | - VPC/Subnets/Route Tables/Nat Gateway/etc 23 | 24 | ## How to Deploy 25 | 26 | ### Prerequisites 27 | 28 | Ensure that you have installed the following tools in your Mac or Windows Laptop before start working with this module and run Terraform Plan and Apply 29 | 30 | 1. [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) 31 | 2. [Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli) 32 | 33 | ### Deployment Steps 34 | 35 | #### Step 1: Clone the repo using the command below 36 | 37 | ```sh 38 | git clone https://github.com/aws-ia/terraform-aws-ec2-image-builder.git 39 | ``` 40 | 41 | #### Step 2: Run Terraform INIT 42 | 43 | Initialize a working directory with configuration files 44 | 45 | ```sh 46 | cd examples/windows/ 47 | terraform init 48 | ``` 49 | 50 | #### Step 3: Run Terraform PLAN 51 | 52 | Verify the resources created by this execution 53 | 54 | ```sh 55 | export AWS_REGION= # default set to `us-west-2` 56 | terraform plan 57 | ``` 58 | 59 | #### Step 4: Finally, Terraform APPLY 60 | 61 | Create the resources 62 | 63 | ```sh 64 | terraform apply 65 | ``` 66 | 67 | Enter `yes` to apply. 68 | 69 | #### Step 5: Verify the AMI on AWS Console 70 | 71 | Output of Terraform apply should look similar 72 | 73 | ``` 74 | module.ec2-image-builder.aws_imagebuilder_image.imagebuilder_image[0]: Creation complete after 1h3m56s [id=arn:aws:imagebuilder:ap-southeast-2:XXXXXXXX:image/myfirstpipeline-image-recipe/0.0.2/1] 75 | ``` 76 | 77 | Login to AWS Console, go to the AWS Region where resources are deployed, and go to the location showed on the Terraform output, for example: 78 | 79 | EC2 Image Builder > Images > "myfirstpipeline-image-recipe 0.0.2" > "myfirstpipeline-image-recipe 0.0.2/1" 80 | You can see the AMI ID on the output resources: 81 | 82 | ![AMI generated by EC2 Image Builder](../../images/outputresources.png) 83 | 84 | ## Cleanup 85 | 86 | To clean up your environment, destroy the Terraform module. 87 | 88 | *NOTE:* Empty the S3 bucket created by this module before executing the `terraform destroy` 89 | 90 | ```sh 91 | terraform destroy -auto-approve 92 | ``` 93 | -------------------------------------------------------------------------------- /examples/windows/.terraform-docs.yaml: -------------------------------------------------------------------------------- 1 | formatter: markdown 2 | header-from: .header.md 3 | settings: 4 | anchor: true 5 | color: true 6 | default: true 7 | escape: true 8 | html: true 9 | indent: 2 10 | required: true 11 | sensitive: true 12 | type: true 13 | lockfile: false 14 | 15 | sort: 16 | enabled: true 17 | by: required 18 | 19 | output: 20 | file: README.md 21 | mode: replace 22 | -------------------------------------------------------------------------------- /examples/windows/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Using the EC2 Image Builder Terraform Module to create an Windows AMI 3 | 4 | This example will use the EC2 Image Builder Terraform Module to create an Windows 2022 Core AMI. 5 | 6 | The build components are currently all in a single file: win2022build.yaml 7 | The tests components are currently all in a single file: win2022test.yaml 8 | 9 | However the module supports providing multiple files, given the parameters build\_component\_arn and test\_component\_arn are of list of strings type. 10 | 11 | The module cover the creation of the core components of the EC2 Image Builder, like Image Pipeline, Image Recipe, Distribution Configuration, 12 | Infrastructure Configuration, etc, but there are components which needs to be created outside of the module and provide as input, which are basically 13 | what it is going to be executed during the build and test process, and also custom IAM Policy with permissions required to interact with 14 | AWS services related to your build and test components. 15 | 16 | This example creates the following resources: 17 | 18 | - S3 Bucket 19 | - EC2 Image Builder Build Component, which executes the hello world powershell script 20 | - EC2 Image Builder Test Component, which executes the pester script which tests the output of the powershell hello world script 21 | - S3 Objects, which uploads the build and Test YAML Files to S3 22 | - IAM Policy with permissions to read/write to the S3 Bucket 23 | - VPC/Subnets/Route Tables/Nat Gateway/etc 24 | 25 | ## How to Deploy 26 | 27 | ### Prerequisites 28 | 29 | Ensure that you have installed the following tools in your Mac or Windows Laptop before start working with this module and run Terraform Plan and Apply 30 | 31 | 1. [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) 32 | 2. [Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli) 33 | 34 | ### Deployment Steps 35 | 36 | #### Step 1: Clone the repo using the command below 37 | 38 | ```sh 39 | git clone https://github.com/aws-ia/terraform-aws-ec2-image-builder.git 40 | ``` 41 | 42 | #### Step 2: Run Terraform INIT 43 | 44 | Initialize a working directory with configuration files 45 | 46 | ```sh 47 | cd examples/windows/ 48 | terraform init 49 | ``` 50 | 51 | #### Step 3: Run Terraform PLAN 52 | 53 | Verify the resources created by this execution 54 | 55 | ```sh 56 | export AWS_REGION= # default set to `us-west-2` 57 | terraform plan 58 | ``` 59 | 60 | #### Step 4: Finally, Terraform APPLY 61 | 62 | Create the resources 63 | 64 | ```sh 65 | terraform apply 66 | ``` 67 | 68 | Enter `yes` to apply. 69 | 70 | #### Step 5: Verify the AMI on AWS Console 71 | 72 | Output of Terraform apply should look similar 73 | 74 | ``` 75 | module.ec2-image-builder.aws_imagebuilder_image.imagebuilder_image[0]: Creation complete after 1h3m56s [id=arn:aws:imagebuilder:ap-southeast-2:XXXXXXXX:image/myfirstpipeline-image-recipe/0.0.2/1] 76 | ``` 77 | 78 | Login to AWS Console, go to the AWS Region where resources are deployed, and go to the location showed on the Terraform output, for example: 79 | 80 | EC2 Image Builder > Images > "myfirstpipeline-image-recipe 0.0.2" > "myfirstpipeline-image-recipe 0.0.2/1" 81 | You can see the AMI ID on the output resources: 82 | 83 | ![AMI generated by EC2 Image Builder](../../images/outputresources.png) 84 | 85 | ## Cleanup 86 | 87 | To clean up your environment, destroy the Terraform module. 88 | 89 | *NOTE:* Empty the S3 bucket created by this module before executing the `terraform destroy` 90 | 91 | ```sh 92 | terraform destroy -auto-approve 93 | ``` 94 | 95 | ## Requirements 96 | 97 | | Name | Version | 98 | |------|---------| 99 | | [terraform](#requirement\_terraform) | >= 1.4.0 | 100 | | [aws](#requirement\_aws) | >= 5.0.0 | 101 | | [random](#requirement\_random) | >= 3.0.0, < 4.0.0 | 102 | | [tls](#requirement\_tls) | >= 4.0.0, < 5.0.0 | 103 | 104 | ## Providers 105 | 106 | | Name | Version | 107 | |------|---------| 108 | | [aws](#provider\_aws) | >= 5.0.0 | 109 | | [random](#provider\_random) | >= 3.0.0, < 4.0.0 | 110 | | [tls](#provider\_tls) | >= 4.0.0, < 5.0.0 | 111 | 112 | ## Modules 113 | 114 | | Name | Source | Version | 115 | |------|--------|---------| 116 | | [ec2-image-builder](#module\_ec2-image-builder) | ../.. | n/a | 117 | | [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | 118 | 119 | ## Resources 120 | 121 | | Name | Type | 122 | |------|------| 123 | | [aws_iam_policy.policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | 124 | | [aws_imagebuilder_component.win2022build](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_component) | resource | 125 | | [aws_imagebuilder_component.win2022test](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_component) | resource | 126 | | [aws_key_pair.imagebuilder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/key_pair) | resource | 127 | | [aws_kms_key.aws_imagebuilder_component_kms_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | 128 | | [aws_kms_key.aws_s3_bucket_kms_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | 129 | | [aws_kms_key.aws_ssm_parameter_kms_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | 130 | | [aws_kms_key.imagebuilder_image_recipe_kms_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | 131 | | [aws_s3_bucket.ec2_image_builder_components](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | 132 | | [aws_s3_bucket_policy.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | 133 | | [aws_s3_bucket_public_access_block.block_public_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | 134 | | [aws_s3_bucket_server_side_encryption_configuration.ec2_image_builder_components_encryption](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | 135 | | [aws_s3_bucket_versioning.ec2_image_builder_components_versioning](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | 136 | | [aws_s3_object.upload_scripts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource | 137 | | [aws_s3_object.win2022build](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource | 138 | | [aws_s3_object.win2022test](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource | 139 | | [aws_ssm_parameter.imagebuilder_ssh_private_key_pem](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | 140 | | [aws_ssm_parameter.imagebuilder_ssh_public_key_openssh](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | 141 | | [aws_ssm_parameter.imagebuilder_ssh_public_key_pem](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | 142 | | [random_uuid.random_uuid](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/uuid) | resource | 143 | | [tls_private_key.imagebuilder](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | 144 | | [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | 145 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | 146 | | [aws_iam_policy_document.iam_policy_document](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 147 | 148 | ## Inputs 149 | 150 | No inputs. 151 | 152 | ## Outputs 153 | 154 | No outputs. 155 | -------------------------------------------------------------------------------- /examples/windows/main.tf: -------------------------------------------------------------------------------- 1 | ##################################################################################### 2 | # Terraform module examples are meant to show an _example_ on how to use a module 3 | # per use-case. The code below should not be copied directly but referenced in order 4 | # to build your own root module that invokes this module 5 | ##################################################################################### 6 | 7 | data "aws_caller_identity" "current" {} 8 | data "aws_availability_zones" "available" {} 9 | 10 | locals { 11 | name = "windowsexample" 12 | vpc_cidr = "10.0.0.0/16" 13 | aws_region = "us-west-2" 14 | azs = slice(data.aws_availability_zones.available.names, 0, 1) 15 | build_version = "0.0.1" 16 | test_version = "0.0.1" 17 | build_file_name = "win2022build.yaml" 18 | test_file_name = "win2022test.yaml" 19 | tags = { 20 | #description = "Tags applied to the role and AWS Resources" 21 | created-by = "Terraform" 22 | dataclassification = "internal" 23 | owner = "Test" 24 | } 25 | } 26 | 27 | module "ec2-image-builder" { 28 | # source = "aws-ia/ec2-image-builder/aws" 29 | source = "../.." 30 | name = local.name 31 | aws_region = local.aws_region 32 | vpc_id = module.vpc.vpc_id 33 | subnet_id = module.vpc.private_subnets[0] 34 | source_cidr = [local.vpc_cidr] #["]" 35 | create_security_group = true 36 | instance_types = ["c5.large"] 37 | instance_key_pair = aws_key_pair.imagebuilder.key_name 38 | source_ami_name = "Windows_Server-2022-English-Core-Base-*" 39 | ami_name = "Windows 2022 core AMI" 40 | ami_description = "Windows 2022 core AMI provided by AWS" 41 | recipe_version = "0.0.1" 42 | build_component_arn = [aws_imagebuilder_component.win2022build.arn] 43 | test_component_arn = [aws_imagebuilder_component.win2022test.arn] 44 | s3_bucket_name = aws_s3_bucket.ec2_image_builder_components.id 45 | attach_custom_policy = true 46 | custom_policy_arn = aws_iam_policy.policy.arn 47 | platform = "Windows" 48 | imagebuilder_image_recipe_kms_key_arn = aws_kms_key.imagebuilder_image_recipe_kms_key.arn 49 | tags = local.tags 50 | 51 | managed_components = [{ 52 | name = "powershell-lts-windows", 53 | version = "7.4.0" 54 | }, 55 | { 56 | name = "chocolatey", 57 | version = "1.0.0" 58 | }] 59 | 60 | target_account_ids = [] #"" 61 | 62 | #For Unencrypted AMI 63 | ami_regions_kms_key = {} # "" = "" 64 | 65 | #or 66 | #for Encrypt AMI 67 | #ami_regions_kms_key = { 68 | # "" = "" 69 | #} 70 | 71 | } 72 | 73 | resource "aws_kms_key" "imagebuilder_image_recipe_kms_key" { 74 | description = "Imagebuilder Image Recipe KMS key" 75 | enable_key_rotation = true 76 | policy = < mc 21 | } 22 | owner = "Amazon" 23 | 24 | filter { 25 | name = "platform" 26 | values = [var.platform] 27 | } 28 | 29 | filter { 30 | name = "name" 31 | values = [each.value.name] 32 | } 33 | 34 | filter { 35 | name = "version" 36 | values = [each.value.version] 37 | } 38 | } 39 | 40 | # --------------------------------------------------------------------------------------------------------------------- 41 | # EC2 Security Group 42 | # --------------------------------------------------------------------------------------------------------------------- 43 | resource "aws_security_group" "security_group" { 44 | count = var.create_security_group ? 1 : 0 45 | #checkov:skip=CKV2_AWS_5:Security Group is being attached if var create_security_group is true 46 | name = "${var.name}-sg" 47 | description = "Security Group for for the EC2 Image Builder Build Instances" 48 | vpc_id = data.aws_vpc.selected.id 49 | 50 | tags = var.tags 51 | } 52 | 53 | resource "aws_security_group_rule" "sg_https_ingress" { 54 | count = var.create_security_group ? 1 : 0 55 | type = "ingress" 56 | from_port = 443 57 | to_port = 443 58 | protocol = "tcp" 59 | cidr_blocks = [data.aws_vpc.selected.cidr_block] 60 | security_group_id = aws_security_group.security_group[count.index].id 61 | description = "HTTPS from VPC" 62 | } 63 | 64 | resource "aws_security_group_rule" "sg_rdp_ingress" { 65 | count = var.create_security_group && length(var.source_cidr) > 0 ? 1 : 0 66 | type = "ingress" 67 | from_port = 3389 68 | to_port = 3389 69 | protocol = "tcp" 70 | cidr_blocks = var.source_cidr 71 | security_group_id = aws_security_group.security_group[count.index].id 72 | description = "RDP from the source variable CIDR" 73 | } 74 | 75 | #tfsec:ignore:aws-vpc-no-public-egress-sgr 76 | resource "aws_security_group_rule" "sg_internet_egress" { 77 | count = var.create_security_group ? 1 : 0 78 | type = "egress" 79 | from_port = 0 80 | to_port = 0 81 | protocol = "all" 82 | cidr_blocks = ["0.0.0.0/0"] 83 | security_group_id = aws_security_group.security_group[count.index].id 84 | description = "Access to the internet" 85 | } 86 | 87 | 88 | # --------------------------------------------------------------------------------------------------------------------- 89 | # IAM Role 90 | # --------------------------------------------------------------------------------------------------------------------- 91 | 92 | data "aws_iam_policy_document" "assume" { 93 | statement { 94 | actions = ["sts:AssumeRole"] 95 | 96 | principals { 97 | type = "Service" 98 | identifiers = ["ec2.amazonaws.com"] 99 | } 100 | } 101 | } 102 | 103 | resource "aws_iam_role" "awsserviceroleforimagebuilder" { 104 | assume_role_policy = data.aws_iam_policy_document.assume.json 105 | name = "${var.name}-role" 106 | tags = var.tags 107 | } 108 | 109 | resource "aws_iam_role_policy_attachment" "imagebuilder" { 110 | policy_arn = "arn:aws:iam::aws:policy/EC2InstanceProfileForImageBuilder" 111 | role = aws_iam_role.awsserviceroleforimagebuilder.name 112 | } 113 | resource "aws_iam_role_policy_attachment" "ssm" { 114 | policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" 115 | role = aws_iam_role.awsserviceroleforimagebuilder.name 116 | } 117 | 118 | resource "aws_iam_instance_profile" "iam_instance_profile" { 119 | name = "EC2InstanceProfileImageBuilder-${var.name}" 120 | role = aws_iam_role.awsserviceroleforimagebuilder.name 121 | } 122 | 123 | resource "aws_iam_role_policy_attachment" "custom_policy" { 124 | count = var.attach_custom_policy ? 1 : 0 125 | policy_arn = var.custom_policy_arn 126 | role = aws_iam_role.awsserviceroleforimagebuilder.name 127 | } 128 | 129 | resource "aws_iam_role_policy" "aws_policy" { 130 | name = "${var.name}-aws-access" 131 | role = aws_iam_role.awsserviceroleforimagebuilder.id 132 | #checkov:skip=CKV_AWS_290:The policy must allow * 133 | #checkov:skip=CKV_AWS_355:The policy must allow * 134 | policy = data.aws_iam_policy_document.aws_policy.json 135 | } 136 | 137 | #tfsec:ignore:aws-iam-no-policy-wildcards 138 | data "aws_iam_policy_document" "aws_policy" { 139 | 140 | statement { 141 | effect = "Allow" 142 | actions = ["sts:AssumeRole"] 143 | resources = ["arn:aws:iam::*:role/EC2ImageBuilderDistributionCrossAccountRole"] 144 | } 145 | 146 | } 147 | 148 | # --------------------------------------------------------------------------------------------------------------------- 149 | # EC2 Image Builder Infrastructure Configuration 150 | # --------------------------------------------------------------------------------------------------------------------- 151 | 152 | resource "aws_imagebuilder_infrastructure_configuration" "imagebuilder_infrastructure_configuration" { 153 | count = 1 154 | instance_profile_name = aws_iam_instance_profile.iam_instance_profile.name 155 | instance_types = var.instance_types 156 | key_pair = var.instance_key_pair 157 | 158 | name = "${var.name}-infrastructure-configuration" 159 | security_group_ids = var.create_security_group ? [aws_security_group.security_group[count.index].id] : var.security_group_ids 160 | subnet_id = var.subnet_id 161 | 162 | instance_metadata_options { 163 | http_tokens = "required" 164 | http_put_response_hop_limit = 2 165 | } 166 | 167 | terminate_instance_on_failure = var.terminate_on_failure 168 | resource_tags = var.tags 169 | tags = var.tags 170 | 171 | logging { 172 | s3_logs { 173 | s3_bucket_name = var.s3_bucket_name 174 | s3_key_prefix = "logs" 175 | } 176 | } 177 | 178 | } 179 | 180 | # --------------------------------------------------------------------------------------------------------------------- 181 | # EC2 Image Builder Image 182 | # --------------------------------------------------------------------------------------------------------------------- 183 | resource "aws_imagebuilder_image" "imagebuilder_image" { 184 | count = 1 185 | image_recipe_arn = aws_imagebuilder_image_recipe.imagebuilder_image_recipe.arn 186 | infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.imagebuilder_infrastructure_configuration[count.index].arn 187 | distribution_configuration_arn = try(aws_imagebuilder_distribution_configuration.imagebuilder_distribution_configuration[count.index].arn, null) 188 | 189 | image_tests_configuration { 190 | image_tests_enabled = true 191 | } 192 | tags = var.tags 193 | 194 | timeouts { 195 | create = var.timeout 196 | } 197 | } 198 | 199 | # --------------------------------------------------------------------------------------------------------------------- 200 | # EC2 Image Builder Image Pipeline 201 | # --------------------------------------------------------------------------------------------------------------------- 202 | resource "aws_imagebuilder_image_pipeline" "imagebuilder_image_pipeline" { 203 | count = 1 204 | image_recipe_arn = aws_imagebuilder_image_recipe.imagebuilder_image_recipe.arn 205 | infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.imagebuilder_infrastructure_configuration[count.index].arn 206 | distribution_configuration_arn = try(aws_imagebuilder_distribution_configuration.imagebuilder_distribution_configuration[count.index].arn, null) 207 | dynamic "schedule" { 208 | for_each = try(var.schedule_expression, tomap({})) 209 | content { 210 | schedule_expression = schedule.key 211 | pipeline_execution_start_condition = schedule.value 212 | } 213 | } 214 | name = "${var.name}-pipeline" 215 | tags = var.tags 216 | } 217 | 218 | 219 | # --------------------------------------------------------------------------------------------------------------------- 220 | # EC2 Image Builder Image Recipe 221 | # --------------------------------------------------------------------------------------------------------------------- 222 | 223 | 224 | 225 | resource "aws_imagebuilder_image_recipe" "imagebuilder_image_recipe" { 226 | name = "${var.name}-image-recipe" 227 | parent_image = data.aws_ami.source_ami.id 228 | version = var.recipe_version 229 | 230 | # it seems there is a bug on checkov for check CKV_AWS_200, even supressing it doesn't help, had to add the below block_device_mapping to pass 231 | block_device_mapping { 232 | device_name = "/dev/xvdb" 233 | 234 | ebs { 235 | delete_on_termination = true 236 | volume_size = var.recipe_volume_size 237 | volume_type = var.recipe_volume_type 238 | encrypted = true 239 | kms_key_id = var.imagebuilder_image_recipe_kms_key_arn 240 | } 241 | } 242 | 243 | lifecycle { 244 | create_before_destroy = true 245 | } 246 | 247 | dynamic "component" { 248 | for_each = { 249 | for key, value in data.aws_imagebuilder_components.managed_components : key => value.arns 250 | } 251 | content { 252 | component_arn = tolist(component.value)[0] 253 | } 254 | } 255 | 256 | dynamic "component" { 257 | for_each = var.build_component_arn 258 | content { 259 | component_arn = component.value 260 | parameter { 261 | name = "S3BucketName" 262 | value = var.s3_bucket_name 263 | } 264 | } 265 | } 266 | 267 | dynamic "component" { 268 | for_each = var.test_component_arn 269 | content { 270 | component_arn = component.value 271 | parameter { 272 | name = "S3BucketName" 273 | value = var.s3_bucket_name 274 | } 275 | } 276 | } 277 | 278 | tags = var.tags 279 | 280 | } 281 | 282 | # --------------------------------------------------------------------------------------------------------------------- 283 | # EC2 Image Builder Distribution Configuration 284 | # --------------------------------------------------------------------------------------------------------------------- 285 | resource "aws_imagebuilder_distribution_configuration" "imagebuilder_distribution_configuration" { 286 | count = length(var.ami_regions_kms_key) > 0 ? 1 : 0 #need to add a count due the error "At least 1 "distribution" blocks are required" when ami_regions_kms_key = {} 287 | name = "${var.name}-distribution" 288 | 289 | dynamic "distribution" { 290 | for_each = var.ami_regions_kms_key # for each region we need to have a new distribution setting 291 | content { 292 | region = distribution.key 293 | ami_distribution_configuration { 294 | name = "${var.ami_name}-{{ imagebuilder:buildDate }}" 295 | description = var.ami_description 296 | target_account_ids = var.target_account_ids 297 | launch_permission { 298 | user_ids = var.target_account_ids 299 | } 300 | 301 | ami_tags = var.tags 302 | # AMI Should be encrypted by different KMS key according to the region, however if no KMS Key is provided the image will not be encrypted 303 | kms_key_id = length(distribution.value) > 0 ? distribution.value : null 304 | 305 | } 306 | } 307 | 308 | } 309 | tags = var.tags 310 | } 311 | 312 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "ami" { 2 | value = try(element(tolist(aws_imagebuilder_image.imagebuilder_image[0].output_resources[0].amis), 0).image, "") 3 | description = "AMI created by Terraform" 4 | } -------------------------------------------------------------------------------- /providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.4.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 5.0.0" 8 | } 9 | } 10 | } 11 | 12 | provider "aws" { 13 | region = var.aws_region 14 | } -------------------------------------------------------------------------------- /tests/01_mandatory.tftest.hcl: -------------------------------------------------------------------------------- 1 | run "unit_test" { 2 | command = plan 3 | module { 4 | source = "./examples/windows" 5 | } 6 | } 7 | 8 | run "e2e_test" { 9 | command = apply 10 | module { 11 | source = "./examples/windows" 12 | } 13 | } -------------------------------------------------------------------------------- /tests/02_linux.tftest.hcl: -------------------------------------------------------------------------------- 1 | run "unit_test" { 2 | command = plan 3 | module { 4 | source = "./examples/linux" 5 | } 6 | } 7 | 8 | run "e2e_test" { 9 | command = apply 10 | module { 11 | source = "./examples/linux" 12 | } 13 | } -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "vpc_id" { 2 | type = string 3 | description = "(Required) VPC ID to deploy the EC2 Image Builder Environment." 4 | } 5 | 6 | variable "aws_region" { 7 | type = string 8 | description = "(Required) AWS Region to deploy the resources" 9 | } 10 | 11 | variable "subnet_id" { 12 | type = string 13 | description = "(Required) Subnet ID to deploy the EC2 Image Builder Environment." 14 | } 15 | 16 | variable "name" { 17 | type = string 18 | description = "(Required) Choose a name for the project which will be the prefix for every resource" 19 | } 20 | 21 | variable "ami_name" { 22 | type = string 23 | description = "(Required) Choose a name for the AMI" 24 | } 25 | 26 | variable "source_ami_name" { 27 | type = string 28 | description = "(Required) Source AMI name, e.g: Windows_Server-2022-English-Core-Base-*" 29 | } 30 | 31 | variable "source_ami_owner" { 32 | type = string 33 | description = "(Optional) Owner of the AMI , default: amazon" 34 | default = "amazon" 35 | } 36 | 37 | variable "ami_description" { 38 | type = string 39 | description = "(Required) Choose a description for the AMI" 40 | } 41 | 42 | variable "instance_types" { 43 | type = list(string) 44 | description = <<-EOD 45 | (Optional) Instance type for the EC2 Image Builder Instances. 46 | Will be set by default to c5.large. Please check the AWS Pricing for more information about the instance types. 47 | EOD 48 | default = ["c5.large"] 49 | } 50 | 51 | variable "recipe_version" { 52 | type = string 53 | description = "(Required) The semantic version of the image recipe. This version follows the semantic version syntax. e.g.: 0.0.1" 54 | default = "0.0.1" 55 | } 56 | 57 | variable "s3_bucket_name" { 58 | type = string 59 | description = "(Required) S3 Bucket Name which will store EC2 Image Builder TOE logs and is storing the build/test YAML files" 60 | default = "" 61 | } 62 | 63 | variable "build_component_arn" { 64 | type = list(string) 65 | description = "(Required) List of ARNs for the Build EC2 Image Builder Build Components" 66 | default = [] 67 | } 68 | 69 | variable "test_component_arn" { 70 | type = list(string) 71 | description = "(Required) List of ARNs for the Build EC2 Image Builder Test Components" 72 | default = [] 73 | } 74 | 75 | variable "tags" { 76 | description = "(Optional) A map of resource tags to associate with the resource" 77 | type = map(string) 78 | default = {} 79 | } 80 | 81 | variable "target_account_ids" { 82 | description = "(Optional) A list of target accounts to share the AMI with" 83 | type = list(string) 84 | default = [] 85 | } 86 | 87 | variable "ami_regions_kms_key" { 88 | description = "(Optional) A list of AWS Regions to share the AMI with and also target KMS Key in each region" 89 | type = map(string) 90 | default = {} 91 | } 92 | 93 | variable "source_cidr" { 94 | type = list(string) 95 | description = "(Required) Source CIDR block which will be allowed to RDP or SSH to EC2 Image Builder Instances" 96 | default = [] 97 | } 98 | 99 | variable "attach_custom_policy" { 100 | type = bool 101 | description = "(Required) Attach custom policy to the EC2 Instance Profile, if true, ARN of the custom policy needs to be specified on the variable custom_policy_arn" 102 | default = false 103 | } 104 | 105 | variable "custom_policy_arn" { 106 | type = string 107 | description = "(Optional) ARN of the custom policy to be attached to the EC2 Instance Profile" 108 | default = null 109 | } 110 | 111 | variable "schedule_expression" { 112 | type = list(object({ 113 | pipeline_execution_start_condition = string, 114 | scheduleExpression = string 115 | })) 116 | description = <<-EOD 117 | "(Optional) pipeline_execution_start_condition = The condition configures when the pipeline should trigger a new image build. 118 | Valid Values: EXPRESSION_MATCH_ONLY | EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE 119 | scheduleExpression = The cron expression determines how often EC2 Image Builder evaluates your pipelineExecutionStartCondition. 120 | e.g.: "cron(0 0 * * ? *)" 121 | EOD 122 | default = [] 123 | } 124 | 125 | variable "timeout" { 126 | type = string 127 | description = "(Optional) Number of hours before image time out. Defaults to 2h. " 128 | default = "2h" 129 | } 130 | 131 | variable "managed_components" { 132 | type = list(object({ 133 | name = string, 134 | version = string 135 | })) 136 | description = "(Optional) Specify the name and version of the AWS managed components that are going to be part of the image recipe" 137 | default = [] 138 | } 139 | 140 | variable "platform" { 141 | type = string 142 | description = "(Required) OS: Windows or Linux" 143 | 144 | validation { 145 | condition = contains(["Windows", "Linux"], var.platform) 146 | error_message = "Invalid input, options: \"Windows\", \"Linux\"." 147 | } 148 | } 149 | 150 | 151 | variable "imagebuilder_image_recipe_kms_key_arn" { 152 | default = null 153 | description = "(Required) KMS Key ARN(CMK) for encrypting Imagebuilder Image Recipe Block Device Mapping" 154 | type = string 155 | } 156 | 157 | variable "terminate_on_failure" { 158 | default = true 159 | description = "(Optional) Change to false if you want to connect to a builder for debugging after failure" 160 | type = bool 161 | } 162 | 163 | variable "create_security_group" { 164 | description = "(Optional) Create security group for EC2 Image Builder instances. Please note this security group will be created with default egress rule to 0.0.0.0/0 CIDR Block. In case you want to have a more restrict set of rules, please provide your own security group id on security_group_ids variable" 165 | type = bool 166 | default = true 167 | } 168 | 169 | variable "security_group_ids" { 170 | description = "(Optional) Security group IDs for EC2 Image Builder instances(In case existent Security Group is provided)" 171 | type = list(string) 172 | default = [] 173 | } 174 | 175 | variable "instance_key_pair" { 176 | default = null 177 | description = "(Optional) EC2 key pair to add to the default user on the builder(In case existent EC2 Key Pair is provided)" 178 | type = string 179 | } 180 | 181 | variable "recipe_volume_size" { 182 | default = 100 183 | description = "(Optional) Volume Size of Imagebuilder Image Recipe Block Device Mapping" 184 | type = string 185 | } 186 | 187 | variable "recipe_volume_type" { 188 | default = "gp3" 189 | description = "(Optional) Volume Type of Imagebuilder Image Recipe Block Device Mapping" 190 | type = string 191 | } --------------------------------------------------------------------------------