diff --git a/Makefile b/Makefile index 1c4247f..fec3baf 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ build-lambda: generate test zip -x wwwroot\* -r /build/dist/lambda.zip . tf-bucket: - $(eval BUCKET_NAME=$(APP_NAME)-terraform) + $(eval BUCKET_NAME=$(SYSTEM_PREFIX)$(APP_NAME)-terraform) @aws s3api get-bucket-location --bucket $(BUCKET_NAME) > /dev/null 2>&1; \ if [ "$$?" -ne "0" ]; \ then \ @@ -55,18 +55,20 @@ tf-bucket: tf-init: tf-bucket echo "Initializing terraform" cd /build/terraform && \ - terraform init -input=false -plugin-dir=/usr/local/lib/custom-terraform-plugins + terraform init -input=false -plugin-dir=/usr/local/lib/custom-terraform-plugins -backend-config=backendconfig/$(SYSTEM_PREFIX)backend.tfbackend plan: tf-init build-lambda asset_hash echo "Planning terraform changes" $(eval PLAN=$(shell mktemp)) cd /build/terraform && \ terraform plan -input=false \ - -var 'signature_secret="$(SIGNATURE_SECRET)"' \ - -var 'build_version="$(BUILD_VERSION)"' \ - -var 'appname="$(APP_NAME)"' \ - -var 'domainsuffix="$(DOMAIN_SUFFIX)"' \ - -var 'asset_hash="$(ASSET_HASH)"' \ + -var 'signature_secret=$(SIGNATURE_SECRET)' \ + -var 'build_version=$(BUILD_VERSION)' \ + -var 'appname=$(APP_NAME)' \ + -var 'domainsuffix=$(DOMAIN_SUFFIX)' \ + -var 'asset_hash=$(ASSET_HASH)' \ + -var 'system_prefix=$(SYSTEM_PREFIX)' \ + -var 'tag_prod=$(TAG_PROD)' \ -out=$(PLAN) apply: plan @@ -77,7 +79,7 @@ apply: plan deploy-assets: asset_hash apply echo "Deploying static content to S3" # best practice for immutable content: cache 1 year (vgl https://jakearchibald.com/2016/caching-best-practices/) - aws s3 sync /buildinternal/AwsLambda/Entrypoint/bin/Release/netcoreapp3.1/linux-x64/publish/wwwroot s3://assets.$(APP_NAME)$(DOMAIN_SUFFIX)/$(ASSET_HASH) --exclude "*.html" --cache-control max-age=31536000 + aws s3 sync /buildinternal/AwsLambda/Entrypoint/bin/Release/netcoreapp3.1/linux-x64/publish/wwwroot s3://assets.$(SYSTEM_PREFIX)$(APP_NAME)$(DOMAIN_SUFFIX)/$(ASSET_HASH) --exclude "*.html" --cache-control max-age=31536000 asset_hash: echo "Creating hash for static content to create a cachable path within S3" @@ -101,7 +103,7 @@ rename: destroy: tf-init echo "destroy is disabled. Uncomment in Makefile to enable destroy." - #cd /build/terraform && terraform destroy -var 'signature_secret="$SIGNATURE_SECRET"' -var 'build_version="$build_version"' -var 'appname="$(APP_NAME)"' -var 'domainsuffix="$(DOMAIN_SUFFIX)"' -input=false -force + #cd /build/terraform && terraform destroy -var 'signature_secret=$(SIGNATURE_SECRET)' -var 'build_version=$(build_version)' -var 'appname=$(APP_NAME)' -var 'domainsuffix=$(DOMAIN_SUFFIX)' -var 'system_prefix=$(SYSTEM_PREFIX)' -var 'tag_prod=$(TAG_PROD)' -input=false -force dns: tf-init cd /build/terraform && terraform output -input=false -json | jq "{Domain: .domain.value, Nameserver: .nameserver.value}" > ../dist/dns-entry.json \ No newline at end of file diff --git a/buildcontainer/Dockerfile b/buildcontainer/Dockerfile index e8baeed..511e04d 100644 --- a/buildcontainer/Dockerfile +++ b/buildcontainer/Dockerfile @@ -15,8 +15,8 @@ RUN curl -fsSL https://releases.hashicorp.com/packer/${PACKER_VERSION}/packer_${ unzip packer.zip -d /usr/local/bin && chmod +x /usr/local/bin/packer ; rm packer.zip # terraform -ENV TERRAFORM_VERSION 0.11.13 -ENV TERRAFORM_CHECKSUM 5925cd4d81e7d8f42a0054df2aafd66e2ab7408dbed2bd748f0022cfe592f8d2 +ENV TERRAFORM_VERSION 0.12.26 +ENV TERRAFORM_CHECKSUM 607bc802b1c6c2a5e62cc48640f38aaa64bef1501b46f0ae4829feb51594b257 RUN curl -fsSL https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip -o terraform.zip && \ echo "${TERRAFORM_CHECKSUM} terraform.zip" | sha256sum -c - && \ unzip terraform.zip -d /usr/local/bin && chmod +x /usr/local/bin/terraform ; rm terraform.zip @@ -30,8 +30,8 @@ RUN curl -fsSL https://releases.hashicorp.com/terraform-provider-terraform/${TER # terraform mongodbatlas provider plugin (for mongodbatlas) -ENV TERRAFORM_MONGODBATLAS_PLUGIN_VERSION 0.4.1 -ENV TERRAFORM_MONGODBATLAS_PLUGIN_CHECKSUM 78d6744c9a9f33f1aa710deec416ca9a35d3ab0da4148fa927cc9c6f81199510 +ENV TERRAFORM_MONGODBATLAS_PLUGIN_VERSION 0.5.1 +ENV TERRAFORM_MONGODBATLAS_PLUGIN_CHECKSUM 70166a9f2022f1ff96a1841648a8282a4cfcc38a6dc592df7b9a78a600d3d87c RUN curl -fsSL https://releases.hashicorp.com/terraform-provider-mongodbatlas/${TERRAFORM_MONGODBATLAS_PLUGIN_VERSION}/terraform-provider-mongodbatlas_${TERRAFORM_MONGODBATLAS_PLUGIN_VERSION}_linux_amd64.zip -o terraform_mongodbatlas_plugin.zip && \ echo "${TERRAFORM_MONGODBATLAS_PLUGIN_CHECKSUM} terraform_mongodbatlas_plugin.zip" | sha256sum -c - && \ unzip terraform_mongodbatlas_plugin.zip -d /usr/local/lib/custom-terraform-plugins ; rm terraform_mongodbatlas_plugin.zip @@ -44,8 +44,8 @@ RUN curl -fsSL https://releases.hashicorp.com/terraform-provider-template/${TERR unzip terraform_template_plugin.zip -d /usr/local/lib/custom-terraform-plugins ; rm terraform_template_plugin.zip # terraform aws provider plugin -ENV TERRAFORM_AWS_PLUGIN_VERSION 2.57.0 -ENV TERRAFORM_AWS_PLUGIN_CHECKSUM c58da1769a6b4e01764ad1e3a9357a3f42b56a2fa95c66899270e31836da7529 +ENV TERRAFORM_AWS_PLUGIN_VERSION 2.64.0 +ENV TERRAFORM_AWS_PLUGIN_CHECKSUM faf2b833298d5a958a94963f1b0a5d3501b80725148a7fb81e5535f2ecba9edf RUN curl -fsSL https://releases.hashicorp.com/terraform-provider-aws/${TERRAFORM_AWS_PLUGIN_VERSION}/terraform-provider-aws_${TERRAFORM_AWS_PLUGIN_VERSION}_linux_amd64.zip -o terraform_aws_plugin.zip && \ echo "${TERRAFORM_AWS_PLUGIN_CHECKSUM} terraform_aws_plugin.zip" | sha256sum -c - && \ unzip terraform_aws_plugin.zip -d /usr/local/lib/custom-terraform-plugins ; rm terraform_aws_plugin.zip diff --git a/environment b/environment index f5c4005..411a052 100644 --- a/environment +++ b/environment @@ -1,4 +1,6 @@ AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN -SIGNATURE_SECRET \ No newline at end of file +SIGNATURE_SECRET +SYSTEM_PREFIX +TAG_PROD \ No newline at end of file diff --git a/policies.txt b/policies.txt new file mode 100644 index 0000000..dfd2454 --- /dev/null +++ b/policies.txt @@ -0,0 +1,247 @@ +##################################################################################################### +User: -buildsystem + - Policies: + - TerraformBucketFullAccess + - CloudwatchFullAccess + - ConfigureBudget + - ConfigureMonitoring + - ServiceLambdaBuildplan + - CreateUseDelegateKMS + - DenyPolicy + +##################################################################################################### + +TerraformBucketFullAccess +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "FullAccessToTerraformStateBucket", + "Effect": "Allow", + "Action": "s3:*", + "Resource": "arn:aws:s3:::*terraform*" + } + ] +} + +CloudwatchFullAccess +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Logging", + "Effect": "Allow", + "Action": [ + "logs:*", + "cloudwatch:*" + ], + "Resource": "*" + } + ] +} + +ConfigureBudget +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Budget", + "Effect": "Allow", + "Action": [ + "budgets:ViewBudget", + "budgets:ModifyBudget" + ], + "Resource": "*" + } + ] +} + +ConfigureMonitoring +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "SNSAllow", + "Effect": "Allow", + "Action": [ + "SNS:CreateTopic", + "SNS:SetTopicAttributes", + "SNS:GetTopicAttributes", + "SNS:ListTagsForResource" + ], + "Resource": "*" + } + ] +} + +ServiceLambdaBuildplan +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Cloudfront", + "Effect": "Allow", + "Action": [ + "cloudfront:TagResource", + "cloudfront:UpdateDistribution", + "cloudfront:DeleteDistribution", + "cloudfront:UntagResource", + "cloudfront:CreateDistribution", + "cloudfront:GetDistribution", + "cloudfront:ListTagsForResource" + ], + "Resource": "*" + }, + { + "Sid": "PassAndAttachLambdaRole", + "Effect": "Allow", + "Action": [ + "iam:PassRole", + "iam:ListAttachedRolePolicies", + "iam:ListInstanceProfilesForRole", + "iam:GetRole", + "iam:UpdateAssumeRolePolicy", + "iam:CreateRole", + "iam:DeleteRole", + "iam:CreateServiceLinkedRole" + ], + "Resource": [ + "arn:aws:iam::*:role/service/service-lambda-role", + "arn:aws:iam::*:role/aws-service-role/ops.apigateway.amazonaws.com/AWSServiceRoleForAPIGateway" + ] + }, + { + "Sid": "AllowToAttachPolicysToRole", + "Effect": "Allow", + "Action": [ + "iam:AttachRolePolicy", + "iam:DetachRolePolicy" + ], + "Resource": [ + "arn:aws:iam::*:role/service/service-lambda-role", + "arn:aws:iam::*:role/aws-service-role/ops.apigateway.amazonaws.com/AWSServiceRoleForAPIGateway" + ], + "Condition": { + "ArnEquals": { + "iam:PolicyArn": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + } + } + }, + { + "Sid": "AllowToCreateAndConfigureHostedZoneWithCertificate", + "Effect": "Allow", + "Action": [ + "route53:CreateHostedZone", + "route53:ChangeResourceRecordSets", + "route53:Get*", + "route53:List*", + "acm:RequestCertificate", + "acm:DescribeCertificate", + "acm:ListTagsForCertificate" + ], + "Resource": "*" + }, + { + "Sid": "FullAccessToApiGateway", + "Effect": "Allow", + "Action": "apigateway:*", + "Resource": "arn:aws:apigateway:*::/*" + }, + { + "Sid": "FullAccessToLambdaFunctions", + "Effect": "Allow", + "Action": "lambda:*", + "Resource": [ + "arn:aws:lambda:eu-central-1:*:function:service" + ] + }, + { + "Sid": "FullAccessToAssetsBucket", + "Effect": "Allow", + "Action": "s3:*", + "Resource": "arn:aws:s3:::assets.*.service.d-velop.cloud*" + } + ] +} + +DenyPolicy +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "DenyDataDeletion", + "Effect": "Deny", + "Action": [ + "kms:ScheduleKeyDeletion", + "kms:DisableKey" + ], + "Resource": [ + "*" + ] + }, + { + "Effect": "Deny", + "Action": "kms:CreateKey", + "Resource": "*", + "Condition": { + "Bool": { + "kms:BypassPolicyLockoutSafetyCheck": true + } + } + } + ] +} + + +CreateUseDelegateKMS +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "CreateKmsKeys", + "Effect": "Allow", + "Action": [ + "kms:CreateKey", + "kms:CreateAlias", + "kms:UpdateKeyDescription", + "kms:DescribeKey", + "kms:GetKeyPolicy", + "kms:GetKeyRotationStatus", + "kms:ListResourceTags", + "kms:ListAliases" + ], + "Resource": "*" + }, + { + "Sid": "AllowKMSKeyUsage", + "Effect": "Allow", + "Action": [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ], + "Resource": [ + "*" + ] + }, + { + "Sid": "AllowKMSKeyDelegationToAWSServices", + "Effect": "Allow", + "Action": [ + "kms:CreateGrant", + "kms:ListGrants", + "kms:RevokeGrant" + ], + "Resource": "*", + "Condition": { + "Bool": { + "kms:GrantIsForAWSResource": true + } + } + } + ] +} diff --git a/terraform/backend.tf b/terraform/backend.tf index ae4120b..2c35ce5 100644 --- a/terraform/backend.tf +++ b/terraform/backend.tf @@ -3,11 +3,7 @@ # cf. https://www.terraform.io/docs/backends/types/s3.html für eine Beschreibung des s3 backends terraform { backend "s3" { - # bucket names must be globally unique across all AWS customers - # so we choose a combination of company prefix ('acme') - # and purpose (terraform) and appname (apptemplatego) - bucket = "acme-apptemplatecs-terraform" - key = "state" + key = "state" # variables can't be used region = "eu-central-1" @@ -17,19 +13,19 @@ terraform { data "terraform_remote_state" "app" { backend = "s3" - config { + config = { # bucket names must be globally unique across all AWS customers # so we choose a combination of company prefix ('acme') # and purpose (terraform) and appname (apptemplatego) - bucket = "acme-apptemplatecs-terraform" + bucket = "${var.system_prefix}${var.appname}-terraform" key = "state" - - # variables can't be used - region = "eu-central-1" + region = var.aws_region } - defaults { - source_code_hash = "0" - build_version = "0" + defaults = { + source_code_hash = "0" + build_version = "0" + prod_service_lambda_version = "1" } } + diff --git a/terraform/backendconfig/backend.tfbackend b/terraform/backendconfig/backend.tfbackend new file mode 100644 index 0000000..6b0b244 --- /dev/null +++ b/terraform/backendconfig/backend.tfbackend @@ -0,0 +1 @@ +bucket = "acme-apptemplatecs-terraform" \ No newline at end of file diff --git a/terraform/backendconfig/dev-backend.tfbackend b/terraform/backendconfig/dev-backend.tfbackend new file mode 100644 index 0000000..ecf200c --- /dev/null +++ b/terraform/backendconfig/dev-backend.tfbackend @@ -0,0 +1 @@ +bucket = "dev-acme-apptemplatecs-terraform" \ No newline at end of file diff --git a/terraform/main.tf b/terraform/main.tf index 6cb5828..858c340 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -1,8 +1,8 @@ locals { - assets_bucket_name = "assets.${var.appname}${var.domainsuffix}" + assets_bucket_name = "assets.${var.system_prefix}${var.appname}${var.domainsuffix}" lambda_file = "../dist/lambda.zip" - source_code_hash = "${base64sha256(file("${local.lambda_file}"))}" + source_code_hash = filebase64sha256(local.lambda_file) # Unfortunately there is a bug in terraform which leads to the destruction of existing resources if # the element order of lists and maps changes cf. https://github.com/hashicorp/terraform/issues/16210 @@ -11,27 +11,27 @@ locals { # If you add stages use keys which result in the stage appended at the end of the list! # If you are uncertain use terraform plan to check the changes terraform would make. stages = { - "a_prod" = "$LATEST" + "a_prod" = var.tag_prod == "1" ? "NewVersion" : data.terraform_remote_state.app.outputs.prod_service_lambda_version "b_dev" = "$LATEST" } // to avoid unnecessary lambda function deployments the build version env var is only changed if the lambda function code has been changed - build_version = "${local.source_code_hash != data.terraform_remote_state.app.source_code_hash ? var.build_version : data.terraform_remote_state.app.build_version}" + build_version = local.source_code_hash != data.terraform_remote_state.app.outputs.source_code_hash ? var.build_version : data.terraform_remote_state.app.outputs.build_version } module "serverless_lambda_app" { - source = "modules/serverless_lambda_app" - stages = "${local.stages}" - appname = "${var.appname}" - lambda_file = "${local.lambda_file}" - source_code_hash = "${local.source_code_hash}" + source = "./modules/serverless_lambda_app" + stages = local.stages + appname = var.appname + lambda_file = local.lambda_file + source_code_hash = local.source_code_hash lambda_memory_size = "512" - + # If you change your output file names or namespaces, you will have to edit the next line # ::.:: - lambda_handler = "EntryPoint::Dvelop.Lambda.EntryPoint.LambdaEntryPoint::FunctionHandlerAsync" - lambda_runtime = "dotnetcore3.1" - assets_bucket_name = "${local.assets_bucket_name}" + lambda_handler = "EntryPoint::Dvelop.Lambda.EntryPoint.LambdaEntryPoint::FunctionHandlerAsync" + lambda_runtime = "dotnetcore3.1" + assets_bucket_name = local.assets_bucket_name # Which rights should the lambda function have. # Terraform user must have appropriate rights to attach these policies! @@ -41,14 +41,14 @@ module "serverless_lambda_app" { ] lambda_environment_vars = { - SIGNATURE_SECRET = "${var.signature_secret}" - BUILD_VERSION = "${local.build_version}" - + SIGNATURE_SECRET = var.signature_secret + BUILD_VERSION = local.build_version # change to ASSET_BASE_PATH = "https://${module.asset_cdn.dns_name}/${var.asset_hash}" if asset_cdn is enabled ASSET_BASE_PATH = "https://s3-eu-central-1.amazonaws.com/${local.assets_bucket_name}/${var.asset_hash}" } - aws_region = "${var.aws_region}" + aws_region = var.aws_region + kms_key_id = module.encryption.encryption_key_id } # If you use cloudfront (a CDN) to deliver your assets, you should remember to remove 's3-eu-central-1.amazonaws.com/' from this output. @@ -57,8 +57,6 @@ output "asset_base_path" { value = "https://s3-eu-central-1.amazonaws.com/${local.assets_bucket_name}/${var.asset_hash}" } - - # Uncomment if you want to use cloudfront (a CDN) to deliver your assets OR custom domain names for your API endpoints. # IMPORTANT: # - Both, the cloudfront distribution and a custom domain name für API endpoints require a DNS hosted zone. @@ -66,10 +64,10 @@ output "asset_base_path" { # cf. https://www.terraform.io/docs/providers/aws/d/route53_zone.html /* resource "aws_route53_zone" "hosted_zone" { - name = "${var.appname}${var.domainsuffix}" + name = "${var.system_prefix}${var.appname}${var.domainsuffix}" } output "nameserver" { - value = "${aws_route53_zone.hosted_zone.name_servers}" + value = aws_route53_zone.hosted_zone.name_servers } */ @@ -81,10 +79,10 @@ output "nameserver" { # for a certificate to be validated by AWS. If this is the case just invoke terraform a second time. /* module "asset_cdn" { - source = "modules/cloudfront_distribution" - hosted_zone_id = "${aws_route53_zone.hosted_zone.id}" + source = "./modules/cloudfront_distribution" + hosted_zone_id = aws_route53_zone.hosted_zone.id custom_subdomain_name = "assets" - origin_domain_name = "${module.serverless_lambda_app.assets_bucket_domain_name}" + origin_domain_name = module.serverless_lambda_app.assets_bucket_domain_name } */ @@ -97,10 +95,46 @@ module "asset_cdn" { # for a certificate to be validated by AWS. If this is the case just invoke terraform a second time. /* module "api_custom_domains" { - source = "modules/api_custom_domain" - hosted_zone_id = "${aws_route53_zone.hosted_zone.id}" - aws_api_gateway_rest_api_id = "${module.serverless_lambda_app.aws_api_gateway_rest_api_id}" - aws_api_gateway_rest_api_endpoint_configuration_types = "${module.serverless_lambda_app.aws_api_gateway_rest_api_endpoint_configuration_types}" - stages = "${module.serverless_lambda_app.stages}" + source = "./modules/api_custom_domain" + hosted_zone_id = aws_route53_zone.hosted_zone.id + aws_api_gateway_rest_api_id = module.serverless_lambda_app.aws_api_gateway_rest_api_id + aws_api_gateway_rest_api_endpoint_configuration_types = module.serverless_lambda_app.aws_api_gateway_rest_api_endpoint_configuration_types + stages = module.serverless_lambda_app.stages } */ + +# If you want to be notified in case of budget overrun or Cloudwatch alarms, you need to subscribe to the SNS topic manually via the AWS Console +# cf. https://www.terraform.io/docs/providers/aws/r/sns_topic.html +resource "aws_sns_topic" "monitoring_topic" { + name = "${var.appname}-Monitoring" + display_name = "Monitoring for budget and cloudwatch alarms" +} + +module "budget" { + source = "./modules/budget" + limit_amount = var.budget_amount + appname = var.appname + sns_topic = aws_sns_topic.monitoring_topic.arn +} + +module "monitoring" { + source = "./modules/monitoring" + sns_topic = aws_sns_topic.monitoring_topic.arn + + lambda_function_names = [ + module.serverless_lambda_app.function_name + ] + + api_names = [ + module.serverless_lambda_app.function_name + ] +} + +# cf. https://www.terraform.io/docs/providers/aws/d/caller_identity.html +data "aws_caller_identity" "current" {} + +module "encryption" { + source = "./modules/encryption" + appname = var.appname + aws_account_id = data.aws_caller_identity.current.account_id +} diff --git a/terraform/modules/api_custom_domain/main.tf b/terraform/modules/api_custom_domain/main.tf index d39139c..b6b4cdd 100644 --- a/terraform/modules/api_custom_domain/main.tf +++ b/terraform/modules/api_custom_domain/main.tf @@ -1,16 +1,16 @@ # cf. https://www.terraform.io/docs/providers/aws/d/route53_zone.html data "aws_route53_zone" "hosted_zone" { - zone_id = "${var.hosted_zone_id}" + zone_id = var.hosted_zone_id } locals { // cf. https://github.com/terraform-providers/terraform-provider-aws/issues/241#issuecomment-438744460 - hosted_zone_name = "${replace(data.aws_route53_zone.hosted_zone.name, "/[.]$/", "")}" + hosted_zone_name = replace(data.aws_route53_zone.hosted_zone.name, "/[.]$/", "") } # cf. https://www.terraform.io/docs/providers/aws/r/acm_certificate.html resource "aws_acm_certificate" "cert" { - domain_name = "${local.hosted_zone_name}" + domain_name = local.hosted_zone_name subject_alternative_names = ["*.${local.hosted_zone_name}"] validation_method = "DNS" @@ -21,65 +21,72 @@ resource "aws_acm_certificate" "cert" { # cf. https://www.terraform.io/docs/providers/aws/r/acm_certificate_validation.html resource "aws_acm_certificate_validation" "cert" { - certificate_arn = "${aws_acm_certificate.cert.arn}" + certificate_arn = aws_acm_certificate.cert.arn validation_record_fqdns = [ - "${aws_route53_record.cert_name_validation.fqdn}", - "${aws_route53_record.cert_alt_name_validation.fqdn}", + aws_route53_record.cert_name_validation.fqdn, + aws_route53_record.cert_alt_name_validation.fqdn, ] } # cf. https://www.terraform.io/docs/providers/aws/r/acm_certificate_validation.html resource "aws_route53_record" "cert_name_validation" { allow_overwrite = true - name = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_name}" - type = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_type}" - zone_id = "${var.hosted_zone_id}" - records = ["${aws_acm_certificate.cert.domain_validation_options.0.resource_record_value}"] - ttl = 60 + name = aws_acm_certificate.cert.domain_validation_options[0].resource_record_name + type = aws_acm_certificate.cert.domain_validation_options[0].resource_record_type + zone_id = var.hosted_zone_id + records = [aws_acm_certificate.cert.domain_validation_options[0].resource_record_value] + ttl = 60 } # cf. https://www.terraform.io/docs/providers/aws/r/acm_certificate_validation.html resource "aws_route53_record" "cert_alt_name_validation" { allow_overwrite = true - name = "${aws_acm_certificate.cert.domain_validation_options.1.resource_record_name}" - type = "${aws_acm_certificate.cert.domain_validation_options.1.resource_record_type}" - zone_id = "${var.hosted_zone_id}" - records = ["${aws_acm_certificate.cert.domain_validation_options.1.resource_record_value}"] - ttl = 60 + name = aws_acm_certificate.cert.domain_validation_options[1].resource_record_name + type = aws_acm_certificate.cert.domain_validation_options[1].resource_record_type + zone_id = var.hosted_zone_id + records = [aws_acm_certificate.cert.domain_validation_options[1].resource_record_value] + ttl = 60 } # cf. https://www.terraform.io/docs/providers/aws/r/api_gateway_domain_name.html resource "aws_api_gateway_domain_name" "stage" { - count = "${length(var.stages)}" + count = length(var.stages) # By convention the 'prod' stage is mapped to the name of the provided hosted zone without the prefix 'prod' - domain_name = "${var.stages[count.index] != "prod" ? format("%s.", var.stages[count.index]) : "" }${local.hosted_zone_name}" - regional_certificate_arn = "${aws_acm_certificate_validation.cert.certificate_arn}" + domain_name = "${var.stages[count.index] != "prod" ? format("%s.", var.stages[count.index]) : ""}${local.hosted_zone_name}" + regional_certificate_arn = aws_acm_certificate_validation.cert.certificate_arn endpoint_configuration { - types = "${var.aws_api_gateway_rest_api_endpoint_configuration_types}" + types = var.aws_api_gateway_rest_api_endpoint_configuration_types } } # cf. https://www.terraform.io/docs/providers/aws/r/api_gateway_base_path_mapping.html resource "aws_api_gateway_base_path_mapping" "stage" { - count = "${length(var.stages)}" - api_id = "${var.aws_api_gateway_rest_api_id}" - stage_name = "${var.stages[count.index]}" - domain_name = "${element(aws_api_gateway_domain_name.stage.*.domain_name,count.index)}" + count = length(var.stages) + api_id = var.aws_api_gateway_rest_api_id + stage_name = var.stages[count.index] + domain_name = element(aws_api_gateway_domain_name.stage.*.domain_name, count.index) } # cf. https://www.terraform.io/docs/providers/aws/r/api_gateway_domain_name.html resource "aws_route53_record" "stage" { - count = "${length(var.stages)}" - zone_id = "${var.hosted_zone_id}" - name = "${element(aws_api_gateway_domain_name.stage.*.domain_name,count.index)}" + count = length(var.stages) + zone_id = var.hosted_zone_id + name = element(aws_api_gateway_domain_name.stage.*.domain_name, count.index) type = "A" alias { - name = "${element(aws_api_gateway_domain_name.stage.*.regional_domain_name,count.index)}" - zone_id = "${element(aws_api_gateway_domain_name.stage.*.regional_zone_id,count.index)}" + name = element( + aws_api_gateway_domain_name.stage.*.regional_domain_name, + count.index, + ) + zone_id = element( + aws_api_gateway_domain_name.stage.*.regional_zone_id, + count.index, + ) evaluate_target_health = false } } + diff --git a/terraform/modules/api_custom_domain/variables.tf b/terraform/modules/api_custom_domain/variables.tf index 8a0159b..a8ccc57 100644 --- a/terraform/modules/api_custom_domain/variables.tf +++ b/terraform/modules/api_custom_domain/variables.tf @@ -7,11 +7,12 @@ variable "aws_api_gateway_rest_api_id" { } variable "aws_api_gateway_rest_api_endpoint_configuration_types" { - type = "list" + type = list(string) description = "A list of endpoint types. cf. https://www.terraform.io/docs/providers/aws/r/api_gateway_domain_name.html#endpoint_configuration-1" } variable "stages" { - type = "list" + type = list(string) description = "Stages for which custom domain names should be created. By convention the 'prod' stage is mapped to the name of the provided hosted zone without the prefix 'prod'" -} \ No newline at end of file +} + diff --git a/terraform/modules/budget/main.tf b/terraform/modules/budget/main.tf new file mode 100644 index 0000000..6b9d941 --- /dev/null +++ b/terraform/modules/budget/main.tf @@ -0,0 +1,26 @@ +# cf. https://www.terraform.io/docs/providers/aws/r/budgets_budget.html +resource "aws_budgets_budget" "budget" { + name = "budget-monthly-${var.appname}" + budget_type = "COST" + limit_amount = var.limit_amount + limit_unit = "USD" + time_period_end = "2087-06-15_00:00" + time_period_start = "2020-06-01_00:00" + time_unit = "MONTHLY" + + notification { + comparison_operator = "GREATER_THAN" + threshold = 100 + threshold_type = "PERCENTAGE" + notification_type = "FORECASTED" + subscriber_sns_topic_arns = [var.sns_topic] + } + + notification { + comparison_operator = "GREATER_THAN" + threshold = 100 + threshold_type = "PERCENTAGE" + notification_type = "ACTUAL" + subscriber_sns_topic_arns = [var.sns_topic] + } +} diff --git a/terraform/modules/budget/variables.tf b/terraform/modules/budget/variables.tf new file mode 100644 index 0000000..a77e6ae --- /dev/null +++ b/terraform/modules/budget/variables.tf @@ -0,0 +1,11 @@ +variable "limit_amount" { + description = "The amount of cost or usage being measured for a budget." +} + +variable "appname" { + description = "appname without app suffix e.g. pdf, dms, inbound." +} + +variable "sns_topic" { + description = "SNS topic arn to notifiy" +} diff --git a/terraform/modules/cloudfront_distribution/main.tf b/terraform/modules/cloudfront_distribution/main.tf index 325413d..d53cd9e 100644 --- a/terraform/modules/cloudfront_distribution/main.tf +++ b/terraform/modules/cloudfront_distribution/main.tf @@ -8,20 +8,20 @@ provider "aws" { # cf. https://www.terraform.io/docs/providers/aws/d/route53_zone.html data "aws_route53_zone" "hosted_zone" { - zone_id = "${var.hosted_zone_id}" + zone_id = var.hosted_zone_id } locals { // cf. https://github.com/terraform-providers/terraform-provider-aws/issues/241#issuecomment-438744460 - hosted_zone_name = "${replace(data.aws_route53_zone.hosted_zone.name, "/[.]$/", "")}" + hosted_zone_name = replace(data.aws_route53_zone.hosted_zone.name, "/[.]$/", "") } # cf. https://www.terraform.io/docs/providers/aws/r/acm_certificate.html resource "aws_acm_certificate" "cert" { - domain_name = "${local.hosted_zone_name}" + domain_name = local.hosted_zone_name subject_alternative_names = ["*.${local.hosted_zone_name}"] validation_method = "DNS" - provider = "aws.virginia" + provider = aws.virginia lifecycle { create_before_destroy = true @@ -30,41 +30,41 @@ resource "aws_acm_certificate" "cert" { # cf. https://www.terraform.io/docs/providers/aws/r/acm_certificate_validation.html resource "aws_acm_certificate_validation" "cert" { - certificate_arn = "${aws_acm_certificate.cert.arn}" + certificate_arn = aws_acm_certificate.cert.arn validation_record_fqdns = [ - "${aws_route53_record.cert_name_validation.fqdn}", - "${aws_route53_record.cert_alt_name_validation.fqdn}", + aws_route53_record.cert_name_validation.fqdn, + aws_route53_record.cert_alt_name_validation.fqdn, ] - provider = "aws.virginia" + provider = aws.virginia } # cf. https://www.terraform.io/docs/providers/aws/r/acm_certificate_validation.html resource "aws_route53_record" "cert_name_validation" { allow_overwrite = true - name = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_name}" - type = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_type}" - zone_id = "${var.hosted_zone_id}" - records = ["${aws_acm_certificate.cert.domain_validation_options.0.resource_record_value}"] - ttl = 60 + name = aws_acm_certificate.cert.domain_validation_options[0].resource_record_name + type = aws_acm_certificate.cert.domain_validation_options[0].resource_record_type + zone_id = var.hosted_zone_id + records = [aws_acm_certificate.cert.domain_validation_options[0].resource_record_value] + ttl = 60 } # cf. https://www.terraform.io/docs/providers/aws/r/acm_certificate_validation.html resource "aws_route53_record" "cert_alt_name_validation" { allow_overwrite = true - name = "${aws_acm_certificate.cert.domain_validation_options.1.resource_record_name}" - type = "${aws_acm_certificate.cert.domain_validation_options.1.resource_record_type}" - zone_id = "${var.hosted_zone_id}" - records = ["${aws_acm_certificate.cert.domain_validation_options.1.resource_record_value}"] - ttl = 60 + name = aws_acm_certificate.cert.domain_validation_options[1].resource_record_name + type = aws_acm_certificate.cert.domain_validation_options[1].resource_record_type + zone_id = var.hosted_zone_id + records = [aws_acm_certificate.cert.domain_validation_options[1].resource_record_value] + ttl = 60 } # cf. https://www.terraform.io/docs/providers/aws/r/cloudfront_distribution.html resource "aws_cloudfront_distribution" "dist" { origin { - origin_id = "${sha256(var.origin_domain_name)}" - domain_name = "${var.origin_domain_name}" + origin_id = sha256(var.origin_domain_name) + domain_name = var.origin_domain_name } enabled = true @@ -78,7 +78,7 @@ resource "aws_cloudfront_distribution" "dist" { allowed_methods = ["GET", "HEAD"] cached_methods = ["GET", "HEAD"] compress = true - target_origin_id = "${sha256(var.origin_domain_name)}" + target_origin_id = sha256(var.origin_domain_name) forwarded_values { query_string = true @@ -104,7 +104,7 @@ resource "aws_cloudfront_distribution" "dist" { viewer_certificate { minimum_protocol_version = "TLSv1.2_2018" ssl_support_method = "sni-only" - acm_certificate_arn = "${aws_acm_certificate_validation.cert.certificate_arn}" + acm_certificate_arn = aws_acm_certificate_validation.cert.certificate_arn } restrictions { @@ -113,20 +113,21 @@ resource "aws_cloudfront_distribution" "dist" { } } - tags { + tags = { Name = "dist for ${var.origin_domain_name}" Created_By = "Terraform - do not modify in AWS Management Console" } } resource "aws_route53_record" "dist" { - zone_id = "${var.hosted_zone_id}" + zone_id = var.hosted_zone_id name = "${var.custom_subdomain_name}.${local.hosted_zone_name}" type = "A" alias { - name = "${aws_cloudfront_distribution.dist.domain_name}" - zone_id = "${aws_cloudfront_distribution.dist.hosted_zone_id}" + name = aws_cloudfront_distribution.dist.domain_name + zone_id = aws_cloudfront_distribution.dist.hosted_zone_id evaluate_target_health = false } } + diff --git a/terraform/modules/cloudfront_distribution/outputs.tf b/terraform/modules/cloudfront_distribution/outputs.tf index 9fc571b..50f109e 100644 --- a/terraform/modules/cloudfront_distribution/outputs.tf +++ b/terraform/modules/cloudfront_distribution/outputs.tf @@ -1,3 +1,4 @@ output "dns_name" { - value = "${aws_route53_record.dist.name}" -} \ No newline at end of file + value = aws_route53_record.dist.name +} + diff --git a/terraform/modules/cloudfront_distribution/variables.tf b/terraform/modules/cloudfront_distribution/variables.tf index 4c4739f..6ec8b38 100644 --- a/terraform/modules/cloudfront_distribution/variables.tf +++ b/terraform/modules/cloudfront_distribution/variables.tf @@ -9,3 +9,4 @@ variable "custom_subdomain_name" { variable "hosted_zone_id" { description = "id of the DNS hosted zone" } + diff --git a/terraform/modules/encryption/main.tf b/terraform/modules/encryption/main.tf new file mode 100644 index 0000000..94e50d5 --- /dev/null +++ b/terraform/modules/encryption/main.tf @@ -0,0 +1,50 @@ +# cf. https://www.terraform.io/docs/providers/aws/r/kms_key.html +resource "aws_kms_key" "encryption_key" { + description = "Encryption kms key" + deletion_window_in_days = 30 + + policy = <