diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index b2f26a3..72edfd3 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -92,13 +92,24 @@ jobs: exit $exit_code - tfsec: - name: tfsec + trivy: + name: trivy runs-on: ubuntu-latest steps: - name: Checkout source code uses: actions/checkout@v4 - - name: Runs tfsec - uses: aquasecurity/tfsec-action@v1.0.0 + - name: Run Trivy vulnarability scanner + uses: aquasecurity/trivy-action@master with: - additional_args: --config-file tfsec.yml + scan-type: config + ignore-unfixed: true + skip-dirs: '"**/*/.terraform"' + exit-code: 1 + format: sarif + output: 'trivy-results.sarif' + + - name: Parse SARIF file + if: always() + uses: Ayrx/sarif_to_github_annotations@v0.2.2 + with: + sarif_file: 'trivy-results.sarif' diff --git a/README.md b/README.md index 787a4f5..415f48c 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,10 @@ terraform. |----------|--------------------------------------|--------------------------------------------------------------------------| | AWS | [backend][aws-backend] | S3 storage backend for tfstate. | | AWS | [cloudfront_waf][aws-cloudfront-waf] | CloudFront distribution that passes traffic through WAF without caching. | +| AWS | [logging][aws-logging] | CloudFront distribution that passes traffic through WAF without caching. | [aws-backend]: ./aws/backend/README.md [aws-cloudfront-waf]: ./aws/cloudfront_waf/README.md +[aws-logging]: ./aws/logging/README.md [opentofu]: https://opentofu.org/ [terraform]: https://www.terraform.io/ diff --git a/aws/cloudfront_waf/README.md b/aws/cloudfront_waf/README.md index 78575fb..9623bd3 100644 --- a/aws/cloudfront_waf/README.md +++ b/aws/cloudfront_waf/README.md @@ -8,13 +8,13 @@ Web Application Firewall (WAF) _without_ caching. Add this module to your `main.tf` (or appropriate) file: ```hcl -module "backend" { - source = "github.com/codeforamerica/tofu-modules/aws/backend" +module "cloudfront_waf" { + source = "github.com/codeforamerica/tofu-modules/aws/cloudfront_waf" project = "my-project" environment = "dev" domain = "my-project.org" - log_bucket = module.log_bucket.s3_bucket_bucket_domain_name + log_bucket = module.logging.bucket } ``` diff --git a/aws/logging/README.md b/aws/logging/README.md new file mode 100644 index 0000000..93ec6a3 --- /dev/null +++ b/aws/logging/README.md @@ -0,0 +1,51 @@ +# AWS Logging Module + +This module created an S3 bucket for logging, as well as a KMS for CloudWatch +logs. + +_Note: The bucket created by this module uses AES256 encryption. CMKs (Customer +Managed Keys) are [not supported] for access logging._ + +## Usage + +Add this module to your `main.tf` (or appropriate) file: + +```hcl +module "cloudfront_waf" { + source = "github.com/codeforamerica/tofu-modules/aws/logging" + + project = "my-project" + environment = "dev" +} +``` + +Make sure you re-run `tofu init` after adding the module to your configuration. + +```bash +tofu init +tofu plan +``` + +To update the source for this module, pass `-upgrade` to `tofu init`: + +```bash +tofu init -upgrade +``` + +## Inputs + +| Name | Description | Type | Default | Required | +|---------------------|-------------------------------------------------------|----------|---------|----------| +| project | Name of the project. | `string` | n/a | yes | +| environment | Environment for the project. | `string` | `"dev"` | no | +| key_recovery_period | Number of days to recover the KMS key after deletion. | `number` | 30 | yes | + +## Outputs + +| Name | Description | Type | +|---------------|------------------------------------|----------| +| bucket | Name of the S3 bucket for logging. | `string` | +| kms_key_alias | Alias of the KMS encryption key. | `string` | +| kms_key_arn | ARN of the KMS encryption key. | `string` | + +[not supported]: https://repost.aws/knowledge-center/s3-server-access-log-not-delivered diff --git a/aws/logging/data.tf b/aws/logging/data.tf new file mode 100644 index 0000000..6c9ddc6 --- /dev/null +++ b/aws/logging/data.tf @@ -0,0 +1,7 @@ +data "aws_caller_identity" "identity" {} + +data "aws_partition" "current" {} + +data "aws_region" "current" {} + +data "aws_elb_service_account" "current" {} diff --git a/aws/logging/local.tf b/aws/logging/local.tf new file mode 100644 index 0000000..512ed02 --- /dev/null +++ b/aws/logging/local.tf @@ -0,0 +1,3 @@ +locals { + prefix = "${var.project}-${var.environment}" +} diff --git a/aws/logging/main.tf b/aws/logging/main.tf new file mode 100644 index 0000000..1c4c38e --- /dev/null +++ b/aws/logging/main.tf @@ -0,0 +1,71 @@ +# We don't want to log access to this bucket, as that would cause an infinite +# loop of logging. +#trivy:ignore:avd-aws-0089 +resource "aws_s3_bucket" "logs" { + bucket = "${local.prefix}-logs" + + lifecycle { + prevent_destroy = true + } +} + +resource "aws_s3_bucket_public_access_block" "good_example" { + bucket = aws_s3_bucket.logs.id + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +#tfsec:ignore:aws-s3-encryption-customer-key +resource "aws_s3_bucket_server_side_encryption_configuration" "logs" { + bucket = aws_s3_bucket.logs.id + + rule { + bucket_key_enabled = true + + apply_server_side_encryption_by_default { + # S3 access logs don't support encryption with a customer managed key + # (CMK). + # See https://repost.aws/knowledge-center/s3-server-access-log-not-delivered + sse_algorithm = "AES256" + } + } +} + +resource "aws_s3_bucket_versioning" "logs" { + bucket = aws_s3_bucket.logs.id + + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_policy" "logs" { + bucket = aws_s3_bucket.logs.id + policy = jsonencode(yamldecode(templatefile("${path.module}/templates/bucket-policy.yaml.tftpl", { + account_id : data.aws_caller_identity.identity.account_id, + partition : data.aws_partition.current.partition, + bucket_arn : aws_s3_bucket.logs.arn, + elb_account_arn : data.aws_elb_service_account.current.arn + }))) +} + +resource "aws_kms_key" "logs" { + description = "Logging encryption key for ${var.project} ${var.environment}" + deletion_window_in_days = var.key_recovery_period + enable_key_rotation = true + policy = jsonencode(yamldecode(templatefile("${path.module}/templates/key-policy.yaml.tftpl", { + account_id : data.aws_caller_identity.identity.account_id + partition : data.aws_partition.current.partition + region : data.aws_region.current.name + bucket_arn : aws_s3_bucket.logs.arn + project : var.project + environment : var.environment + }))) +} + +resource "aws_kms_alias" "logs" { + name = "alias/${var.project}/${var.environment}/logs" + target_key_id = aws_kms_key.logs.id +} diff --git a/aws/logging/output.tf b/aws/logging/output.tf new file mode 100644 index 0000000..f42d525 --- /dev/null +++ b/aws/logging/output.tf @@ -0,0 +1,11 @@ +output "bucket" { + value = aws_s3_bucket.logs.bucket +} + +output "kms_key_arn" { + value = aws_kms_key.logs.arn +} + +output "kms_key_alias" { + value = aws_kms_alias.logs.name +} diff --git a/aws/logging/templates/bucket-policy.yaml.tftpl b/aws/logging/templates/bucket-policy.yaml.tftpl new file mode 100644 index 0000000..cc7505d --- /dev/null +++ b/aws/logging/templates/bucket-policy.yaml.tftpl @@ -0,0 +1,41 @@ +Version: '2012-10-17' +Statement: +- Sid: AllowSSLRequestsOnly + Effect: Deny + Principal: + Service: config.amazonaws.com + Action: + - s3:* + Resource: + - "${bucket_arn}" + - "${bucket_arn}/*" + Condition: + Bool: + aws:SecureTransport: false +- Sid: AllowLBAccessLogsFromAWS + Effect: Allow + Principal: + AWS: "${elb_account_arn}" + Action: s3:PutObject + Resource: "${bucket_arn}/AWSLogs/${account_id}/*" +- Sid: AWSLogDeliveryWrite + Effect: Allow + Principal: + Service: delivery.logs.amazonaws.com + Action: s3:PutObject + Resource: "${bucket_arn}/AWSLogs/${account_id}/*" + Condition: + StringEquals: + s3:x-amz-acl: bucket-owner-full-control +- Sid: AWSLogDeliveryAclCheck + Effect: Allow + Principal: + Service: delivery.logs.amazonaws.com + Action: s3:GetBucketAcl + Resource: "${bucket_arn}" +- Sid: AllowS3AccessLogs + Effect: Allow + Principal: + Service: logging.s3.amazonaws.com + Action: s3:PutObject + Resource: "${bucket_arn}/*" diff --git a/aws/logging/templates/key-policy.yaml.tftpl b/aws/logging/templates/key-policy.yaml.tftpl new file mode 100644 index 0000000..84f49e2 --- /dev/null +++ b/aws/logging/templates/key-policy.yaml.tftpl @@ -0,0 +1,40 @@ +Version: '2012-10-17' +Id: key-default-1 +Statement: +- Sid: Enable IAM User Permissions + Effect: Allow + Principal: + AWS: arn:${partition}:iam::${account_id}:root + Action: kms:* + Resource: "*" +- Sid: Allow CloudWatch to encrypt logs + Effect: Allow + Principal: + Service: logs.${region}.amazonaws.com + Action: + - kms:Encrypt* + - kms:Decrypt* + - kms:ReEncrypt* + - kms:GenerateDataKey* + - kms:Describe* + Resource: "*" + Condition: + ArnLike: + kms:EncryptionContext:aws:logs:arn: arn:${partition}:logs:${region}:${account_id}:log-group:* +- Sid: Allow RDS Performance Insights + Effect: Allow + Principal: + AWS: + - arn:${partition}:iam::${account_id}:root + Action: + - kms:Decrypt + - kms:GenerateDataKey + Resource: "*" + Condition: + StringEquals: + kms:ViaService: rds.${region}.amazonaws.com + aws:RequestTag/project: ${project} + aws:RequestTag/environment: ${environment} + ForAnyValue:StringLike: + kms:EncryptionContext:aws:pi:service: rds + kms:EncryptionContext:service: pi diff --git a/aws/logging/variables.tf b/aws/logging/variables.tf new file mode 100644 index 0000000..0d7cfbc --- /dev/null +++ b/aws/logging/variables.tf @@ -0,0 +1,21 @@ +variable "environment" { + type = string + description = "Environment for the deployment." + default = "dev" +} + +variable "key_recovery_period" { + type = number + default = 30 + description = "Recovery period for deleted KMS keys in days. Must be between 7 and 30." + + validation { + condition = var.key_recovery_period > 6 && var.key_recovery_period < 31 + error_message = "Recovery period must be between 7 and 30." + } +} + +variable "project" { + type = string + description = "Project that these resources are supporting." +} diff --git a/aws/logging/versions.tf b/aws/logging/versions.tf new file mode 100644 index 0000000..ff09141 --- /dev/null +++ b/aws/logging/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.6" + + required_providers { + aws = { + version = ">= 5.44" + source = "hashicorp/aws" + } + } +}