diff --git a/.github/workflows/manual-terraform-apply.yaml b/.github/workflows/manual-terraform-apply.yaml index d81d2092e..3b7c03fd9 100644 --- a/.github/workflows/manual-terraform-apply.yaml +++ b/.github/workflows/manual-terraform-apply.yaml @@ -1,6 +1,8 @@ name: Manual Terraform Apply on: + pull_request: + types: [ opened, synchronize, reopened ] workflow_dispatch: inputs: environment: @@ -10,13 +12,13 @@ on: options: [dev, test, preprod] jobs: - plan-stacks: + apply-stacks: runs-on: ubuntu-latest environment: ${{ inputs.environment }} permissions: id-token: write contents: read - + timeout-minutes: 30 steps: - name: "Setup Terraform" uses: hashicorp/setup-terraform@v3 @@ -57,7 +59,7 @@ jobs: - name: "Terraform Plan Stacks" env: ENVIRONMENT: ${{ inputs.environment }} - WORKSPACE: ${{ inputs.environment }} + WORKSPACE: "default" TF_VAR_API_CA_CERT: ${{ secrets.API_CA_CERT }} TF_VAR_API_CLIENT_CERT: ${{ secrets.API_CLIENT_CERT }} TF_VAR_API_PRIVATE_KEY_CERT: ${{ secrets.API_PRIVATE_KEY_CERT }} diff --git a/infrastructure/modules/api_gateway/api_gateway.tf b/infrastructure/modules/api_gateway/api_gateway.tf index 8bcf52e93..3e9c5571e 100644 --- a/infrastructure/modules/api_gateway/api_gateway.tf +++ b/infrastructure/modules/api_gateway/api_gateway.tf @@ -8,6 +8,8 @@ resource "aws_api_gateway_rest_api" "api_gateway" { create_before_destroy = true } + depends_on = [aws_kms_key_policy.api_gateway] + tags = { Stack = var.stack_name } diff --git a/infrastructure/modules/api_gateway/cloudwatch.tf b/infrastructure/modules/api_gateway/cloudwatch.tf index 272a1c465..1f244883f 100644 --- a/infrastructure/modules/api_gateway/cloudwatch.tf +++ b/infrastructure/modules/api_gateway/cloudwatch.tf @@ -7,4 +7,6 @@ resource "aws_cloudwatch_log_group" "api_gateway" { lifecycle { prevent_destroy = false } + + depends_on = [aws_kms_key_policy.api_gateway] } diff --git a/infrastructure/modules/api_gateway/iam.tf b/infrastructure/modules/api_gateway/iam.tf index c59937a70..e0b80749e 100644 --- a/infrastructure/modules/api_gateway/iam.tf +++ b/infrastructure/modules/api_gateway/iam.tf @@ -16,6 +16,7 @@ resource "aws_iam_role" "api_gateway" { data "aws_iam_policy_document" "api_gateway_logging" { #checkov:skip=CKV_AWS_356: Wildcard permissions needed for global log event reads + #checkov:skip=CKV_AWS_111: Ensure IAM policies does not allow write access without constraints statement { sid = "AllowCreateLogGroup" effect = "Allow" @@ -44,7 +45,8 @@ data "aws_iam_policy_document" "api_gateway_logging" { "logs:DescribeLogGroups", "logs:DescribeLogStreams", "logs:GetLogEvents", - "logs:FilterLogEvents" + "logs:FilterLogEvents", + "logs:PutRetentionPolicy" ] resources = ["*"] } diff --git a/infrastructure/modules/api_gateway/kms.tf b/infrastructure/modules/api_gateway/kms.tf index 4994b9b40..8506fd9c8 100644 --- a/infrastructure/modules/api_gateway/kms.tf +++ b/infrastructure/modules/api_gateway/kms.tf @@ -23,17 +23,17 @@ data "aws_iam_policy_document" "api_gateway" { sid = "Enable IAM User Permissions for ${var.api_gateway_name} API Gateway" effect = "Allow" principals { - type = "AWS" + type = "AWS" identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] } - actions = ["kms:*"] + actions = ["kms:*"] resources = [aws_kms_key.api_gateway.arn] } statement { sid = "APIGatewayCloudwatchKMSAccess" effect = "Allow" principals { - type = "Service" + type = "Service" identifiers = ["logs.${var.region}.amazonaws.com"] } actions = [ @@ -41,8 +41,16 @@ data "aws_iam_policy_document" "api_gateway" { "kms:Decrypt*", "kms:ReEncrypt*", "kms:GenerateDataKey*", - "kms:Describe*" + "kms:Describe*", + "kms:CreateGrant" ] resources = [aws_kms_key.api_gateway.arn] + condition { + test = "StringLike" + variable = "kms:EncryptionContext:aws:logs:arn" + values = [ + "arn:aws:logs:${var.region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/apigateway/*" + ] + } } } diff --git a/infrastructure/modules/kinesis_firehose/kinesis_firehose_delivery_stream.tf b/infrastructure/modules/kinesis_firehose/kinesis_firehose_delivery_stream.tf index a0937d692..397469bcf 100644 --- a/infrastructure/modules/kinesis_firehose/kinesis_firehose_delivery_stream.tf +++ b/infrastructure/modules/kinesis_firehose/kinesis_firehose_delivery_stream.tf @@ -25,5 +25,10 @@ resource "aws_kinesis_firehose_delivery_stream" "eligibility_audit_firehose_deli key_type = "CUSTOMER_MANAGED_CMK" } + depends_on = [ + aws_kms_key_policy.firehose_key_policy, + var.kinesis_cloud_watch_log_group_name + ] + tags = var.tags } diff --git a/infrastructure/modules/kinesis_firehose/kms.tf b/infrastructure/modules/kinesis_firehose/kms.tf index 8ba36b987..43824c81f 100644 --- a/infrastructure/modules/kinesis_firehose/kms.tf +++ b/infrastructure/modules/kinesis_firehose/kms.tf @@ -85,7 +85,8 @@ data "aws_iam_policy_document" "firehose_kms_key_policy" { "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", - "kms:DescribeKey" + "kms:DescribeKey", + "kms:CreateGrant" ] resources = [aws_kms_key.firehose_cmk.arn] condition { diff --git a/infrastructure/modules/s3/outputs.tf b/infrastructure/modules/s3/outputs.tf index 407876513..62de5bf85 100644 --- a/infrastructure/modules/s3/outputs.tf +++ b/infrastructure/modules/s3/outputs.tf @@ -18,6 +18,14 @@ output "storage_bucket_id" { value = aws_s3_bucket.storage_bucket.id } +output "storage_bucket_kms_key_id" { + value = aws_kms_key.storage_bucket_cmk.id +} + output "storage_bucket_kms_key_arn" { value = aws_kms_key.storage_bucket_cmk.arn } + +output "storage_bucket_versioning_config" { + value = aws_s3_bucket_versioning.storage_bucket_versioning_config +} diff --git a/infrastructure/stacks/api-layer/iam_policies.tf b/infrastructure/stacks/api-layer/iam_policies.tf index 09cfcf6fc..481516c4f 100644 --- a/infrastructure/stacks/api-layer/iam_policies.tf +++ b/infrastructure/stacks/api-layer/iam_policies.tf @@ -1,7 +1,7 @@ # Read-only policy for DynamoDB data "aws_iam_policy_document" "dynamodb_read_policy_doc" { statement { - actions = ["dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan"] + actions = ["dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan"] resources = [module.eligibility_status_table.arn] } } @@ -16,14 +16,14 @@ resource "aws_iam_role_policy" "lambda_dynamodb_read_policy" { # Write-only policy for DynamoDB data "aws_iam_policy_document" "dynamodb_write_policy_doc" { statement { - actions = ["dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:BatchWriteItem"] + actions = ["dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:BatchWriteItem"] resources = [module.eligibility_status_table.arn] } } # Attach dynamoDB write policy to external write role resource "aws_iam_role_policy" "external_dynamodb_write_policy" { - count = length(aws_iam_role.write_access_role) + count = length(aws_iam_role.write_access_role) name = "DynamoDBWriteAccess" role = aws_iam_role.write_access_role[count.index].id policy = data.aws_iam_policy_document.dynamodb_write_policy_doc.json @@ -43,7 +43,7 @@ data "aws_iam_policy_document" "s3_rules_bucket_policy" { ] condition { test = "Bool" - values = ["true"] + values = ["true"] variable = "aws:SecureTransport" } } @@ -106,7 +106,7 @@ resource "aws_iam_role_policy_attachment" "lambda_logs_policy_attachment" { # Policy doc for S3 Audit bucket data "aws_iam_policy_document" "s3_audit_bucket_policy" { statement { - sid = "AllowSSLRequestsOnly" + sid = "AllowSSLRequestsOnly" actions = ["s3:*"] resources = [ module.s3_audit_bucket.storage_bucket_arn, @@ -114,7 +114,7 @@ data "aws_iam_policy_document" "s3_audit_bucket_policy" { ] condition { test = "Bool" - values = ["true"] + values = ["true"] variable = "aws:SecureTransport" } } @@ -136,10 +136,10 @@ data "aws_iam_policy_document" "dynamodb_kms_key_policy" { sid = "EnableIamUserPermissions" effect = "Allow" principals { - type = "AWS" + type = "AWS" identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] } - actions = ["kms:*"] + actions = ["kms:*"] resources = ["*"] } @@ -147,7 +147,7 @@ data "aws_iam_policy_document" "dynamodb_kms_key_policy" { sid = "AllowLambdaDecrypt" effect = "Allow" principals { - type = "AWS" + type = "AWS" identifiers = [aws_iam_role.eligibility_lambda_role.arn] } actions = [ @@ -174,10 +174,10 @@ data "aws_iam_policy_document" "s3_rules_kms_key_policy" { sid = "EnableIamUserPermissions" effect = "Allow" principals { - type = "AWS" + type = "AWS" identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] } - actions = ["kms:*"] + actions = ["kms:*"] resources = ["*"] } @@ -185,10 +185,10 @@ data "aws_iam_policy_document" "s3_rules_kms_key_policy" { sid = "AllowLambdaDecrypt" effect = "Allow" principals { - type = "AWS" + type = "AWS" identifiers = [aws_iam_role.eligibility_lambda_role.arn] } - actions = ["kms:Decrypt"] + actions = ["kms:Decrypt"] resources = ["*"] } } @@ -207,10 +207,10 @@ data "aws_iam_policy_document" "s3_audit_kms_key_policy" { sid = "EnableIamUserPermissions" effect = "Allow" principals { - type = "AWS" + type = "AWS" identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] } - actions = ["kms:*"] + actions = ["kms:*"] resources = ["*"] } @@ -218,7 +218,7 @@ data "aws_iam_policy_document" "s3_audit_kms_key_policy" { sid = "AllowLambdaFullWrite" effect = "Allow" principals { - type = "AWS" + type = "AWS" identifiers = [aws_iam_role.eligibility_lambda_role.arn, aws_iam_role.eligibility_audit_firehose_role.arn] } actions = [ @@ -241,8 +241,12 @@ data "aws_iam_policy_document" "lambda_firehose_write_policy" { sid = "AllowLambdaToPutToFirehose" effect = "Allow" actions = [ + "firehose:StartDeliveryStreamEncryption", + "firehose:StopDeliveryStreamEncryption", "firehose:PutRecord", - "firehose:PutRecordBatch" + "firehose:PutRecordBatch", + "firehose:DescribeDeliveryStream", + "firehose:ListDeliveryStreams" ] resources = [ "arn:aws:firehose:${var.default_aws_region}:${data.aws_caller_identity.current.account_id}:deliverystream/${module.eligibility_audit_firehose_delivery_stream.firehose_stream_name}" diff --git a/infrastructure/stacks/api-layer/truststore_s3_bucket.tf b/infrastructure/stacks/api-layer/truststore_s3_bucket.tf index d78ad6f9c..4e6bc5ccf 100644 --- a/infrastructure/stacks/api-layer/truststore_s3_bucket.tf +++ b/infrastructure/stacks/api-layer/truststore_s3_bucket.tf @@ -18,7 +18,7 @@ data "aws_iam_policy_document" "truststore_api_gateway" { effect = "Allow" principals { - type = "Service" + type = "Service" identifiers = ["apigateway.amazonaws.com"] } @@ -31,9 +31,53 @@ data "aws_iam_policy_document" "truststore_api_gateway" { } resource "aws_s3_object" "pem_file" { - bucket = module.s3_truststore_bucket.storage_bucket_name - key = "truststore.pem" - content = local.pem_file_content + bucket = module.s3_truststore_bucket.storage_bucket_name + key = "truststore.pem" + content = local.pem_file_content + acl = "private" + kms_key_id = module.s3_truststore_bucket.storage_bucket_kms_key_arn - acl = "private" + depends_on = [module.s3_truststore_bucket.storage_bucket_versioning_config] } + + +resource "aws_kms_key_policy" "storage_bucket_cmk" { + key_id = module.s3_truststore_bucket.storage_bucket_kms_key_id + policy = data.aws_iam_policy_document.trust_store_kms_policy.json +} + +data "aws_iam_policy_document" "trust_store_kms_policy" { + #checkov:skip=CKV_AWS_111: Root user needs full KMS key management + #checkov:skip=CKV_AWS_356: Root user needs full KMS key management + #checkov:skip=CKV_AWS_109: Root user needs full KMS key management + statement { + sid = "AllowRootAccountFullAccess" + effect = "Allow" + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" + ] + } + + actions = ["kms:*"] + resources = ["*"] + } + + # 2. Allow API Gateway to decrypt truststore + statement { + sid = "APIGatewayS3TruststoreDecrypt" + effect = "Allow" + principals { + type = "Service" + identifiers = [ + "apigateway.amazonaws.com", + "apigateway.${var.default_aws_region}.amazonaws.com" + ] + } + actions = ["kms:Decrypt"] + resources = ["*"] + } +} + diff --git a/infrastructure/stacks/iams-developer-roles/github_actions_policies.tf b/infrastructure/stacks/iams-developer-roles/github_actions_policies.tf index 6ea409dad..2cf94cf87 100644 --- a/infrastructure/stacks/iams-developer-roles/github_actions_policies.tf +++ b/infrastructure/stacks/iams-developer-roles/github_actions_policies.tf @@ -456,7 +456,9 @@ resource "aws_iam_policy" "firehose_readonly" { "firehose:PutRecordBatch", "firehose:TagDeliveryStream", "firehose:ListTagsForDeliveryStream", - "firehose:UntagDeliveryStream" + "firehose:UntagDeliveryStream", + "firehose:StartDeliveryStreamEncryption", + "firehose:StopDeliveryStreamEncryption" ] Resource = "arn:aws:firehose:${var.default_aws_region}:${data.aws_caller_identity.current.account_id}:deliverystream/eligibility-signposting-api*" }