From c916fc8efe42b7a569c195c3ef456199c8548a69 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Sun, 23 Mar 2025 18:26:00 -0400 Subject: [PATCH 01/10] Add s3 backend --- terraform/backend/s3/.terraform.lock.hcl | 46 ++++ terraform/backend/s3/main.tf | 257 +++++++++++++++++++++++ terraform/backend/s3/outputs.tf | 19 ++ terraform/backend/s3/variables.tf | 183 ++++++++++++++++ 4 files changed, 505 insertions(+) create mode 100644 terraform/backend/s3/.terraform.lock.hcl create mode 100644 terraform/backend/s3/main.tf create mode 100644 terraform/backend/s3/outputs.tf create mode 100644 terraform/backend/s3/variables.tf diff --git a/terraform/backend/s3/.terraform.lock.hcl b/terraform/backend/s3/.terraform.lock.hcl new file mode 100644 index 00000000..b3c31f49 --- /dev/null +++ b/terraform/backend/s3/.terraform.lock.hcl @@ -0,0 +1,46 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.90.0" + constraints = "5.90.0" + hashes = [ + "h1:4zF/N14sv3ZMo/Goa4s1B/LR+2SmK2cEZqoqO7+2F8Q=", + "h1:cJ3ab7uBP0NmD+LzxHK63ZG1o9nIppAjt6c0OafGKPw=", + "zh:0ed246595c4ffb3ea3649528ff171503db208fb20be5f750b8e359d17ee72b60", + "zh:1d5c500913b5df0fbf5e8143354aecc736cc4e66d58d4ab17deb24b721ab743a", + "zh:337f3511335e6e32431548913d1973ae077d1a4c2f77677675c92c60cd2f5e0a", + "zh:624762ff78819aee434d6c3e6c79eb93c91060be2df4f45f9014272a60b5d608", + "zh:7f4ab9bcd667e38b7d7b7aa1068535f01eef3656ecd422acccbe8238d377a15a", + "zh:84542ce0403cacee245c1a159169cc0ddb965d7d734216f9eb0bb3ff0a0bae36", + "zh:85dd27e39f2c3ab13cb5c02236b810893bd90ec6da33fabaa7ab6d116accfa10", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a0cf76959ade91958b08d186f5bcdc403395fa635f21912464da40bc7a5db4ff", + "zh:a9a48f9f7f4122b6a44b7273b4cc54020887f7346f50286d7da1278cca2ee952", + "zh:c119b826e334aac2d03ea561774dad536ccd6449e2a4f42b3af100623ae02679", + "zh:d4204ca7f1295732660c70db4ea04c3ae1f7e1ac82c0ec9d0dc549493bc45e7a", + "zh:d95f89181d12ebab1b1f964274d29795e1e6e2d112ea97caffd8a7f1326a922d", + "zh:e529c7be1037f1a9a733fc0bcbbdcc58fc44f85ed343f891e5c584b2ef56fd5c", + "zh:e541c135514a6727f20410a9a52c06cb71b4ddadaf2a41da28d599fb1c442845", + ] +} + +provider "registry.terraform.io/hashicorp/local" { + version = "2.5.2" + hashes = [ + "h1:6XyefmvbkprppmYbGmMcQW5NB4w6C363SSShzuhF4R0=", + "h1:IyFbOIO6mhikFNL/2h1iZJ6kyN3U00jgkpCLUCThAfE=", + "zh:136299545178ce281c56f36965bf91c35407c11897f7082b3b983d86cb79b511", + "zh:3b4486858aa9cb8163378722b642c57c529b6c64bfbfc9461d940a84cd66ebea", + "zh:4855ee628ead847741aa4f4fc9bed50cfdbf197f2912775dd9fe7bc43fa077c0", + "zh:4b8cd2583d1edcac4011caafe8afb7a95e8110a607a1d5fb87d921178074a69b", + "zh:52084ddaff8c8cd3f9e7bcb7ce4dc1eab00602912c96da43c29b4762dc376038", + "zh:71562d330d3f92d79b2952ffdda0dad167e952e46200c767dd30c6af8d7c0ed3", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:805f81ade06ff68fa8b908d31892eaed5c180ae031c77ad35f82cb7a74b97cf4", + "zh:8b6b3ebeaaa8e38dd04e56996abe80db9be6f4c1df75ac3cccc77642899bd464", + "zh:ad07750576b99248037b897de71113cc19b1a8d0bc235eb99173cc83d0de3b1b", + "zh:b9f1c3bfadb74068f5c205292badb0661e17ac05eb23bfe8bd809691e4583d0e", + "zh:cc4cbcd67414fefb111c1bf7ab0bc4beb8c0b553d01719ad17de9a047adff4d1", + ] +} diff --git a/terraform/backend/s3/main.tf b/terraform/backend/s3/main.tf new file mode 100644 index 00000000..1067e4ef --- /dev/null +++ b/terraform/backend/s3/main.tf @@ -0,0 +1,257 @@ +#--------------------------------------------------------------------------------------------------- +# Providers Configuration +# This section defines the required providers for the Terraform configuration. +# It specifies the AWS provider with a version constraint, ensuring compatibility +# with the AWS services used in this setup. +#--------------------------------------------------------------------------------------------------- + +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "5.90.0" + } + } +} + +provider "aws" { + region = var.region + default_tags { + tags = merge( + var.tags, + { + ManagedBy = "Terraform" + } + ) + } +} + +data "aws_caller_identity" "current" {} + +#--------------------------------------------------------------------------------------------------- +# S3 Bucket Creation +# This section creates the S3 bucket used for storing Terraform state. +# It ensures that the bucket is unique per account and region. +#--------------------------------------------------------------------------------------------------- + +resource "aws_s3_bucket" "this" { + bucket = var.s3_bucket_name != "" ? var.s3_bucket_name : local.default_s3_bucket_name + + tags = { + Name = var.s3_bucket_name != "" ? var.s3_bucket_name : local.default_s3_bucket_name + } +} + +#--------------------------------------------------------------------------------------------------- +# S3 Bucket Configuration +# This section defines local variables for S3 bucket naming conventions and policy statements. +# It ensures that the bucket names are unique per account and enforces security policies +# like HTTPS and encryption for data in transit and at rest. +#--------------------------------------------------------------------------------------------------- + +locals { + account_id_hash = substr(md5(data.aws_caller_identity.current.account_id), 0, 8) + default_s3_bucket_name = "terraform-state-${local.account_id_hash}" + log_bucket_name = var.s3_log_bucket_name != "" ? var.s3_log_bucket_name : (var.s3_bucket_name != "" ? "${var.s3_bucket_name}-logs" : "terraform-state-logs-${local.account_id_hash}") + + bucket_policy_statements = flatten([ + var.bucket_policy_enforce_https ? [ + { + Sid = "enforceHttps", + Effect = "Deny", + Principal = "*", + Action = "s3:*", + Resource = [ + aws_s3_bucket.this.arn, + "${aws_s3_bucket.this.arn}/*" + ], + Condition = { + Bool = { + "aws:SecureTransport" = "false" + } + } + } + ] : [], + var.bucket_policy_enforce_encryption ? [ + { + Sid = "enforceEncryptionMethod", + Principal = "*", + Effect = "Deny", + Action = [ + "s3:PutObject" + ], + Resource = [ + "${aws_s3_bucket.this.arn}/*" + ], + Condition = { + StringNotEquals = { + "s3:x-amz-server-side-encryption" = [ + "AES256", + "aws:kms" + ] + } + } + } + ] : [] + ]) +} + +#--------------------------------------------------------------------------------------------------- +# Logging Configuration +# This section sets up logging for the S3 bucket. It creates a logging bucket if enabled, +# and configures the target bucket and prefix for storing access logs, which is crucial +# for monitoring and auditing access to the S3 bucket. +#--------------------------------------------------------------------------------------------------- + +resource "aws_s3_bucket_logging" "this" { + count = var.enable_log_bucket ? 1 : 0 + bucket = aws_s3_bucket.this.id + + target_bucket = aws_s3_bucket.this.id + target_prefix = aws_s3_bucket.this.id +} + +#--------------------------------------------------------------------------------------------------- +# Encryption +# This section configures server-side encryption for the S3 bucket using either KMS or AES256. +# It ensures that all objects stored in the bucket are encrypted, enhancing data security. +#--------------------------------------------------------------------------------------------------- + +resource "aws_s3_bucket_server_side_encryption_configuration" "this" { + bucket = aws_s3_bucket.this.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = var.enable_kms && length(aws_kms_key.terraform_state) > 0 ? "aws:kms" : "AES256" + kms_master_key_id = var.enable_kms && length(aws_kms_key.terraform_state) > 0 ? aws_kms_key.terraform_state[0].arn : null + } + } +} + +resource "aws_s3_bucket_policy" "this" { + count = var.enable_bucket_policy ? 1 : 0 + bucket = aws_s3_bucket.this.id + + policy = var.custom_bucket_policy != "" ? var.custom_bucket_policy : jsonencode({ + Version = "2012-10-17", + Statement = local.bucket_policy_statements + }) +} + +#--------------------------------------------------------------------------------------------------- +# Versioning +# This section enables versioning on the S3 bucket, allowing for the preservation, +# retrieval, and restoration of every version of every object stored in the bucket. +#--------------------------------------------------------------------------------------------------- + +resource "aws_s3_bucket_versioning" "this" { + count = var.enable_versioning ? 1 : 0 + + bucket = aws_s3_bucket.this.id + versioning_configuration { + status = "Enabled" + } +} + +#--------------------------------------------------------------------------------------------------- +# Public Access Block +# This section configures the public access block settings for the S3 bucket, +# preventing public access to the bucket and its objects, thereby enhancing security. +#--------------------------------------------------------------------------------------------------- + +resource "aws_s3_bucket_public_access_block" "this" { + bucket = aws_s3_bucket.this.id + + block_public_acls = var.public_access_block.block_public_acls + block_public_policy = var.public_access_block.block_public_policy + ignore_public_acls = var.public_access_block.ignore_public_acls + restrict_public_buckets = var.public_access_block.restrict_public_buckets +} + +#--------------------------------------------------------------------------------------------------- +# DynamoDB Table for Locking +# This section creates a DynamoDB table used for state locking and consistency checking +# during Terraform operations, ensuring that only one process can modify the state at a time. +#--------------------------------------------------------------------------------------------------- + +resource "aws_dynamodb_table" "terraform_locks" { + count = var.enable_dynamodb ? 1 : 0 + + name = var.dynamodb_table_name != "" ? var.dynamodb_table_name : "terraform-state-locks-${local.account_id_hash}" + billing_mode = var.dynamodb_billing_mode + hash_key = var.dynamodb_lock_key + + attribute { + name = var.dynamodb_lock_key + type = "S" + } + + point_in_time_recovery { + enabled = var.dynamodb_pti_enabled + } +} + +#--------------------------------------------------------------------------------------------------- +# KMS Key Policy Document for Terraform State Encryption +# This section defines the IAM policy document for the KMS key used to encrypt the Terraform state. +# It grants necessary permissions to the AWS account root user for managing the KMS key. +#--------------------------------------------------------------------------------------------------- + +data "aws_iam_policy_document" "terraform_state_kms_policy" { + statement { + sid = "EnableIAMUserPermissions" + effect = "Allow" + + principals { + type = "AWS" + identifiers = [ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" ] + } + + actions = [ "kms:*" ] + resources = [ "*" ] + } +} + +#--------------------------------------------------------------------------------------------------- +# KMS Key for Terraform State Encryption (if not provided externally) +# This section creates a KMS key for encrypting the Terraform state file in S3, +# if an external KMS key is not provided. It includes key rotation and deletion settings. +#--------------------------------------------------------------------------------------------------- + +resource "aws_kms_key" "terraform_state" { + count = var.enable_kms && var.kms_key_alias == "" ? 1 : 0 + + description = "KMS key for encrypting Terraform state file in S3" + enable_key_rotation = var.kms_enable_key_rotation + deletion_window_in_days = var.kms_deletion_window + policy = data.aws_iam_policy_document.terraform_state_kms_policy.json +} + +resource "aws_kms_alias" "terraform_state_alias" { + count = var.enable_kms && var.kms_key_alias == "" ? 1 : 0 + + name = var.kms_key_alias != "" ? var.kms_key_alias : "alias/terraform-state-${local.account_id_hash}" + target_key_id = aws_kms_key.terraform_state[0].key_id +} + +#--------------------------------------------------------------------------------------------------- +# Backend Configuration Output +# This section outputs the backend configuration to a local file in tfvars format +#--------------------------------------------------------------------------------------------------- + +resource "local_file" "backend_config" { + count = 1 + + content = <= 7 && var.kms_deletion_window <= 30 + error_message = "The KMS deletion window must be between 7 and 30 days." + } +} + +variable "kms_enable_key_rotation" { + description = "Flag to enable automatic key rotation for the KMS key" + type = bool + default = true +} + +#--------------------------------------------------------------------------------------------------- +# S3 Bucket Policies and Versioning +#--------------------------------------------------------------------------------------------------- + +variable "enable_bucket_policy" { + description = "Flag to enable the S3 bucket policy" + type = bool + default = true +} + +variable "bucket_policy_enforce_https" { + description = "Whether to include the HTTPS enforcement in the bucket policy" + type = bool + default = true +} + +variable "bucket_policy_enforce_encryption" { + description = "Whether to enforce server side encryption in the bucket policy" + type = bool + default = true +} + +variable "custom_bucket_policy" { + description = "If provided, overrides the default computed S3 bucket policy." + type = string + default = "" +} + +variable "enable_versioning" { + description = "Flag to enable bucket versioning on the S3 state bucket" + type = bool + default = true +} + +#--------------------------------------------------------------------------------------------------- +# Public Access Block +#--------------------------------------------------------------------------------------------------- + +variable "public_access_block" { + description = "Public access block configuration for the S3 bucket" + type = object({ + block_public_acls : bool, + block_public_policy : bool, + ignore_public_acls : bool, + restrict_public_buckets : bool, + }) + default = { + block_public_acls = true, + block_public_policy = true, + ignore_public_acls = true, + restrict_public_buckets = true + } +} + +#--------------------------------------------------------------------------------------------------- +# Feature Flags and Tags +#--------------------------------------------------------------------------------------------------- + +variable "enable_dynamodb" { + description = "Feature flag to enable DynamoDB table creation" + type = bool + default = true +} + +variable "enable_kms" { + description = "Feature flag to enable KMS encryption" + type = bool + default = true +} + +variable "enable_log_bucket" { + description = "Feature flag to enable log bucket creation" + type = bool + default = true +} + +variable "tags" { + description = "Additional tags to apply to resources (default is empty)." + type = map(string) + default = {} +} From 9ea3fd6bdc74b408cb5aac0cc079987f6ab420f8 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Mon, 31 Mar 2025 20:18:01 -0400 Subject: [PATCH 02/10] wip --- .gitignore | 1 + contexts/local-colima/blueprint.yaml | 23 +++++++- .../terraform/cluster/talos.tfvars | 26 -------- terraform/backend/s3/main.tf | 55 +++++++++++++++-- windsor.yaml | 59 +++++++++++++++++++ 5 files changed, 129 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 3d37ca5f..abd60f70 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ contexts/**/.tfstate/ contexts/**/.kube/ contexts/**/.talos/ contexts/**/.aws/ +contexts/**/.omni/ diff --git a/contexts/local-colima/blueprint.yaml b/contexts/local-colima/blueprint.yaml index 60ea4880..b93ab3c1 100644 --- a/contexts/local-colima/blueprint.yaml +++ b/contexts/local-colima/blueprint.yaml @@ -8,21 +8,30 @@ repository: ref: branch: main secretName: flux-system -sources: [] +sources: +- name: core + url: github.com/windsorcli/core + ref: + branch: main terraform: -- path: cluster/talos -- path: gitops/flux +- source: core + path: cluster/talos +- source: core + path: gitops/flux kustomize: - name: policy-base path: policy/base + source: core components: - kyverno - name: policy-resources path: policy/resources + source: core dependsOn: - policy-base - name: csi path: csi + source: core dependsOn: - policy-resources force: true @@ -31,6 +40,7 @@ kustomize: - openebs/dynamic-localpv - name: lb-base path: lb/base + source: core dependsOn: - policy-resources force: true @@ -38,6 +48,7 @@ kustomize: - metallb - name: lb-resources path: lb/resources + source: core dependsOn: - lb-base force: true @@ -45,6 +56,7 @@ kustomize: - metallb/layer2 - name: ingress-base path: ingress/base + source: core dependsOn: - pki-resources force: true @@ -56,6 +68,7 @@ kustomize: - nginx/web - name: pki-base path: pki/base + source: core dependsOn: - policy-resources force: true @@ -64,6 +77,7 @@ kustomize: - trust-manager - name: pki-resources path: pki/resources + source: core dependsOn: - pki-base force: true @@ -72,6 +86,7 @@ kustomize: - public-issuer/selfsigned - name: dns path: dns + source: core dependsOn: - ingress-base - pki-base @@ -84,6 +99,7 @@ kustomize: - external-dns/ingress - name: gitops path: gitops/flux + source: core dependsOn: - ingress-base force: true @@ -91,6 +107,7 @@ kustomize: - webhook - name: demo path: demo/bookinfo + source: core dependsOn: - ingress-base force: true diff --git a/contexts/local-colima/terraform/cluster/talos.tfvars b/contexts/local-colima/terraform/cluster/talos.tfvars index bdc09e9e..108fefd2 100644 --- a/contexts/local-colima/terraform/cluster/talos.tfvars +++ b/contexts/local-colima/terraform/cluster/talos.tfvars @@ -1,28 +1,2 @@ // Managed by Windsor CLI: This file is partially managed by the windsor CLI. Your changes will not be overwritten. // Module source: github.com/windsorcli/core//terraform/cluster/talos?ref=main - -// The external controlplane API endpoint of the kubernetes API -cluster_endpoint = "https://controlplane-1.test:6443" - -// The name of the cluster -cluster_name = "talos" - -// A YAML string of common config patches to apply -common_config_patches = "\"cluster\":\n \"apiServer\":\n \"certSANs\":\n - \"localhost\"\n - \"controlplane-1.test\"\n - \"10.5.0.2\"\n \"extraManifests\":\n - \"https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/v0.8.7/deploy/standalone-install.yaml\"\n\"machine\":\n \"certSANs\":\n - \"localhost\"\n - \"controlplane-1.test\"\n - \"10.5.0.2\"\n \"features\":\n \"hostDNS\":\n \"forwardKubeDNSToHost\": true\n \"kubelet\":\n \"extraArgs\":\n \"rotate-server-certificates\": \"true\"\n \"network\": {}\n \"registries\":\n \"mirrors\":\n \"docker.io\":\n \"endpoints\":\n - \"http://registry-1.docker.test:5000\"\n \"gcr.io\":\n \"endpoints\":\n - \"http://gcr.test:5000\"\n \"ghcr.io\":\n \"endpoints\":\n - \"http://ghcr.test:5000\"\n \"quay.io\":\n \"endpoints\":\n - \"http://quay.test:5000\"\n \"registry.k8s.io\":\n \"endpoints\":\n - \"http://registry.k8s.test:5000\"\n \"registry.test\":\n \"endpoints\":\n - \"http://registry.test:5000\"" - -// Machine config details for control planes -controlplanes = [{ - endpoint = "controlplane-1.test" - hostname = "controlplane-1.test" - node = "controlplane-1.test" -}] - -// A YAML string of worker config patches to apply -worker_config_patches = "\"machine\":\n \"kubelet\":\n \"extraMounts\":\n - \"destination\": \"/var/local\"\n \"options\":\n - \"rbind\"\n - \"rw\"\n \"source\": \"/var/local\"\n \"type\": \"bind\"" - -// Machine config details for workers -workers = [{ - endpoint = "worker-1.test" - hostname = "worker-1.test" - node = "worker-1.test" -}] diff --git a/terraform/backend/s3/main.tf b/terraform/backend/s3/main.tf index 1067e4ef..ce5a64f5 100644 --- a/terraform/backend/s3/main.tf +++ b/terraform/backend/s3/main.tf @@ -175,6 +175,7 @@ resource "aws_s3_bucket_public_access_block" "this" { #--------------------------------------------------------------------------------------------------- resource "aws_dynamodb_table" "terraform_locks" { + # checkov:skip=CKV_AWS_119:Encryption is not necessary for this DynamoDB table as it is used solely for Terraform state locking, which does not involve sensitive data. count = var.enable_dynamodb ? 1 : 0 name = var.dynamodb_table_name != "" ? var.dynamodb_table_name : "terraform-state-locks-${local.account_id_hash}" @@ -194,12 +195,13 @@ resource "aws_dynamodb_table" "terraform_locks" { #--------------------------------------------------------------------------------------------------- # KMS Key Policy Document for Terraform State Encryption # This section defines the IAM policy document for the KMS key used to encrypt the Terraform state. -# It grants necessary permissions to the AWS account root user for managing the KMS key. +# It grants necessary permissions to the AWS account root user for managing the KMS key, +# while ensuring that write access is constrained to specific actions. #--------------------------------------------------------------------------------------------------- data "aws_iam_policy_document" "terraform_state_kms_policy" { statement { - sid = "EnableIAMUserPermissions" + sid = "AllowKeyAdministration" effect = "Allow" principals { @@ -207,8 +209,49 @@ data "aws_iam_policy_document" "terraform_state_kms_policy" { identifiers = [ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" ] } - actions = [ "kms:*" ] - resources = [ "*" ] + 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.terraform_state[0].arn}" ] + } + + statement { + sid = "AllowKeyUsage" + effect = "Allow" + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" + ] + } + + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ] + resources = [ "${aws_kms_key.terraform_state[0].arn}" ] + condition { + test = "StringEquals" + variable = "kms:CallerAccount" + values = [ "${data.aws_caller_identity.current.account_id}" ] + } } } @@ -245,8 +288,8 @@ resource "local_file" "backend_config" { content = < 0 ? "kms_key_id = \"" + aws_kms_key.terraform_state[0].arn + "\"" : ""} +${var.dynamodb_table_name != "" ? "dynamodb_table = \"" + var.dynamodb_table_name + "\"" : ""} EOF filename = "${var.context_path}/terraform/backend.tfvars" diff --git a/windsor.yaml b/windsor.yaml index 1598cb77..2c840543 100644 --- a/windsor.yaml +++ b/windsor.yaml @@ -1,6 +1,65 @@ version: v1alpha1 contexts: + local: + docker: + enabled: true + registries: + gcr.io: + remote: https://gcr.io + ghcr.io: + remote: https://ghcr.io + quay.io: + remote: https://quay.io + registry-1.docker.io: + remote: https://registry-1.docker.io + local: https://docker.io + registry.k8s.io: + remote: https://registry.k8s.io + registry.test: {} + git: + livereload: + enabled: true + rsync_exclude: .windsor,.terraform,data,.volumes,.venv + rsync_protect: flux-system + username: local + password: local + webhook_url: http://worker-1.test:30292/hook/5dc88e45e809fb0872b749c0969067e2c1fd142e17aed07573fad20553cc0c59 + verify_ssl: false + image: ghcr.io/windsorcli/git-livereload:v0.1.1 + terraform: + enabled: true + backend: + type: local + vm: + driver: docker-desktop + cluster: + enabled: true + driver: talos + controlplanes: + count: 1 + cpu: 2 + memory: 2 + workers: + count: 1 + cpu: 4 + memory: 4 + hostports: + - 8080:30080/tcp + - 8443:30443/tcp + - 9292:30292/tcp + - 8053:30053/udp + volumes: + - ${WINDSOR_PROJECT_ROOT}/.volumes:/var/local + network: + cidr_block: 10.5.0.0/16 + dns: + enabled: true + domain: test local-colima: + aws: + enabled: true + localstack: + enabled: true docker: enabled: true registries: From 68b5795ead0aa604a28ad418c50a4a6d5e51c5a7 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Sun, 11 May 2025 09:11:23 -0400 Subject: [PATCH 03/10] add checkov scan shortcut --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 38f79ae1..7a8c268e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -38,7 +38,7 @@ jobs: uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: '3.x' - + - name: Setup Terraform uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 with: @@ -66,7 +66,7 @@ jobs: run: | sudo apt-get install -y shellcheck find . -name "*.sh" -print0 | xargs -0 shellcheck - + - name: Run terraform fmt run: terraform fmt -check -recursive @@ -101,7 +101,7 @@ jobs: directory: ./terraform output_format: cli,sarif output_file_path: console,results.sarif - + - name: Upload SARIF file uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 with: From c5c9438a42cf22eafc4bfcff31007fe87ed7227f Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Sun, 11 May 2025 09:11:32 -0400 Subject: [PATCH 04/10] Add checkov scan task --- Taskfile.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Taskfile.yaml diff --git a/Taskfile.yaml b/Taskfile.yaml new file mode 100644 index 00000000..9e6146df --- /dev/null +++ b/Taskfile.yaml @@ -0,0 +1,7 @@ +version: '3' + +tasks: + scan: + desc: Scan for security vulnerabilities + cmds: + - checkov -d terraform/ \ No newline at end of file From 7a8154cf13ae6b6e69c4353fb1bf2e0e685b2b00 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Sun, 11 May 2025 10:10:16 -0400 Subject: [PATCH 05/10] Use context id --- terraform/backend/s3/main.tf | 13 ++++--------- terraform/backend/s3/outputs.tf | 2 +- terraform/backend/s3/variables.tf | 6 ++++++ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/terraform/backend/s3/main.tf b/terraform/backend/s3/main.tf index ce5a64f5..cb2c70e6 100644 --- a/terraform/backend/s3/main.tf +++ b/terraform/backend/s3/main.tf @@ -50,9 +50,8 @@ resource "aws_s3_bucket" "this" { #--------------------------------------------------------------------------------------------------- locals { - account_id_hash = substr(md5(data.aws_caller_identity.current.account_id), 0, 8) - default_s3_bucket_name = "terraform-state-${local.account_id_hash}" - log_bucket_name = var.s3_log_bucket_name != "" ? var.s3_log_bucket_name : (var.s3_bucket_name != "" ? "${var.s3_bucket_name}-logs" : "terraform-state-logs-${local.account_id_hash}") + default_s3_bucket_name = var.s3_bucket_name != "" ? var.s3_bucket_name : "terraform-state-${var.context_id}" + log_bucket_name = var.s3_log_bucket_name != "" ? var.s3_log_bucket_name : (var.s3_bucket_name != "" ? "${var.s3_bucket_name}-logs" : "terraform-state-logs-${var.context_id}") bucket_policy_statements = flatten([ var.bucket_policy_enforce_https ? [ @@ -178,7 +177,7 @@ resource "aws_dynamodb_table" "terraform_locks" { # checkov:skip=CKV_AWS_119:Encryption is not necessary for this DynamoDB table as it is used solely for Terraform state locking, which does not involve sensitive data. count = var.enable_dynamodb ? 1 : 0 - name = var.dynamodb_table_name != "" ? var.dynamodb_table_name : "terraform-state-locks-${local.account_id_hash}" + name = var.dynamodb_table_name != "" ? var.dynamodb_table_name : "terraform-state-locks-${var.context_id}" billing_mode = var.dynamodb_billing_mode hash_key = var.dynamodb_lock_key @@ -273,7 +272,7 @@ resource "aws_kms_key" "terraform_state" { resource "aws_kms_alias" "terraform_state_alias" { count = var.enable_kms && var.kms_key_alias == "" ? 1 : 0 - name = var.kms_key_alias != "" ? var.kms_key_alias : "alias/terraform-state-${local.account_id_hash}" + name = var.kms_key_alias != "" ? var.kms_key_alias : "alias/terraform-state-${var.context_id}" target_key_id = aws_kms_key.terraform_state[0].key_id } @@ -293,8 +292,4 @@ ${var.dynamodb_table_name != "" ? "dynamodb_table = \"" + var.dynamodb_table_nam EOF filename = "${var.context_path}/terraform/backend.tfvars" - - lifecycle { - ignore_changes = [] - } } diff --git a/terraform/backend/s3/outputs.tf b/terraform/backend/s3/outputs.tf index 93f57074..84fdf925 100644 --- a/terraform/backend/s3/outputs.tf +++ b/terraform/backend/s3/outputs.tf @@ -8,7 +8,7 @@ output "dynamodb_table" { value = var.enable_dynamodb ? aws_dynamodb_table.terraform_locks[0].name : null } -output "kms_key_arn_with_alias" { +output "kms_key_arn" { description = "The ARN of the KMS key using the alias for encrypting the S3 bucket" value = (var.enable_kms && var.kms_key_alias == "") ? aws_kms_alias.terraform_state_alias[0].arn : null } diff --git a/terraform/backend/s3/variables.tf b/terraform/backend/s3/variables.tf index da4b7b47..1a07bf71 100644 --- a/terraform/backend/s3/variables.tf +++ b/terraform/backend/s3/variables.tf @@ -4,6 +4,12 @@ variable "context_path" { default = "" } +variable "context_id" { + description = "Context ID for the resources" + type = string + default = null +} + #--------------------------------------------------------------------------------------------------- # AWS Account Details #--------------------------------------------------------------------------------------------------- From 3b0f2f0e9688edbdc13dbc137a355a84074aca31 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Sun, 11 May 2025 15:10:40 -0400 Subject: [PATCH 06/10] Fix policies --- contexts/.gitignore | 1 + terraform/backend/s3/main.tf | 81 +++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 38 deletions(-) create mode 100644 contexts/.gitignore diff --git a/contexts/.gitignore b/contexts/.gitignore new file mode 100644 index 00000000..a703ae48 --- /dev/null +++ b/contexts/.gitignore @@ -0,0 +1 @@ +cloud/ \ No newline at end of file diff --git a/terraform/backend/s3/main.tf b/terraform/backend/s3/main.tf index cb2c70e6..9146693d 100644 --- a/terraform/backend/s3/main.tf +++ b/terraform/backend/s3/main.tf @@ -52,46 +52,46 @@ resource "aws_s3_bucket" "this" { locals { default_s3_bucket_name = var.s3_bucket_name != "" ? var.s3_bucket_name : "terraform-state-${var.context_id}" log_bucket_name = var.s3_log_bucket_name != "" ? var.s3_log_bucket_name : (var.s3_bucket_name != "" ? "${var.s3_bucket_name}-logs" : "terraform-state-logs-${var.context_id}") + kms_key_id = var.enable_kms && var.kms_key_alias == "" ? aws_kms_key.terraform_state[0].arn : "" bucket_policy_statements = flatten([ - var.bucket_policy_enforce_https ? [ - { - Sid = "enforceHttps", - Effect = "Deny", - Principal = "*", - Action = "s3:*", - Resource = [ - aws_s3_bucket.this.arn, - "${aws_s3_bucket.this.arn}/*" - ], - Condition = { - Bool = { - "aws:SecureTransport" = "false" - } + { + Sid = "AllowAdminAccess", + Effect = "Allow", + Principal = { + AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" + }, + Action = [ + "s3:ListBucket", + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ], + Resource = [ + aws_s3_bucket.this.arn, + "${aws_s3_bucket.this.arn}/*" + ], + Condition = { + Bool = { + "aws:SecureTransport" = "true" } } - ] : [], - var.bucket_policy_enforce_encryption ? [ - { - Sid = "enforceEncryptionMethod", - Principal = "*", - Effect = "Deny", - Action = [ - "s3:PutObject" - ], - Resource = [ - "${aws_s3_bucket.this.arn}/*" - ], - Condition = { - StringNotEquals = { - "s3:x-amz-server-side-encryption" = [ - "AES256", - "aws:kms" - ] - } + }, + { + Sid = "EnforceHttps", + Effect = "Deny", + Principal = "*", + Action = "s3:*", + Resource = [ + aws_s3_bucket.this.arn, + "${aws_s3_bucket.this.arn}/*" + ], + Condition = { + Bool = { + "aws:SecureTransport" = "false" } } - ] : [] + } ]) } @@ -224,7 +224,12 @@ data "aws_iam_policy_document" "terraform_state_kms_policy" { "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion" ] - resources = [ "${aws_kms_key.terraform_state[0].arn}" ] + resources = [ "arn:aws:kms:${var.region}:${data.aws_caller_identity.current.account_id}:key/*" ] + condition { + test = "StringEquals" + variable = "kms:CallerAccount" + values = [ "${data.aws_caller_identity.current.account_id}" ] + } } statement { @@ -245,7 +250,7 @@ data "aws_iam_policy_document" "terraform_state_kms_policy" { "kms:GenerateDataKey*", "kms:DescribeKey" ] - resources = [ "${aws_kms_key.terraform_state[0].arn}" ] + resources = [ "arn:aws:kms:${var.region}:${data.aws_caller_identity.current.account_id}:key/*" ] condition { test = "StringEquals" variable = "kms:CallerAccount" @@ -287,8 +292,8 @@ resource "local_file" "backend_config" { content = < 0 ? "kms_key_id = \"" + aws_kms_key.terraform_state[0].arn + "\"" : ""} -${var.dynamodb_table_name != "" ? "dynamodb_table = \"" + var.dynamodb_table_name + "\"" : ""} +${var.enable_kms && var.kms_key_alias == "" ? "kms_key_id = \"${aws_kms_key.terraform_state[0].arn}\"" : ""} +${var.enable_dynamodb ? "dynamodb_table = \"${var.dynamodb_table_name != "" ? var.dynamodb_table_name : "terraform-state-locks-${var.context_id}"}\"" : ""} EOF filename = "${var.context_path}/terraform/backend.tfvars" From 1681bb3a763b3d3fde17a7bbade521d73fc0dfc4 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Mon, 12 May 2025 10:29:13 -0400 Subject: [PATCH 07/10] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index abd60f70..01d98ae4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ contexts/**/.kube/ contexts/**/.talos/ contexts/**/.aws/ contexts/**/.omni/ +contexts/**/.azure/ From fbf8a7017c89cfb840cc178d5aa2badcd5492f25 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Mon, 12 May 2025 10:56:23 -0400 Subject: [PATCH 08/10] fmt --- terraform/backend/s3/main.tf | 34 +++++++++++++++---------------- terraform/backend/s3/variables.tf | 6 +++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/terraform/backend/s3/main.tf b/terraform/backend/s3/main.tf index 9146693d..149c46a9 100644 --- a/terraform/backend/s3/main.tf +++ b/terraform/backend/s3/main.tf @@ -50,24 +50,24 @@ resource "aws_s3_bucket" "this" { #--------------------------------------------------------------------------------------------------- locals { - default_s3_bucket_name = var.s3_bucket_name != "" ? var.s3_bucket_name : "terraform-state-${var.context_id}" - log_bucket_name = var.s3_log_bucket_name != "" ? var.s3_log_bucket_name : (var.s3_bucket_name != "" ? "${var.s3_bucket_name}-logs" : "terraform-state-logs-${var.context_id}") + default_s3_bucket_name = var.s3_bucket_name != "" ? var.s3_bucket_name : "terraform-state-${var.context_id}" + log_bucket_name = var.s3_log_bucket_name != "" ? var.s3_log_bucket_name : (var.s3_bucket_name != "" ? "${var.s3_bucket_name}-logs" : "terraform-state-logs-${var.context_id}") kms_key_id = var.enable_kms && var.kms_key_alias == "" ? aws_kms_key.terraform_state[0].arn : "" bucket_policy_statements = flatten([ { - Sid = "AllowAdminAccess", - Effect = "Allow", + Sid = "AllowAdminAccess", + Effect = "Allow", Principal = { AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" }, - Action = [ + Action = [ "s3:ListBucket", "s3:GetObject", "s3:PutObject", "s3:DeleteObject" ], - Resource = [ + Resource = [ aws_s3_bucket.this.arn, "${aws_s3_bucket.this.arn}/*" ], @@ -82,7 +82,7 @@ locals { Effect = "Deny", Principal = "*", Action = "s3:*", - Resource = [ + Resource = [ aws_s3_bucket.this.arn, "${aws_s3_bucket.this.arn}/*" ], @@ -121,7 +121,7 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "this" { rule { apply_server_side_encryption_by_default { - sse_algorithm = var.enable_kms && length(aws_kms_key.terraform_state) > 0 ? "aws:kms" : "AES256" + sse_algorithm = var.enable_kms && length(aws_kms_key.terraform_state) > 0 ? "aws:kms" : "AES256" kms_master_key_id = var.enable_kms && length(aws_kms_key.terraform_state) > 0 ? aws_kms_key.terraform_state[0].arn : null } } @@ -175,7 +175,7 @@ resource "aws_s3_bucket_public_access_block" "this" { resource "aws_dynamodb_table" "terraform_locks" { # checkov:skip=CKV_AWS_119:Encryption is not necessary for this DynamoDB table as it is used solely for Terraform state locking, which does not involve sensitive data. - count = var.enable_dynamodb ? 1 : 0 + count = var.enable_dynamodb ? 1 : 0 name = var.dynamodb_table_name != "" ? var.dynamodb_table_name : "terraform-state-locks-${var.context_id}" billing_mode = var.dynamodb_billing_mode @@ -205,10 +205,10 @@ data "aws_iam_policy_document" "terraform_state_kms_policy" { principals { type = "AWS" - identifiers = [ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" ] + identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] } - actions = [ + actions = [ "kms:Create*", "kms:Describe*", "kms:Enable*", @@ -224,11 +224,11 @@ data "aws_iam_policy_document" "terraform_state_kms_policy" { "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion" ] - resources = [ "arn:aws:kms:${var.region}:${data.aws_caller_identity.current.account_id}:key/*" ] + resources = ["arn:aws:kms:${var.region}:${data.aws_caller_identity.current.account_id}:key/*"] condition { test = "StringEquals" variable = "kms:CallerAccount" - values = [ "${data.aws_caller_identity.current.account_id}" ] + values = ["${data.aws_caller_identity.current.account_id}"] } } @@ -237,24 +237,24 @@ data "aws_iam_policy_document" "terraform_state_kms_policy" { effect = "Allow" principals { - type = "AWS" + type = "AWS" identifiers = [ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" ] } - actions = [ + actions = [ "kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey" ] - resources = [ "arn:aws:kms:${var.region}:${data.aws_caller_identity.current.account_id}:key/*" ] + resources = ["arn:aws:kms:${var.region}:${data.aws_caller_identity.current.account_id}:key/*"] condition { test = "StringEquals" variable = "kms:CallerAccount" - values = [ "${data.aws_caller_identity.current.account_id}" ] + values = ["${data.aws_caller_identity.current.account_id}"] } } } diff --git a/terraform/backend/s3/variables.tf b/terraform/backend/s3/variables.tf index 1a07bf71..3502010d 100644 --- a/terraform/backend/s3/variables.tf +++ b/terraform/backend/s3/variables.tf @@ -147,9 +147,9 @@ variable "enable_versioning" { variable "public_access_block" { description = "Public access block configuration for the S3 bucket" type = object({ - block_public_acls : bool, - block_public_policy : bool, - ignore_public_acls : bool, + block_public_acls : bool, + block_public_policy : bool, + ignore_public_acls : bool, restrict_public_buckets : bool, }) default = { From 0ca5cd85a683b305eb59c3050380774f6554b926 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Mon, 12 May 2025 11:07:06 -0400 Subject: [PATCH 09/10] wip --- terraform/backend/s3/main.tf | 161 ++++++++++++++++++++++++++++++ terraform/backend/s3/variables.tf | 12 +++ 2 files changed, 173 insertions(+) diff --git a/terraform/backend/s3/main.tf b/terraform/backend/s3/main.tf index 149c46a9..88dee42c 100644 --- a/terraform/backend/s3/main.tf +++ b/terraform/backend/s3/main.tf @@ -26,6 +26,19 @@ provider "aws" { } } +provider "aws" { + alias = "replication" + region = var.replication_destination_region + default_tags { + tags = merge( + var.tags, + { + ManagedBy = "Terraform" + } + ) + } +} + data "aws_caller_identity" "current" {} #--------------------------------------------------------------------------------------------------- @@ -35,6 +48,9 @@ data "aws_caller_identity" "current" {} #--------------------------------------------------------------------------------------------------- resource "aws_s3_bucket" "this" { + # checkov:skip=CKV2_AWS_62:Event notifications are not needed for terraform state bucket + # checkov:skip=CKV_AWS_19:Server-side encryption is configured via aws_s3_bucket_server_side_encryption_configuration + # checkov:skip=CKV_AWS_145:KMS encryption is configured via aws_s3_bucket_server_side_encryption_configuration bucket = var.s3_bucket_name != "" ? var.s3_bucket_name : local.default_s3_bucket_name tags = { @@ -42,6 +58,151 @@ resource "aws_s3_bucket" "this" { } } +resource "aws_s3_bucket_lifecycle_configuration" "this" { + bucket = aws_s3_bucket.this.id + + rule { + id = "cleanup" + status = "Enabled" + + expiration { + days = 90 + } + + noncurrent_version_expiration { + noncurrent_days = 90 + } + } +} + +resource "aws_s3_bucket_replication_configuration" "this" { + count = var.enable_replication ? 1 : 0 + + bucket = aws_s3_bucket.this.id + role = aws_iam_role.replication[0].arn + + rule { + id = "replication" + status = "Enabled" + + destination { + bucket = aws_s3_bucket.replication[0].arn + storage_class = "STANDARD" + } + } +} + +resource "aws_s3_bucket" "replication" { + count = var.enable_replication ? 1 : 0 + provider = aws.replication + + bucket = "${var.s3_bucket_name != "" ? var.s3_bucket_name : local.default_s3_bucket_name}-replication" + + tags = { + Name = "${var.s3_bucket_name != "" ? var.s3_bucket_name : local.default_s3_bucket_name}-replication" + } +} + +resource "aws_s3_bucket_versioning" "replication" { + count = var.enable_replication ? 1 : 0 + provider = aws.replication + + bucket = aws_s3_bucket.replication[0].id + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "replication" { + count = var.enable_replication ? 1 : 0 + provider = aws.replication + + bucket = aws_s3_bucket.replication[0].id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = var.enable_kms && length(aws_kms_key.terraform_state) > 0 ? "aws:kms" : "AES256" + kms_master_key_id = var.enable_kms && length(aws_kms_key.terraform_state) > 0 ? aws_kms_key.terraform_state[0].arn : null + } + } +} + +resource "aws_s3_bucket_public_access_block" "replication" { + count = var.enable_replication ? 1 : 0 + provider = aws.replication + + bucket = aws_s3_bucket.replication[0].id + + block_public_acls = var.public_access_block.block_public_acls + block_public_policy = var.public_access_block.block_public_policy + ignore_public_acls = var.public_access_block.ignore_public_acls + restrict_public_buckets = var.public_access_block.restrict_public_buckets +} + +resource "aws_iam_role" "replication" { + count = var.enable_replication ? 1 : 0 + + name = "s3-replication-role-${var.context_id}" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "s3.amazonaws.com" + } + } + ] + }) +} + +resource "aws_iam_policy" "replication" { + count = var.enable_replication ? 1 : 0 + + name = "s3-replication-policy-${var.context_id}" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "s3:GetReplicationConfiguration", + "s3:ListBucket" + ] + Resource = aws_s3_bucket.this.arn + }, + { + Effect = "Allow" + Action = [ + "s3:GetObjectVersionForReplication", + "s3:GetObjectVersionAcl", + "s3:GetObjectVersionTagging" + ] + Resource = "${aws_s3_bucket.this.arn}/*" + }, + { + Effect = "Allow" + Action = [ + "s3:ReplicateObject", + "s3:ReplicateDelete", + "s3:ReplicateTags" + ] + Resource = "${aws_s3_bucket.replication[0].arn}/*" + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "replication" { + count = var.enable_replication ? 1 : 0 + + role = aws_iam_role.replication[0].name + policy_arn = aws_iam_policy.replication[0].arn +} + #--------------------------------------------------------------------------------------------------- # S3 Bucket Configuration # This section defines local variables for S3 bucket naming conventions and policy statements. diff --git a/terraform/backend/s3/variables.tf b/terraform/backend/s3/variables.tf index 3502010d..16b2be7a 100644 --- a/terraform/backend/s3/variables.tf +++ b/terraform/backend/s3/variables.tf @@ -182,6 +182,18 @@ variable "enable_log_bucket" { default = true } +variable "enable_replication" { + description = "Enable cross-region replication for the S3 bucket" + type = bool + default = false +} + +variable "replication_destination_region" { + description = "The AWS region for the replication destination bucket" + type = string + default = "us-west-2" +} + variable "tags" { description = "Additional tags to apply to resources (default is empty)." type = map(string) From 7829f92dbb9f61219764817cd057791c53968158 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Mon, 12 May 2025 23:24:32 -0400 Subject: [PATCH 10/10] Refinement --- Taskfile.yaml | 2 +- terraform/backend/s3/main.tf | 212 ++++-------------------------- terraform/backend/s3/variables.tf | 150 +++------------------ 3 files changed, 49 insertions(+), 315 deletions(-) diff --git a/Taskfile.yaml b/Taskfile.yaml index 9e6146df..2ab1f35a 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -4,4 +4,4 @@ tasks: scan: desc: Scan for security vulnerabilities cmds: - - checkov -d terraform/ \ No newline at end of file + - source .venv/bin/activate && checkov -d {{.CLI_ARGS | default "terraform/"}} 2>/dev/null \ No newline at end of file diff --git a/terraform/backend/s3/main.tf b/terraform/backend/s3/main.tf index 88dee42c..6cc34ed7 100644 --- a/terraform/backend/s3/main.tf +++ b/terraform/backend/s3/main.tf @@ -26,19 +26,6 @@ provider "aws" { } } -provider "aws" { - alias = "replication" - region = var.replication_destination_region - default_tags { - tags = merge( - var.tags, - { - ManagedBy = "Terraform" - } - ) - } -} - data "aws_caller_identity" "current" {} #--------------------------------------------------------------------------------------------------- @@ -49,6 +36,7 @@ data "aws_caller_identity" "current" {} resource "aws_s3_bucket" "this" { # checkov:skip=CKV2_AWS_62:Event notifications are not needed for terraform state bucket + # checkov:skip=CKV_AWS_144:Cross-region replication is not required for Terraform state bucket # checkov:skip=CKV_AWS_19:Server-side encryption is configured via aws_s3_bucket_server_side_encryption_configuration # checkov:skip=CKV_AWS_145:KMS encryption is configured via aws_s3_bucket_server_side_encryption_configuration bucket = var.s3_bucket_name != "" ? var.s3_bucket_name : local.default_s3_bucket_name @@ -65,144 +53,12 @@ resource "aws_s3_bucket_lifecycle_configuration" "this" { id = "cleanup" status = "Enabled" - expiration { - days = 90 - } - - noncurrent_version_expiration { - noncurrent_days = 90 - } - } -} - -resource "aws_s3_bucket_replication_configuration" "this" { - count = var.enable_replication ? 1 : 0 - - bucket = aws_s3_bucket.this.id - role = aws_iam_role.replication[0].arn - - rule { - id = "replication" - status = "Enabled" - - destination { - bucket = aws_s3_bucket.replication[0].arn - storage_class = "STANDARD" - } - } -} - -resource "aws_s3_bucket" "replication" { - count = var.enable_replication ? 1 : 0 - provider = aws.replication - - bucket = "${var.s3_bucket_name != "" ? var.s3_bucket_name : local.default_s3_bucket_name}-replication" - - tags = { - Name = "${var.s3_bucket_name != "" ? var.s3_bucket_name : local.default_s3_bucket_name}-replication" - } -} - -resource "aws_s3_bucket_versioning" "replication" { - count = var.enable_replication ? 1 : 0 - provider = aws.replication - - bucket = aws_s3_bucket.replication[0].id - versioning_configuration { - status = "Enabled" - } -} - -resource "aws_s3_bucket_server_side_encryption_configuration" "replication" { - count = var.enable_replication ? 1 : 0 - provider = aws.replication - - bucket = aws_s3_bucket.replication[0].id - - rule { - apply_server_side_encryption_by_default { - sse_algorithm = var.enable_kms && length(aws_kms_key.terraform_state) > 0 ? "aws:kms" : "AES256" - kms_master_key_id = var.enable_kms && length(aws_kms_key.terraform_state) > 0 ? aws_kms_key.terraform_state[0].arn : null + abort_incomplete_multipart_upload { + days_after_initiation = 7 } } } -resource "aws_s3_bucket_public_access_block" "replication" { - count = var.enable_replication ? 1 : 0 - provider = aws.replication - - bucket = aws_s3_bucket.replication[0].id - - block_public_acls = var.public_access_block.block_public_acls - block_public_policy = var.public_access_block.block_public_policy - ignore_public_acls = var.public_access_block.ignore_public_acls - restrict_public_buckets = var.public_access_block.restrict_public_buckets -} - -resource "aws_iam_role" "replication" { - count = var.enable_replication ? 1 : 0 - - name = "s3-replication-role-${var.context_id}" - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = "sts:AssumeRole" - Effect = "Allow" - Principal = { - Service = "s3.amazonaws.com" - } - } - ] - }) -} - -resource "aws_iam_policy" "replication" { - count = var.enable_replication ? 1 : 0 - - name = "s3-replication-policy-${var.context_id}" - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Effect = "Allow" - Action = [ - "s3:GetReplicationConfiguration", - "s3:ListBucket" - ] - Resource = aws_s3_bucket.this.arn - }, - { - Effect = "Allow" - Action = [ - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionTagging" - ] - Resource = "${aws_s3_bucket.this.arn}/*" - }, - { - Effect = "Allow" - Action = [ - "s3:ReplicateObject", - "s3:ReplicateDelete", - "s3:ReplicateTags" - ] - Resource = "${aws_s3_bucket.replication[0].arn}/*" - } - ] - }) -} - -resource "aws_iam_role_policy_attachment" "replication" { - count = var.enable_replication ? 1 : 0 - - role = aws_iam_role.replication[0].name - policy_arn = aws_iam_policy.replication[0].arn -} - #--------------------------------------------------------------------------------------------------- # S3 Bucket Configuration # This section defines local variables for S3 bucket naming conventions and policy statements. @@ -212,7 +68,6 @@ resource "aws_iam_role_policy_attachment" "replication" { locals { default_s3_bucket_name = var.s3_bucket_name != "" ? var.s3_bucket_name : "terraform-state-${var.context_id}" - log_bucket_name = var.s3_log_bucket_name != "" ? var.s3_log_bucket_name : (var.s3_bucket_name != "" ? "${var.s3_bucket_name}-logs" : "terraform-state-logs-${var.context_id}") kms_key_id = var.enable_kms && var.kms_key_alias == "" ? aws_kms_key.terraform_state[0].arn : "" bucket_policy_statements = flatten([ @@ -220,13 +75,19 @@ locals { Sid = "AllowAdminAccess", Effect = "Allow", Principal = { - AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" + AWS = concat( + ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"], + var.terraform_state_iam_roles + ) }, Action = [ "s3:ListBucket", "s3:GetObject", "s3:PutObject", - "s3:DeleteObject" + "s3:DeleteObject", + "s3:GetBucketVersioning", + "s3:GetEncryptionConfiguration", + "s3:GetBucketLocation" ], Resource = [ aws_s3_bucket.this.arn, @@ -237,21 +98,6 @@ locals { "aws:SecureTransport" = "true" } } - }, - { - Sid = "EnforceHttps", - Effect = "Deny", - Principal = "*", - Action = "s3:*", - Resource = [ - aws_s3_bucket.this.arn, - "${aws_s3_bucket.this.arn}/*" - ], - Condition = { - Bool = { - "aws:SecureTransport" = "false" - } - } } ]) } @@ -264,11 +110,12 @@ locals { #--------------------------------------------------------------------------------------------------- resource "aws_s3_bucket_logging" "this" { - count = var.enable_log_bucket ? 1 : 0 + count = var.s3_log_bucket_name != "" ? 1 : 0 + bucket = aws_s3_bucket.this.id - target_bucket = aws_s3_bucket.this.id - target_prefix = aws_s3_bucket.this.id + target_bucket = var.s3_log_bucket_name + target_prefix = "${aws_s3_bucket.this.id}/" } #--------------------------------------------------------------------------------------------------- @@ -289,10 +136,9 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "this" { } resource "aws_s3_bucket_policy" "this" { - count = var.enable_bucket_policy ? 1 : 0 bucket = aws_s3_bucket.this.id - policy = var.custom_bucket_policy != "" ? var.custom_bucket_policy : jsonencode({ + policy = jsonencode({ Version = "2012-10-17", Statement = local.bucket_policy_statements }) @@ -305,8 +151,6 @@ resource "aws_s3_bucket_policy" "this" { #--------------------------------------------------------------------------------------------------- resource "aws_s3_bucket_versioning" "this" { - count = var.enable_versioning ? 1 : 0 - bucket = aws_s3_bucket.this.id versioning_configuration { status = "Enabled" @@ -322,10 +166,10 @@ resource "aws_s3_bucket_versioning" "this" { resource "aws_s3_bucket_public_access_block" "this" { bucket = aws_s3_bucket.this.id - block_public_acls = var.public_access_block.block_public_acls - block_public_policy = var.public_access_block.block_public_policy - ignore_public_acls = var.public_access_block.ignore_public_acls - restrict_public_buckets = var.public_access_block.restrict_public_buckets + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true } #--------------------------------------------------------------------------------------------------- @@ -338,17 +182,17 @@ resource "aws_dynamodb_table" "terraform_locks" { # checkov:skip=CKV_AWS_119:Encryption is not necessary for this DynamoDB table as it is used solely for Terraform state locking, which does not involve sensitive data. count = var.enable_dynamodb ? 1 : 0 - name = var.dynamodb_table_name != "" ? var.dynamodb_table_name : "terraform-state-locks-${var.context_id}" - billing_mode = var.dynamodb_billing_mode - hash_key = var.dynamodb_lock_key + name = "terraform-state-locks-${var.context_id}" + billing_mode = "PAY_PER_REQUEST" + hash_key = "LockID" attribute { - name = var.dynamodb_lock_key + name = "LockID" type = "S" } point_in_time_recovery { - enabled = var.dynamodb_pti_enabled + enabled = true } } @@ -430,8 +274,8 @@ resource "aws_kms_key" "terraform_state" { count = var.enable_kms && var.kms_key_alias == "" ? 1 : 0 description = "KMS key for encrypting Terraform state file in S3" - enable_key_rotation = var.kms_enable_key_rotation - deletion_window_in_days = var.kms_deletion_window + enable_key_rotation = true + deletion_window_in_days = 7 policy = data.aws_iam_policy_document.terraform_state_kms_policy.json } @@ -454,7 +298,7 @@ resource "local_file" "backend_config" { bucket = "${var.s3_bucket_name != "" ? var.s3_bucket_name : local.default_s3_bucket_name}" region = "${var.region}" ${var.enable_kms && var.kms_key_alias == "" ? "kms_key_id = \"${aws_kms_key.terraform_state[0].arn}\"" : ""} -${var.enable_dynamodb ? "dynamodb_table = \"${var.dynamodb_table_name != "" ? var.dynamodb_table_name : "terraform-state-locks-${var.context_id}"}\"" : ""} +${var.enable_dynamodb ? "dynamodb_table = \"terraform-state-locks-${var.context_id}\"" : ""} EOF filename = "${var.context_path}/terraform/backend.tfvars" diff --git a/terraform/backend/s3/variables.tf b/terraform/backend/s3/variables.tf index 16b2be7a..fc81a09c 100644 --- a/terraform/backend/s3/variables.tf +++ b/terraform/backend/s3/variables.tf @@ -1,6 +1,10 @@ +#--------------------------------------------------------------------------------------------------- +# General Context +#--------------------------------------------------------------------------------------------------- + variable "context_path" { type = string - description = "The path to the context folder, where kubeconfig and talosconfig are stored" + description = "The path to the context folder" default = "" } @@ -11,21 +15,21 @@ variable "context_id" { } #--------------------------------------------------------------------------------------------------- -# AWS Account Details +# AWS Region #--------------------------------------------------------------------------------------------------- variable "region" { description = "The AWS Region for the S3 Bucket and DynamoDB Table" type = string - default = "us-east-1" + default = "us-east-2" } #--------------------------------------------------------------------------------------------------- -# S3 Bucket Configuration +# S3 Bucket #--------------------------------------------------------------------------------------------------- variable "s3_bucket_name" { - description = "The name of the S3 bucket for storing Terraform state" + description = "The name of the S3 bucket for storing Terraform state, overrides the default bucket name" type = string default = "" validation { @@ -35,7 +39,7 @@ variable "s3_bucket_name" { } variable "s3_log_bucket_name" { - description = "Optional S3 logging bucket name override. If not provided, a name will be generated." + description = "Name of a pre-existing, centralized S3 logging bucket to receive access logs. Must be created outside this module." type = string default = "" validation { @@ -44,42 +48,6 @@ variable "s3_log_bucket_name" { } } -#--------------------------------------------------------------------------------------------------- -# DynamoDB Table for Locking -#--------------------------------------------------------------------------------------------------- - -variable "dynamodb_table_name" { - description = "The name of the DynamoDB table for state locking" - type = string - default = "" - validation { - condition = length(var.dynamodb_table_name) <= 255 - error_message = "The DynamoDB table name must be 255 characters or less." - } -} - -variable "dynamodb_lock_key" { - description = "The hash key attribute name for the DynamoDB state locking table" - type = string - default = "LockID" -} - -variable "dynamodb_billing_mode" { - description = "Billing mode for the DynamoDB table used for state locking" - type = string - default = "PAY_PER_REQUEST" - validation { - condition = contains(["PROVISIONED", "PAY_PER_REQUEST"], var.dynamodb_billing_mode) - error_message = "The billing mode must be either 'PROVISIONED' or 'PAY_PER_REQUEST'." - } -} - -variable "dynamodb_pti_enabled" { - description = "Enable point in time recovery for the DynamoDB table" - type = bool - default = true -} - #--------------------------------------------------------------------------------------------------- # KMS Key #--------------------------------------------------------------------------------------------------- @@ -90,78 +58,8 @@ variable "kms_key_alias" { default = "" } -variable "kms_deletion_window" { - description = "Deletion window (in days) for the KMS key" - type = number - default = 10 - validation { - condition = var.kms_deletion_window >= 7 && var.kms_deletion_window <= 30 - error_message = "The KMS deletion window must be between 7 and 30 days." - } -} - -variable "kms_enable_key_rotation" { - description = "Flag to enable automatic key rotation for the KMS key" - type = bool - default = true -} - -#--------------------------------------------------------------------------------------------------- -# S3 Bucket Policies and Versioning -#--------------------------------------------------------------------------------------------------- - -variable "enable_bucket_policy" { - description = "Flag to enable the S3 bucket policy" - type = bool - default = true -} - -variable "bucket_policy_enforce_https" { - description = "Whether to include the HTTPS enforcement in the bucket policy" - type = bool - default = true -} - -variable "bucket_policy_enforce_encryption" { - description = "Whether to enforce server side encryption in the bucket policy" - type = bool - default = true -} - -variable "custom_bucket_policy" { - description = "If provided, overrides the default computed S3 bucket policy." - type = string - default = "" -} - -variable "enable_versioning" { - description = "Flag to enable bucket versioning on the S3 state bucket" - type = bool - default = true -} - #--------------------------------------------------------------------------------------------------- -# Public Access Block -#--------------------------------------------------------------------------------------------------- - -variable "public_access_block" { - description = "Public access block configuration for the S3 bucket" - type = object({ - block_public_acls : bool, - block_public_policy : bool, - ignore_public_acls : bool, - restrict_public_buckets : bool, - }) - default = { - block_public_acls = true, - block_public_policy = true, - ignore_public_acls = true, - restrict_public_buckets = true - } -} - -#--------------------------------------------------------------------------------------------------- -# Feature Flags and Tags +# Feature Flags #--------------------------------------------------------------------------------------------------- variable "enable_dynamodb" { @@ -176,26 +74,18 @@ variable "enable_kms" { default = true } -variable "enable_log_bucket" { - description = "Feature flag to enable log bucket creation" - type = bool - default = true -} - -variable "enable_replication" { - description = "Enable cross-region replication for the S3 bucket" - type = bool - default = false -} - -variable "replication_destination_region" { - description = "The AWS region for the replication destination bucket" - type = string - default = "us-west-2" -} +#--------------------------------------------------------------------------------------------------- +# Tags and IAM Roles +#--------------------------------------------------------------------------------------------------- variable "tags" { description = "Additional tags to apply to resources (default is empty)." type = map(string) default = {} } + +variable "terraform_state_iam_roles" { + description = "List of IAM role ARNs that should have access to the Terraform state bucket" + type = list(string) + default = [] +}