diff --git a/.tool-versions b/.tool-versions index 32db55a4..6211f549 100644 --- a/.tool-versions +++ b/.tool-versions @@ -2,7 +2,7 @@ terraform 1.7.0 pre-commit 3.6.0 - +tfsec 1.28.6 # ============================================================================== # The section below is reserved for Docker image versions. diff --git a/infrastructure/images/.gitkeep b/infrastructure/images/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/infrastructure/modules/.gitkeep b/infrastructure/modules/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/infrastructure/terraform/.gitignore b/infrastructure/terraform/.gitignore new file mode 100644 index 00000000..f0d9138a --- /dev/null +++ b/infrastructure/terraform/.gitignore @@ -0,0 +1,60 @@ +### Terraform ### + +# Transient backends +components/**/backend_tfscaffold.tf + +# Compiled files +**/*.tfstate +**/*.tfplan +**/*.tfstate.backup +**/.terraform +**/.terraform.lock.hcl +**/.terraform/* +**/build/* +**/work/* +**/*tfstate.lock.info + +# Scaffold Plugin Cache +plugin-cache/* + +# PyCache +**/__pycache__ + +### OSX ### +**/.DS_Store +**/.AppleDouble +**/.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +*.swp +.nyc_output + +# VS Code +.vscode + +# IntelliJ Idea +.idea +**/*.iml + +# js +node_modules diff --git a/infrastructure/terraform/components/acct/.tool-versions b/infrastructure/terraform/components/acct/.tool-versions new file mode 100644 index 00000000..3874604d --- /dev/null +++ b/infrastructure/terraform/components/acct/.tool-versions @@ -0,0 +1 @@ +terraform 1.9.2 diff --git a/infrastructure/terraform/components/acct/cloudwatch_log_group_route53_query_log.tf b/infrastructure/terraform/components/acct/cloudwatch_log_group_route53_query_log.tf new file mode 100644 index 00000000..e30e2087 --- /dev/null +++ b/infrastructure/terraform/components/acct/cloudwatch_log_group_route53_query_log.tf @@ -0,0 +1,37 @@ +resource "aws_cloudwatch_log_group" "aws_route53_query_log" { + provider = aws.us-east-1 # Route53 query logging must be in us-east-1 https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_query_log + + name = "/aws/route53/${local.csi}" + retention_in_days = var.log_retention_in_days +} + +resource "aws_cloudwatch_log_resource_policy" "route53_query_logging_policy" { + provider = aws.us-east-1 # Route53 query logging must be in us-east-1 https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_query_log + + policy_document = data.aws_iam_policy_document.route53_logs.json + policy_name = "${local.csi}-route53-query-logging-policy" +} + +data "aws_iam_policy_document" "route53_logs" { + statement { + effect = "Allow" + + principals { + type = "Service" + + identifiers = [ + "route53.amazonaws.com" + ] + } + + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + + resources = [ + aws_cloudwatch_log_group.aws_route53_query_log.arn, + "${aws_cloudwatch_log_group.aws_route53_query_log.arn}:*" + ] + } +} diff --git a/infrastructure/terraform/components/acct/locals_tfscaffold.tf b/infrastructure/terraform/components/acct/locals_tfscaffold.tf new file mode 100644 index 00000000..b7cf3217 --- /dev/null +++ b/infrastructure/terraform/components/acct/locals_tfscaffold.tf @@ -0,0 +1,44 @@ +locals { + terraform_state_bucket = format( + "%s-tfscaffold-%s-%s", + var.project, + var.aws_account_id, + var.region, + ) + + csi = replace( + format( + "%s-%s-%s", + var.project, + var.environment, + var.component, + ), + "_", + "", + ) + + # CSI for use in resources with a global namespace, i.e. S3 Buckets + csi_global = replace( + format( + "%s-%s-%s-%s-%s", + var.project, + var.aws_account_id, + var.region, + var.environment, + var.component, + ), + "_", + "", + ) + + default_tags = merge( + var.default_tags, + { + Project = var.project + Environment = var.environment + Component = var.component + Group = var.group + Name = local.csi + }, + ) +} diff --git a/infrastructure/terraform/components/acct/outputs.tf b/infrastructure/terraform/components/acct/outputs.tf new file mode 100644 index 00000000..0e910212 --- /dev/null +++ b/infrastructure/terraform/components/acct/outputs.tf @@ -0,0 +1,11 @@ +output "dns_zone" { + value = { + id = aws_route53_zone.main.id + name = aws_route53_zone.main.name + nameservers = aws_route53_zone.main.name_servers + } +} + +output "github_pat_ssm_param_name" { + value = aws_ssm_parameter.github_pat.name +} diff --git a/infrastructure/terraform/components/acct/provider_aws.tf b/infrastructure/terraform/components/acct/provider_aws.tf new file mode 100644 index 00000000..d694811e --- /dev/null +++ b/infrastructure/terraform/components/acct/provider_aws.tf @@ -0,0 +1,24 @@ +provider "aws" { + region = var.region + + allowed_account_ids = [ + var.aws_account_id, + ] + + default_tags { + tags = local.default_tags + } +} + +provider "aws" { + alias = "us-east-1" + region = "us-east-1" + + default_tags { + tags = local.default_tags + } + + allowed_account_ids = [ + var.aws_account_id, + ] +} diff --git a/infrastructure/terraform/components/acct/route53_delegation_set.tf b/infrastructure/terraform/components/acct/route53_delegation_set.tf new file mode 100644 index 00000000..b1a35d79 --- /dev/null +++ b/infrastructure/terraform/components/acct/route53_delegation_set.tf @@ -0,0 +1,3 @@ +resource "aws_route53_delegation_set" "main" { + reference_name = "iam.${var.root_domain_name}" +} diff --git a/infrastructure/terraform/components/acct/route53_query_log.tf b/infrastructure/terraform/components/acct/route53_query_log.tf new file mode 100644 index 00000000..305ebb44 --- /dev/null +++ b/infrastructure/terraform/components/acct/route53_query_log.tf @@ -0,0 +1,9 @@ +resource "aws_route53_query_log" "main" { + zone_id = aws_route53_zone.main.zone_id + + cloudwatch_log_group_arn = aws_cloudwatch_log_group.aws_route53_query_log.arn + + depends_on = [ + aws_cloudwatch_log_resource_policy.route53_query_logging_policy + ] +} diff --git a/infrastructure/terraform/components/acct/route53_zone.tf b/infrastructure/terraform/components/acct/route53_zone.tf new file mode 100644 index 00000000..1ffaf689 --- /dev/null +++ b/infrastructure/terraform/components/acct/route53_zone.tf @@ -0,0 +1,5 @@ +resource "aws_route53_zone" "main" { + name = "iam.${var.root_domain_name}" + + delegation_set_id = aws_route53_delegation_set.main.id +} diff --git a/infrastructure/terraform/components/acct/ssm_parameter_github_pat.tf b/infrastructure/terraform/components/acct/ssm_parameter_github_pat.tf new file mode 100644 index 00000000..059ab78f --- /dev/null +++ b/infrastructure/terraform/components/acct/ssm_parameter_github_pat.tf @@ -0,0 +1,16 @@ +resource "aws_ssm_parameter" "github_pat" { + name = "/${local.csi}/github_pat" + description = "A GitHub PAT token for settings up AWS Amplify. This is only used at initial setup of the service" + type = "SecureString" + value = try(var.initial_cli_secrets_provision_override.github_pat, "UNSET") + + lifecycle { + ignore_changes = [value] + } +} + +# This can be set at provision time like: +# PARAM_OBJECT=$(jq -n \ +# --arg github_pat "github_pat_123abc" \ +# '{github_pat:$github_pat}' | jq -R) +# .bin/terraform .. -a apply -- -var="initial_cli_secrets_provision_override=${PARAM_OBJECT}" diff --git a/infrastructure/terraform/components/acct/variables.tf b/infrastructure/terraform/components/acct/variables.tf new file mode 100644 index 00000000..f6b1f627 --- /dev/null +++ b/infrastructure/terraform/components/acct/variables.tf @@ -0,0 +1,72 @@ +## +# Basic Required Variables for tfscaffold Components +## + +variable "project" { + type = string + description = "The name of the tfscaffold project" +} + +variable "environment" { + type = string + description = "The name of the tfscaffold environment" +} + +variable "aws_account_id" { + type = string + description = "The AWS Account ID (numeric)" +} + +variable "region" { + type = string + description = "The AWS Region" +} + +variable "group" { + type = string + description = "The group variables are being inherited from (often synonmous with account short-name)" +} + +## +# tfscaffold variables specific to this component +## + +# This is the only primary variable to have its value defined as +# a default within its declaration in this file, because the variables +# purpose is as an identifier unique to this component, rather +# then to the environment from where all other variables come. +variable "component" { + type = string + description = "The variable encapsulating the name of this component" + default = "acct" +} + +variable "default_tags" { + type = map(string) + description = "A map of default tags to apply to all taggable resources within the component" + default = {} +} + +## +# Variables specific to the "dnsroot"component +## + +variable "log_retention_in_days" { + type = number + description = "The retention period in days for the Cloudwatch Logs events to be retained, default of 0 is indefinite" + default = 0 +} + +variable "root_domain_name" { + type = string + description = "The service's root DNS root nameespace, like nonprod.nhsnotify.national.nhs.uk" + default = "nonprod.nhsnotify.national.nhs.uk" +} + +variable "initial_cli_secrets_provision_override" { + type = map(string) + description = "A map of default value to intialise SSM secret values with. Only useful for initial setup of the account due to lifecycle rules." + default = {} + # Usage like: + # ... -a apply -- -var initial_cli_secrets_provision_override={\"github_pat\":\"l0ngstr1ng"} +} diff --git a/infrastructure/terraform/components/acct/versions.tf b/infrastructure/terraform/components/acct/versions.tf new file mode 100644 index 00000000..5fba18d2 --- /dev/null +++ b/infrastructure/terraform/components/acct/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.50" + } + } + + required_version = ">= 1.9.0" +} diff --git a/infrastructure/terraform/components/branch/.tool-versions b/infrastructure/terraform/components/branch/.tool-versions new file mode 100644 index 00000000..3874604d --- /dev/null +++ b/infrastructure/terraform/components/branch/.tool-versions @@ -0,0 +1 @@ +terraform 1.9.2 diff --git a/infrastructure/terraform/components/branch/locals_remote_state.tf b/infrastructure/terraform/components/branch/locals_remote_state.tf new file mode 100644 index 00000000..bc450717 --- /dev/null +++ b/infrastructure/terraform/components/branch/locals_remote_state.tf @@ -0,0 +1,61 @@ +locals { + bootstrap = data.terraform_remote_state.bootstrap.outputs + acct = data.terraform_remote_state.acct.outputs + iam = data.terraform_remote_state.iam.outputs +} + +data "terraform_remote_state" "bootstrap" { + backend = "s3" + + config = { + bucket = local.terraform_state_bucket + + key = format( + "%s/%s/%s/%s/bootstrap.tfstate", + var.project, + var.aws_account_id, + "eu-west-2", + "bootstrap" + ) + + region = "eu-west-2" + } +} + +data "terraform_remote_state" "acct" { + backend = "s3" + + config = { + bucket = local.terraform_state_bucket + + key = format( + "%s/%s/%s/%s/acct.tfstate", + var.project, + var.aws_account_id, + "eu-west-2", + "main" + ) + + region = "eu-west-2" + } +} + +data "terraform_remote_state" "iam" { + backend = "s3" + + config = { + bucket = local.terraform_state_bucket + + key = format( + "%s/%s/%s/%s/iam.tfstate", + var.project, + var.aws_account_id, + "eu-west-2", + var.parent_amplify_environment, + ) + + region = "eu-west-2" + } +} + + diff --git a/infrastructure/terraform/components/branch/locals_tfscaffold.tf b/infrastructure/terraform/components/branch/locals_tfscaffold.tf new file mode 100644 index 00000000..b7cf3217 --- /dev/null +++ b/infrastructure/terraform/components/branch/locals_tfscaffold.tf @@ -0,0 +1,44 @@ +locals { + terraform_state_bucket = format( + "%s-tfscaffold-%s-%s", + var.project, + var.aws_account_id, + var.region, + ) + + csi = replace( + format( + "%s-%s-%s", + var.project, + var.environment, + var.component, + ), + "_", + "", + ) + + # CSI for use in resources with a global namespace, i.e. S3 Buckets + csi_global = replace( + format( + "%s-%s-%s-%s-%s", + var.project, + var.aws_account_id, + var.region, + var.environment, + var.component, + ), + "_", + "", + ) + + default_tags = merge( + var.default_tags, + { + Project = var.project + Environment = var.environment + Component = var.component + Group = var.group + Name = local.csi + }, + ) +} diff --git a/infrastructure/terraform/components/branch/module_amplify_branch.tf b/infrastructure/terraform/components/branch/module_amplify_branch.tf new file mode 100644 index 00000000..70aeea89 --- /dev/null +++ b/infrastructure/terraform/components/branch/module_amplify_branch.tf @@ -0,0 +1,18 @@ +module "amplify_branch" { + source = "../../modules/amp_branch" + + name = lower(substr(join("", regexall("[a-zA-Z0-9-]+", var.branch_name)), 0, 25)) + aws_account_id = var.aws_account_id + component = var.component + environment = var.environment + project = var.project + region = var.region + group = var.group + + cognito_user_pool_client_id = local.iam.cognito_user_pool["id"] + cognito_user_pool_identity_provider_names = local.iam.cognito_user_pool["identity_providers"] + amplify_app_id = local.iam.amplify["id"] + branch = var.branch_name + domain_name = local.acct.dns_zone["name"] + subdomain = var.environment +} diff --git a/infrastructure/terraform/components/branch/provider_aws.tf b/infrastructure/terraform/components/branch/provider_aws.tf new file mode 100644 index 00000000..d694811e --- /dev/null +++ b/infrastructure/terraform/components/branch/provider_aws.tf @@ -0,0 +1,24 @@ +provider "aws" { + region = var.region + + allowed_account_ids = [ + var.aws_account_id, + ] + + default_tags { + tags = local.default_tags + } +} + +provider "aws" { + alias = "us-east-1" + region = "us-east-1" + + default_tags { + tags = local.default_tags + } + + allowed_account_ids = [ + var.aws_account_id, + ] +} diff --git a/infrastructure/terraform/components/branch/variables.tf b/infrastructure/terraform/components/branch/variables.tf new file mode 100644 index 00000000..8f507a4f --- /dev/null +++ b/infrastructure/terraform/components/branch/variables.tf @@ -0,0 +1,65 @@ +## +# Basic Required Variables for tfscaffold Components +## + +variable "project" { + type = string + description = "The name of the tfscaffold project" +} + +variable "environment" { + type = string + description = "The name of the tfscaffold environment" +} + +variable "aws_account_id" { + type = string + description = "The AWS Account ID (numeric)" +} + +variable "region" { + type = string + description = "The AWS Region" +} + +variable "group" { + type = string + description = "The group variables are being inherited from (often synonmous with account short-name)" +} + +## +# tfscaffold variables specific to this component +## + +# This is the only primary variable to have its value defined as +# a default within its declaration in this file, because the variables +# purpose is as an identifier unique to this component, rather +# then to the environment from where all other variables come. +variable "component" { + type = string + description = "The variable encapsulating the name of this component" + default = "branch" +} + +variable "default_tags" { + type = map(string) + description = "A map of default tags to apply to all taggable resources within the component" + default = {} +} + +## +# Variables specific to the "dnsroot"component +## + +variable "parent_amplify_environment" { + type = string + description = "The name of the environment which deployed the parent Amplify resource. Used to identify the appropriate state file." + default = "main" +} + +variable "branch_name" { + type = string + description = "The branch name to deploy" + default = "branch" +} + diff --git a/infrastructure/terraform/components/branch/versions.tf b/infrastructure/terraform/components/branch/versions.tf new file mode 100644 index 00000000..5fba18d2 --- /dev/null +++ b/infrastructure/terraform/components/branch/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.50" + } + } + + required_version = ">= 1.9.0" +} diff --git a/infrastructure/terraform/components/iam/.tool-versions b/infrastructure/terraform/components/iam/.tool-versions new file mode 100644 index 00000000..3874604d --- /dev/null +++ b/infrastructure/terraform/components/iam/.tool-versions @@ -0,0 +1 @@ +terraform 1.9.2 diff --git a/infrastructure/terraform/components/iam/acm_certificate.tf b/infrastructure/terraform/components/iam/acm_certificate.tf new file mode 100644 index 00000000..478f774d --- /dev/null +++ b/infrastructure/terraform/components/iam/acm_certificate.tf @@ -0,0 +1,14 @@ +# resource "aws_acm_certificate" "main" { +# # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cnames-and-https-requirements.html#https-requirements-certificate-issuer +# domain_name = "${local.csi}.${aws_route53_zone.main.name}" +# validation_method = "DNS" + +# lifecycle { +# create_before_destroy = true +# } +# } + +# resource "aws_acm_certificate_validation" "main" { +# certificate_arn = aws_acm_certificate.main.arn +# validation_record_fqdns = [for record in aws_route53_record.validation : record.fqdn] +# } diff --git a/infrastructure/terraform/components/iam/amplify_app.tf b/infrastructure/terraform/components/iam/amplify_app.tf new file mode 100644 index 00000000..95b6c22c --- /dev/null +++ b/infrastructure/terraform/components/iam/amplify_app.tf @@ -0,0 +1,24 @@ +resource "aws_amplify_app" "main" { + name = local.csi + repository = "https://github.com/NHSDigital/nhs-notify-iam-webauth" + access_token = data.aws_ssm_parameter.github_pat_ssm_param_name.value + + iam_service_role_arn = aws_iam_role.amplify.arn + + enable_auto_branch_creation = false + enable_branch_auto_build = var.enable_amplify_branch_auto_build + platform = "WEB_COMPUTE" + + auto_branch_creation_patterns = [ + "*", + "*/**" + ] + + environment_variables = { + USER_POOL_ID = aws_cognito_user_pool.main.id + # HOSTED_LOGIN_DOMAIN = "auth.${var.environment}.${local.acct.dns_zone["name"]}" + NOTIFY_GROUP = var.group + NOTIFY_ENVIRONMENT = var.environment + NOTIFY_DOMAIN_NAME = local.acct.dns_zone["name"] + } +} diff --git a/infrastructure/terraform/components/iam/amplify_domain_association.tf b/infrastructure/terraform/components/iam/amplify_domain_association.tf new file mode 100644 index 00000000..cc40b925 --- /dev/null +++ b/infrastructure/terraform/components/iam/amplify_domain_association.tf @@ -0,0 +1,10 @@ +resource "aws_amplify_domain_association" "domain" { + app_id = aws_amplify_app.main.id + domain_name = local.acct.dns_zone["name"] + enable_auto_sub_domain = false + + sub_domain { + branch_name = module.amplify_branch.name + prefix = "" + } +} diff --git a/infrastructure/terraform/components/iam/cloudwatch_log_group_amplify.tf b/infrastructure/terraform/components/iam/cloudwatch_log_group_amplify.tf new file mode 100644 index 00000000..68418846 --- /dev/null +++ b/infrastructure/terraform/components/iam/cloudwatch_log_group_amplify.tf @@ -0,0 +1,5 @@ +resource "aws_cloudwatch_log_group" "amplify" { + name = "/aws/amplify/${local.csi}" + retention_in_days = var.log_retention_in_days + kms_key_id = module.kms.key_arn +} diff --git a/infrastructure/terraform/components/iam/cognito_user_pool.tf b/infrastructure/terraform/components/iam/cognito_user_pool.tf new file mode 100644 index 00000000..8c0ade62 --- /dev/null +++ b/infrastructure/terraform/components/iam/cognito_user_pool.tf @@ -0,0 +1,9 @@ +resource "aws_cognito_user_pool" "main" { + name = local.csi + + username_attributes = ["email"] + + admin_create_user_config { + allow_admin_create_user_only = true + } +} diff --git a/infrastructure/terraform/components/iam/cognito_user_pool_client.tf b/infrastructure/terraform/components/iam/cognito_user_pool_client.tf new file mode 100644 index 00000000..754c7276 --- /dev/null +++ b/infrastructure/terraform/components/iam/cognito_user_pool_client.tf @@ -0,0 +1,26 @@ +resource "aws_cognito_user_pool_client" "main" { + name = local.csi + user_pool_id = aws_cognito_user_pool.main.id + + callback_urls = flatten([ + var.cognito_user_pool_additional_callback_urls, + [ + "https://${local.csi}.${var.root_domain_name}/auth/", + "https://${local.csi}.${aws_amplify_app.main.id}.amplifyapp.com/auth/" + ] + ]) + + supported_identity_providers = flatten([ + var.enable_cognito_built_in_idp ? ["COGNITO"] : [], + # identity_provider_names.provider.provider_name #e.g. auth0 + ]) + + allowed_oauth_flows = ["code"] + allowed_oauth_scopes = [ + "openid", + "email", + "phone", + "profile", + "aws.cognito.signin.user.admin" + ] +} diff --git a/infrastructure/terraform/components/iam/cognito_user_pool_domain.tf b/infrastructure/terraform/components/iam/cognito_user_pool_domain.tf new file mode 100644 index 00000000..181c6c6d --- /dev/null +++ b/infrastructure/terraform/components/iam/cognito_user_pool_domain.tf @@ -0,0 +1,4 @@ +resource "aws_cognito_user_pool_domain" "main" { + user_pool_id = aws_cognito_user_pool.main.id + domain = "nhsnotify" +} diff --git a/infrastructure/terraform/components/iam/data_ssm_parameter_github_pat.tf b/infrastructure/terraform/components/iam/data_ssm_parameter_github_pat.tf new file mode 100644 index 00000000..cc52a394 --- /dev/null +++ b/infrastructure/terraform/components/iam/data_ssm_parameter_github_pat.tf @@ -0,0 +1,3 @@ +data "aws_ssm_parameter" "github_pat_ssm_param_name" { + name = local.acct.github_pat_ssm_param_name +} diff --git a/infrastructure/terraform/components/iam/iam_role_amplify.tf b/infrastructure/terraform/components/iam/iam_role_amplify.tf new file mode 100644 index 00000000..c26ec81e --- /dev/null +++ b/infrastructure/terraform/components/iam/iam_role_amplify.tf @@ -0,0 +1,51 @@ +resource "aws_iam_role" "amplify" { + name = "${local.csi}-service-role" + assume_role_policy = data.aws_iam_policy_document.assumerole_amplify.json +} + +data "aws_iam_policy_document" "assumerole_amplify" { + statement { + effect = "Allow" + + principals { + type = "Service" + identifiers = ["amplify.amazonaws.com"] + } + + actions = ["sts:AssumeRole"] + } +} + +resource "aws_iam_role_policy_attachment" "amplify_amplify_backend_built_in" { + role = aws_iam_role.amplify.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmplifyBackendDeployFullAccess" +} + +resource "aws_iam_role_policy_attachment" "amplify_amplify" { + role = aws_iam_role.amplify.name + policy_arn = aws_iam_policy.amplify.arn +} + +resource "aws_iam_policy" "amplify" { + name = "${local.csi}-amplify" + description = "Amplify " + policy = data.aws_iam_policy_document.amplify.json +} + +data "aws_iam_policy_document" "amplify" { + statement { + effect = "Allow" + + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogGroups", + ] + + #tfsec:ignore:aws-iam-no-policy-wildcards + resources = [ + "${aws_cloudwatch_log_group.amplify.arn}:*", + "${aws_cloudwatch_log_group.amplify.arn}:log-stream:*", + ] + } +} diff --git a/infrastructure/terraform/components/iam/locals_remote_state.tf b/infrastructure/terraform/components/iam/locals_remote_state.tf new file mode 100644 index 00000000..50c57af6 --- /dev/null +++ b/infrastructure/terraform/components/iam/locals_remote_state.tf @@ -0,0 +1,40 @@ +locals { + bootstrap = data.terraform_remote_state.bootstrap.outputs + acct = data.terraform_remote_state.acct.outputs +} + +data "terraform_remote_state" "bootstrap" { + backend = "s3" + + config = { + bucket = local.terraform_state_bucket + + key = format( + "%s/%s/%s/%s/bootstrap.tfstate", + var.project, + var.aws_account_id, + "eu-west-2", + "bootstrap" + ) + + region = "eu-west-2" + } +} + +data "terraform_remote_state" "acct" { + backend = "s3" + + config = { + bucket = local.terraform_state_bucket + + key = format( + "%s/%s/%s/%s/acct.tfstate", + var.project, + var.aws_account_id, + "eu-west-2", + "main" + ) + + region = "eu-west-2" + } +} diff --git a/infrastructure/terraform/components/iam/locals_tfscaffold.tf b/infrastructure/terraform/components/iam/locals_tfscaffold.tf new file mode 100644 index 00000000..b7cf3217 --- /dev/null +++ b/infrastructure/terraform/components/iam/locals_tfscaffold.tf @@ -0,0 +1,44 @@ +locals { + terraform_state_bucket = format( + "%s-tfscaffold-%s-%s", + var.project, + var.aws_account_id, + var.region, + ) + + csi = replace( + format( + "%s-%s-%s", + var.project, + var.environment, + var.component, + ), + "_", + "", + ) + + # CSI for use in resources with a global namespace, i.e. S3 Buckets + csi_global = replace( + format( + "%s-%s-%s-%s-%s", + var.project, + var.aws_account_id, + var.region, + var.environment, + var.component, + ), + "_", + "", + ) + + default_tags = merge( + var.default_tags, + { + Project = var.project + Environment = var.environment + Component = var.component + Group = var.group + Name = local.csi + }, + ) +} diff --git a/infrastructure/terraform/components/iam/module_amplify_branch.tf b/infrastructure/terraform/components/iam/module_amplify_branch.tf new file mode 100644 index 00000000..03c76c7b --- /dev/null +++ b/infrastructure/terraform/components/iam/module_amplify_branch.tf @@ -0,0 +1,18 @@ +module "amplify_branch" { + source = "../../modules/amp_branch" + + name = var.environment + aws_account_id = var.aws_account_id + component = var.component + environment = var.environment + project = var.project + region = var.region + group = var.group + + cognito_user_pool_client_id = aws_cognito_user_pool_client.main.user_pool_id + cognito_user_pool_identity_provider_names = aws_cognito_user_pool_client.main.supported_identity_providers + amplify_app_id = aws_amplify_app.main.id + branch = var.environment + domain_name = local.acct.dns_zone["name"] + subdomain = var.environment +} diff --git a/infrastructure/terraform/components/iam/module_kms.tf b/infrastructure/terraform/components/iam/module_kms.tf new file mode 100644 index 00000000..0a09f3d2 --- /dev/null +++ b/infrastructure/terraform/components/iam/module_kms.tf @@ -0,0 +1,54 @@ +module "kms" { + source = "../../modules/kms" + + aws_account_id = var.aws_account_id + component = var.component + environment = var.environment + project = var.project + region = var.region + + name = "main" + deletion_window = var.kms_deletion_window + alias = "alias/${local.csi}" + key_policy_documents = [data.aws_iam_policy_document.kms.json] + iam_delegation = true +} + +data "aws_iam_policy_document" "kms" { + # '*' resource scope is permitted in access policies as as the resource is itself + # https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-services.html + + statement { + sid = "AllowCloudWatchEncrypt" + effect = "Allow" + + principals { + type = "Service" + + identifiers = [ + "logs.${var.region}.amazonaws.com", + ] + } + + actions = [ + "kms:Encrypt*", + "kms:Decrypt*", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:Describe*" + ] + + resources = [ + "*", + ] + + condition { + test = "ArnLike" + variable = "kms:EncryptionContext:aws:logs:arn" + + values = [ + "arn:aws:logs:${var.region}:${var.aws_account_id}:log-group:*", + ] + } + } +} diff --git a/infrastructure/terraform/components/iam/outputs.tf b/infrastructure/terraform/components/iam/outputs.tf new file mode 100644 index 00000000..f0bb257c --- /dev/null +++ b/infrastructure/terraform/components/iam/outputs.tf @@ -0,0 +1,12 @@ +output "cognito_user_pool" { + value = { + id = aws_cognito_user_pool.main.id + identity_providers = aws_cognito_user_pool_client.main.supported_identity_providers + } +} + +output "amplify" { + value = { + id = aws_amplify_app.main.id + } +} diff --git a/infrastructure/terraform/components/iam/provider_aws.tf b/infrastructure/terraform/components/iam/provider_aws.tf new file mode 100644 index 00000000..d694811e --- /dev/null +++ b/infrastructure/terraform/components/iam/provider_aws.tf @@ -0,0 +1,24 @@ +provider "aws" { + region = var.region + + allowed_account_ids = [ + var.aws_account_id, + ] + + default_tags { + tags = local.default_tags + } +} + +provider "aws" { + alias = "us-east-1" + region = "us-east-1" + + default_tags { + tags = local.default_tags + } + + allowed_account_ids = [ + var.aws_account_id, + ] +} diff --git a/infrastructure/terraform/components/iam/route53_record_validation.tf b/infrastructure/terraform/components/iam/route53_record_validation.tf new file mode 100644 index 00000000..e7ad40e4 --- /dev/null +++ b/infrastructure/terraform/components/iam/route53_record_validation.tf @@ -0,0 +1,16 @@ +# resource "aws_route53_record" "validation" { +# for_each = { +# for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => { +# name = dvo.resource_record_name +# record = dvo.resource_record_value +# type = dvo.resource_record_type +# } +# } + +# allow_overwrite = true +# name = each.value.name +# records = [each.value.record] +# ttl = 60 +# type = each.value.type +# zone_id = local.acct.dns_zone["id"] +# } diff --git a/infrastructure/terraform/components/iam/variables.tf b/infrastructure/terraform/components/iam/variables.tf new file mode 100644 index 00000000..44ad61d5 --- /dev/null +++ b/infrastructure/terraform/components/iam/variables.tf @@ -0,0 +1,88 @@ +## +# Basic Required Variables for tfscaffold Components +## + +variable "project" { + type = string + description = "The name of the tfscaffold project" +} + +variable "environment" { + type = string + description = "The name of the tfscaffold environment" +} + +variable "aws_account_id" { + type = string + description = "The AWS Account ID (numeric)" +} + +variable "region" { + type = string + description = "The AWS Region" +} + +variable "group" { + type = string + description = "The group variables are being inherited from (often synonmous with account short-name)" +} + +## +# tfscaffold variables specific to this component +## + +# This is the only primary variable to have its value defined as +# a default within its declaration in this file, because the variables +# purpose is as an identifier unique to this component, rather +# then to the environment from where all other variables come. +variable "component" { + type = string + description = "The variable encapsulating the name of this component" + default = "iam" +} + +variable "default_tags" { + type = map(string) + description = "A map of default tags to apply to all taggable resources within the component" + default = {} +} + +## +# Variables specific to the "dnsroot"component +## + +variable "log_retention_in_days" { + type = number + description = "The retention period in days for the Cloudwatch Logs events to be retained, default of 0 is indefinite" + default = 0 +} + +variable "kms_deletion_window" { + type = string + description = "When a kms key is deleted, how long should it wait in the pending deletion state?" + default = "30" +} + +variable "root_domain_name" { + type = string + description = "The service's root DNS root nameespace, like nonprod.nhsnotify.national.nhs.uk" + default = "nonprod.nhsnotify.national.nhs.uk" +} + +variable "enable_amplify_branch_auto_build" { + type = bool + description = "Enable automatic building of branches" + default = false +} + +variable "cognito_user_pool_additional_callback_urls" { + type = list(string) + description = "A list of additional callback_urls for the cognito user pool" + default = [] +} + +variable "enable_cognito_built_in_idp" { + type = bool + description = "Enable the use of Cognito as an IDP; CIS2 is prefered" + default = false +} diff --git a/infrastructure/terraform/components/iam/versions.tf b/infrastructure/terraform/components/iam/versions.tf new file mode 100644 index 00000000..5fba18d2 --- /dev/null +++ b/infrastructure/terraform/components/iam/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.50" + } + } + + required_version = ">= 1.9.0" +} diff --git a/infrastructure/terraform/etc/README.md b/infrastructure/terraform/etc/README.md new file mode 100644 index 00000000..0c683ab2 --- /dev/null +++ b/infrastructure/terraform/etc/README.md @@ -0,0 +1,22 @@ +# THIS DIRECTORY SHOULD BE IGNORED IN THE CONTEXT OF THE NHS NOTIFY PROGRAMME AND IS INTENDED FOR INDEPENDENT DEVELOPMENT USE ONLY + +## Example configuration files may look like this with the global.tfvars forming the base, any configuration will override with a most specific config taking presidence. env > region > group > Global + +```env_eu-west-2_example.tfvars +environment = "example" +``` + +```eu-west-2.tfvars +region = "eu-west-2" +``` + +```group_example.tfvars +group = "example" +aws_account_id = "1234567890" +``` + +```global.tfvars +tfscaffold_bucket_prefix = "nhs-notify-tfscaffold" +project = "myproject" +aws_account_id = "0987654321" +``` diff --git a/infrastructure/environments/.gitkeep b/infrastructure/terraform/modules/.gitkeep similarity index 100% rename from infrastructure/environments/.gitkeep rename to infrastructure/terraform/modules/.gitkeep diff --git a/infrastructure/terraform/modules/amp_branch/amplify_branch.tf b/infrastructure/terraform/modules/amp_branch/amplify_branch.tf new file mode 100644 index 00000000..8aefbc76 --- /dev/null +++ b/infrastructure/terraform/modules/amp_branch/amplify_branch.tf @@ -0,0 +1,11 @@ +resource "aws_amplify_branch" "main" { + app_id = var.amplify_app_id + branch_name = var.branch + display_name = var.name + enable_pull_request_preview = false # PR previews are not supported for public repos + + environment_variables = { + USER_POOL_CLIENT_ID = var.cognito_user_pool_client_id + NOTIFY_SUBDOMAIN = var.subdomain + } +} diff --git a/infrastructure/terraform/modules/amp_branch/locals.tf b/infrastructure/terraform/modules/amp_branch/locals.tf new file mode 100644 index 00000000..8d1bc72d --- /dev/null +++ b/infrastructure/terraform/modules/amp_branch/locals.tf @@ -0,0 +1,33 @@ +locals { + csi = format( + "%s-%s-%s-%s-%s", + var.project, + var.environment, + var.component, + var.module, + var.name, + ) + + # CSI for use in resources with an account namespace, eg IAM roles + csi_account = replace( + format( + "%s-%s-%s-%s-%s-%s", + var.project, + var.region, + var.environment, + var.component, + var.module, + var.name, + ), + "_", + "", + ) + + default_tags = merge( + var.default_tags, + { + Module = var.module + Name = local.csi + }, + ) +} diff --git a/infrastructure/terraform/modules/amp_branch/outputs.tf b/infrastructure/terraform/modules/amp_branch/outputs.tf new file mode 100644 index 00000000..2e300ce4 --- /dev/null +++ b/infrastructure/terraform/modules/amp_branch/outputs.tf @@ -0,0 +1,3 @@ +output "name" { + value = aws_amplify_branch.main.branch_name +} diff --git a/infrastructure/terraform/modules/amp_branch/variables.tf b/infrastructure/terraform/modules/amp_branch/variables.tf new file mode 100644 index 00000000..168aec69 --- /dev/null +++ b/infrastructure/terraform/modules/amp_branch/variables.tf @@ -0,0 +1,92 @@ +## +# Basic inherited variables for terraformscaffold modules +## + +variable "project" { + type = string + description = "The name of the terraformscaffold project calling the module" +} + +variable "environment" { + type = string + description = "The name of the terraformscaffold environment the module is called for" +} + +variable "component" { + type = string + description = "The name of the terraformscaffold component calling this module" +} + +variable "aws_account_id" { + type = string + description = "The AWS Account ID (numeric)" +} + +variable "group" { + type = string + description = "The group variables are being inherited from (often synonmous with account short-name)" +} + +## +# Module self-identification +## + +variable "module" { + type = string + description = "The name of this module. This is a special variable, it should be set only here and never overridden." + default = "kms" +} + +## +# Variable specific to the module +## + +# We presume this will always be specified. The default of {} will cause an error if a valid map is not specified. +# If we ever want to define this but allow it to not be specified, then we must provide a default tag keypair will be applied +# as the true default. In any other case default_tags should be removed from the module. +variable "default_tags" { + type = map(string) + description = "Default tag map for application to all taggable resources in the module" + default = {} +} + +variable "region" { + type = string + description = "The AWS Region" +} + +variable "name" { + type = string + description = "A unique name to distinguish this module invocation from others within the same CSI scope" +} + +variable "cognito_user_pool_client_id" { + description = "Cognito User Pool client ID" + type = string +} + +variable "cognito_user_pool_identity_provider_names" { + description = "A list of Cognito IDP names" + type = list(string) +} + +variable "amplify_app_id" { + description = "Amplify application ID" + type = string +} + +variable "branch" { + description = "The name of the branch being deployed" + type = string +} + +variable "domain_name" { + type = string + description = "Root domain name for this Amplify app" +} + +variable "subdomain" { + type = string + default = "main" + description = "Subdomain used as the branch alias" +} diff --git a/infrastructure/terraform/modules/amp_branch/versions.tf b/infrastructure/terraform/modules/amp_branch/versions.tf new file mode 100644 index 00000000..f8dc86e9 --- /dev/null +++ b/infrastructure/terraform/modules/amp_branch/versions.tf @@ -0,0 +1,9 @@ + +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + } + } + required_version = ">= 1.9.0" +} diff --git a/infrastructure/terraform/modules/kms/data_iam_kms_admin_policy.tf b/infrastructure/terraform/modules/kms/data_iam_kms_admin_policy.tf new file mode 100644 index 00000000..9a62b871 --- /dev/null +++ b/infrastructure/terraform/modules/kms/data_iam_kms_admin_policy.tf @@ -0,0 +1,31 @@ +#tfsec:ignore:aws-iam-no-policy-wildcards +data "aws_iam_policy_document" "admin" { + policy_id = "${local.csi}-admin" + + statement { + sid = "AllowKeyAdmin" + effect = "Allow" + + actions = [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:TagResource", + "kms:UntagResource", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + ] + + resources = [ + aws_kms_key.main.arn, + aws_kms_alias.main.arn, + ] + } +} diff --git a/infrastructure/terraform/modules/kms/data_iam_kms_user_policy.tf b/infrastructure/terraform/modules/kms/data_iam_kms_user_policy.tf new file mode 100644 index 00000000..5cb6f3b5 --- /dev/null +++ b/infrastructure/terraform/modules/kms/data_iam_kms_user_policy.tf @@ -0,0 +1,43 @@ +#tfsec:ignore:aws-iam-no-policy-wildcards +data "aws_iam_policy_document" "user" { + policy_id = "${local.csi}-user" + + statement { + sid = "AllowUseOfTheKmskey" + effect = "Allow" + + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey", + ] + + resources = [ + aws_kms_key.main.arn, + ] + } + + statement { + sid = "AllowDelegationToAwsServiceViaGrant" + effect = "Allow" + + actions = [ + "kms:CreateGrant", + ] + + resources = [ + aws_kms_key.main.arn, + ] + + condition { + test = "Bool" + variable = "kms:GrantIsForAWSResource" + + values = [ + "true", + ] + } + } +} diff --git a/infrastructure/terraform/modules/kms/data_iam_policy_document_key.tf b/infrastructure/terraform/modules/kms/data_iam_policy_document_key.tf new file mode 100644 index 00000000..7116cd32 --- /dev/null +++ b/infrastructure/terraform/modules/kms/data_iam_policy_document_key.tf @@ -0,0 +1,27 @@ +data "aws_iam_policy_document" "key" { + source_policy_documents = var.key_policy_documents + + dynamic "statement" { + for_each = var.iam_delegation ? [1] : [] + content { + sid = "AllowFullLocalAdministration" + effect = "Allow" + + principals { + type = "AWS" + + identifiers = [ + "arn:aws:iam::${var.aws_account_id}:root", + ] + } + + actions = [ + "kms:*", + ] + + resources = [ + "*", + ] + } + } +} diff --git a/infrastructure/terraform/modules/kms/iam_policy_admin.tf b/infrastructure/terraform/modules/kms/iam_policy_admin.tf new file mode 100644 index 00000000..bdef4b39 --- /dev/null +++ b/infrastructure/terraform/modules/kms/iam_policy_admin.tf @@ -0,0 +1,13 @@ +# Create the Key Policy for the AWS KMS Key +resource "aws_iam_policy" "admin" { + name = "${local.csi_account}-admin" + path = "/" + policy = data.aws_iam_policy_document.admin.json + + tags = merge( + local.default_tags, + { + Name = "${local.csi_account}-admin", + }, + ) +} diff --git a/infrastructure/terraform/modules/kms/iam_policy_user.tf b/infrastructure/terraform/modules/kms/iam_policy_user.tf new file mode 100644 index 00000000..f7dc182b --- /dev/null +++ b/infrastructure/terraform/modules/kms/iam_policy_user.tf @@ -0,0 +1,13 @@ +# Create the Key Policy for the AWS KMS Key +resource "aws_iam_policy" "user" { + name = "${local.csi_account}-user" + path = "/" + policy = data.aws_iam_policy_document.user.json + + tags = merge( + local.default_tags, + { + Name = "${local.csi_account}-user", + }, + ) +} diff --git a/infrastructure/terraform/modules/kms/kms_key.tf b/infrastructure/terraform/modules/kms/kms_key.tf new file mode 100644 index 00000000..787c01e2 --- /dev/null +++ b/infrastructure/terraform/modules/kms/kms_key.tf @@ -0,0 +1,8 @@ +resource "aws_kms_key" "main" { + bypass_policy_lockout_safety_check = false + deletion_window_in_days = var.deletion_window + description = local.csi + enable_key_rotation = true + policy = data.aws_iam_policy_document.key.json + tags = local.default_tags +} diff --git a/infrastructure/terraform/modules/kms/kms_key_alias.tf b/infrastructure/terraform/modules/kms/kms_key_alias.tf new file mode 100644 index 00000000..96c986e1 --- /dev/null +++ b/infrastructure/terraform/modules/kms/kms_key_alias.tf @@ -0,0 +1,4 @@ +resource "aws_kms_alias" "main" { + name = var.alias + target_key_id = aws_kms_key.main.key_id +} diff --git a/infrastructure/terraform/modules/kms/locals.tf b/infrastructure/terraform/modules/kms/locals.tf new file mode 100644 index 00000000..8d1bc72d --- /dev/null +++ b/infrastructure/terraform/modules/kms/locals.tf @@ -0,0 +1,33 @@ +locals { + csi = format( + "%s-%s-%s-%s-%s", + var.project, + var.environment, + var.component, + var.module, + var.name, + ) + + # CSI for use in resources with an account namespace, eg IAM roles + csi_account = replace( + format( + "%s-%s-%s-%s-%s-%s", + var.project, + var.region, + var.environment, + var.component, + var.module, + var.name, + ), + "_", + "", + ) + + default_tags = merge( + var.default_tags, + { + Module = var.module + Name = local.csi + }, + ) +} diff --git a/infrastructure/terraform/modules/kms/outputs.tf b/infrastructure/terraform/modules/kms/outputs.tf new file mode 100644 index 00000000..0f0fd9e9 --- /dev/null +++ b/infrastructure/terraform/modules/kms/outputs.tf @@ -0,0 +1,15 @@ +output "key_arn" { + value = aws_kms_key.main.arn +} + +output "key_id" { + value = aws_kms_key.main.key_id +} + +output "admin_policy_arn" { + value = aws_iam_policy.admin.arn +} + +output "user_policy_arn" { + value = aws_iam_policy.user.arn +} diff --git a/infrastructure/terraform/modules/kms/variables.tf b/infrastructure/terraform/modules/kms/variables.tf new file mode 100644 index 00000000..a6a7fdbe --- /dev/null +++ b/infrastructure/terraform/modules/kms/variables.tf @@ -0,0 +1,78 @@ +## +# Basic inherited variables for terraformscaffold modules +## + +variable "project" { + type = string + description = "The name of the terraformscaffold project calling the module" +} + +variable "environment" { + type = string + description = "The name of the terraformscaffold environment the module is called for" +} + +variable "component" { + type = string + description = "The name of the terraformscaffold component calling this module" +} + +variable "aws_account_id" { + type = string + description = "The AWS Account ID (numeric)" +} + +## +# Module self-identification +## + +variable "module" { + type = string + description = "The name of this module. This is a special variable, it should be set only here and never overridden." + default = "kms" +} + +## +# Variable specific to the module +## + +# We presume this will always be specified. The default of {} will cause an error if a valid map is not specified. +# If we ever want to define this but allow it to not be specified, then we must provide a default tag keypair will be applied +# as the true default. In any other case default_tags should be removed from the module. +variable "default_tags" { + type = map(string) + description = "Default tag map for application to all taggable resources in the module" + default = {} +} + +variable "region" { + type = string + description = "The AWS Region" +} + +variable "name" { + type = string + description = "A unique name to distinguish this module invocation from others within the same CSI scope" +} + +variable "deletion_window" { + type = string + description = "KMS key deletion window" +} + +variable "alias" { + type = string + description = "Alias name for the hieradata KMS key" +} + +variable "key_policy_documents" { + type = list(string) + description = "List of KMS key policy JSON documents" + default = [] +} + +variable "iam_delegation" { + type = bool + description = "Whether to delegate administration of the key to the local account. Defaults to true" + default = true +} diff --git a/infrastructure/terraform/modules/kms/versions.tf b/infrastructure/terraform/modules/kms/versions.tf new file mode 100644 index 00000000..f8dc86e9 --- /dev/null +++ b/infrastructure/terraform/modules/kms/versions.tf @@ -0,0 +1,9 @@ + +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + } + } + required_version = ">= 1.9.0" +} diff --git a/scripts/config/tfsec.yml b/scripts/config/tfsec.yml new file mode 100644 index 00000000..e19ea1cc --- /dev/null +++ b/scripts/config/tfsec.yml @@ -0,0 +1,3 @@ +--- +minimum_severity: WARNING +exclude: diff --git a/scripts/config/vale/styles/Vocab/words/accept.txt b/scripts/config/vale/styles/Vocab/words/accept.txt index eb9cd04e..a895c390 100644 --- a/scripts/config/vale/styles/Vocab/words/accept.txt +++ b/scripts/config/vale/styles/Vocab/words/accept.txt @@ -1,17 +1,19 @@ +[A-Z]+s Bitwarden +bot +config Cyber Dependabot +env Gitleaks Grype +idempotence OAuth Octokit +onboarding Podman Python Syft Terraform -Trufflehog -bot -idempotence -onboarding toolchain -[A-Z]+s +Trufflehog diff --git a/scripts/terraform/examples/terraform-state-aws-s3/versions.tf b/scripts/terraform/examples/terraform-state-aws-s3/versions.tf index 18fd04af..839b47e9 100644 --- a/scripts/terraform/examples/terraform-state-aws-s3/versions.tf +++ b/scripts/terraform/examples/terraform-state-aws-s3/versions.tf @@ -1,8 +1,8 @@ terraform { - required_version = ">= 1.5.0" required_providers { aws = { version = ">= 5.14.0" } } + required_version = ">= 1.9.0" } diff --git a/scripts/terraform/terraform.mk b/scripts/terraform/terraform.mk index 120a0591..0db0a88a 100644 --- a/scripts/terraform/terraform.mk +++ b/scripts/terraform/terraform.mk @@ -42,9 +42,9 @@ clean:: # Remove Terraform files (terraform) - optional: terraform_dir|dir=[path _terraform: # Terraform command wrapper - mandatory: cmd=[command to execute]; optional: dir=[path to a directory where the command will be executed, relative to the project's top-level directory, default is one of the module variables or the example directory, if not set], opts=[options to pass to the Terraform command, default is none/empty] # 'TERRAFORM_STACK' is passed to the functions as environment variable - TERRAFORM_STACK=$(or ${TERRAFORM_STACK}, $(or ${terraform_stack}, $(or ${STACK}, $(or ${stack}, scripts/terraform/examples/terraform-state-aws-s3)))) + TERRAFORM_STACK=$(or ${TERRAFORM_STACK}, $(or ${terraform_stack}, $(or ${STACK}, ${stack}))) dir=$(or ${dir}, ${TERRAFORM_STACK}) - source scripts/terraform/terraform.lib.sh + . "scripts/terraform/terraform.lib.sh"; \ terraform-${cmd} # 'dir' and 'opts' are accessible by the function as environment variables, if set # ============================================================================== @@ -55,6 +55,12 @@ terraform-shellscript-lint: # Lint all Terraform module shell scripts @Quality file=$${file} scripts/shellscript-linter.sh done +terraform-sec: # TFSEC check against Terraform files - optional: terraform_dir|dir=[path to a directory where the command will be executed, relative to the project's top-level directory, default is one of the module variables or the example directory, if not set], terraform_opts|opts=[options to pass to the Terraform fmt command, default is '-recursive'] @Quality + tfsec infrastructure/terraform \ + --force-all-dirs \ + --exclude-downloaded-modules \ + --config-file scripts/config/tfsec.yml + # ============================================================================== # Module tests and examples - please DO NOT edit this section! @@ -68,7 +74,7 @@ terraform-example-destroy-aws-infrastructure: # Destroy example of AWS infrastru terraform-example-clean: # Remove Terraform example files @ExamplesAndTests dir=$(or ${dir}, ${TERRAFORM_STACK}) - source scripts/terraform/terraform.lib.sh + . "scripts/terraform/terraform.lib.sh"; \ terraform-clean rm -f ${TERRAFORM_STACK}/.terraform.lock.hcl