From 1b7163d5e82337ebe059a46c26085cf9dc0a4681 Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Tue, 17 Feb 2026 19:26:52 +0530 Subject: [PATCH 01/16] separate backend for each environment --- infrastructure/backend/README.md | 89 +++++++++++++ infrastructure/backend/main.tf | 185 ++++++++++++++++------------ infrastructure/backend/outputs.tf | 12 +- infrastructure/backend/providers.tf | 3 +- infrastructure/backend/variables.tf | 13 ++ 5 files changed, 216 insertions(+), 86 deletions(-) create mode 100644 infrastructure/backend/README.md diff --git a/infrastructure/backend/README.md b/infrastructure/backend/README.md new file mode 100644 index 0000000000..f0230acdf9 --- /dev/null +++ b/infrastructure/backend/README.md @@ -0,0 +1,89 @@ +## Inline Permissions +Use the following inline permissions for the `nest-backend` IAM User +*Note*: replace ${AWS_ACCOUNT_ID} and ${AWS_BACKEND_KMS_KEY} with approriate values. + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "S3Management", + "Effect": "Allow", + "Action": [ + "s3:CreateBucket", + "s3:DeleteBucket", + "s3:GetAccelerateConfiguration", + "s3:GetBucketAcl", + "s3:GetBucketCors", + "s3:GetBucketLogging", + "s3:GetBucketObjectLockConfiguration", + "s3:GetBucketPolicy", + "s3:GetBucketPublicAccessBlock", + "s3:GetBucketRequestPayment", + "s3:GetBucketTagging", + "s3:GetBucketVersioning", + "s3:GetBucketWebsite", + "s3:GetEncryptionConfiguration", + "s3:GetLifecycleConfiguration", + "s3:GetObject", + "s3:GetReplicationConfiguration", + "s3:ListBucket", + "s3:PutBucketLogging", + "s3:PutBucketObjectLockConfiguration", + "s3:PutBucketPolicy", + "s3:PutBucketPublicAccessBlock", + "s3:PutBucketTagging", + "s3:PutBucketVersioning", + "s3:PutEncryptionConfiguration", + "s3:PutLifecycleConfiguration", + "s3:PutObject" + ], + "Resource": [ + "arn:aws:s3:::nest-terraform-state-*", + "arn:aws:s3:::nest-terraform-state-*/*" + ] + }, + { + "Sid": "DynamoDBManagement", + "Effect": "Allow", + "Action": [ + "dynamodb:CreateTable", + "dynamodb:DeleteTable", + "dynamodb:DescribeContinuousBackups", + "dynamodb:DescribeTable", + "dynamodb:DescribeTimeToLive", + "dynamodb:ListTagsOfResource", + "dynamodb:TagResource", + "dynamodb:UntagResource", + "dynamodb:UpdateContinuousBackups", + "dynamodb:UpdateTable" + ], + "Resource": "arn:aws:dynamodb:*:${AWS_ACCOUNT_ID}:table/nest-terraform-state-lock-*" + }, + { + "Sid": "KMSManagement", + "Effect": "Allow", + "Action": [ + "kms:CreateAlias", + "kms:CreateGrant", + "kms:CreateKey", + "kms:DeleteAlias", + "kms:DescribeKey", + "kms:DisableKeyRotation", + "kms:EnableKeyRotation", + "kms:GetKeyPolicy", + "kms:GetKeyRotationStatus", + "kms:ListAliases", + "kms:ListResourceTags", + "kms:PutKeyPolicy", + "kms:ScheduleKeyDeletion", + "kms:TagResource", + "kms:UntagResource", + "kms:UpdateAlias", + "kms:UpdateKeyDescription" + ], + "Resource": "${AWS_BACKEND_KMS_KEY}" + } + ] +} +``` diff --git a/infrastructure/backend/main.tf b/infrastructure/backend/main.tf index d3a0b76456..77cd642f36 100644 --- a/infrastructure/backend/main.tf +++ b/infrastructure/backend/main.tf @@ -18,6 +18,19 @@ locals { ManagedBy = "Terraform" Project = var.project_name } + state_environments = toset(var.state_environments) +} + +module "kms" { + source = "../modules/kms" + + common_tags = local.common_tags + environment = "backend" + project_name = var.project_name +} + +resource "random_id" "suffix" { + byte_length = 4 } data "aws_iam_policy_document" "logs" { @@ -28,19 +41,25 @@ data "aws_iam_policy_document" "logs" { sid = "s3-log-delivery" principals { - type = "Service" identifiers = ["logging.s3.amazonaws.com"] + type = "Service" } } } data "aws_iam_policy_document" "state_https_only" { + for_each = local.state_environments policy_id = "ForceHTTPS" statement { actions = ["s3:*"] - sid = "HTTPSOnly" effect = "Deny" + sid = "HTTPSOnly" + + resources = [ + aws_s3_bucket.state[each.key].arn, + "${aws_s3_bucket.state[each.key].arn}/*", + ] condition { test = "Bool" @@ -51,31 +70,18 @@ data "aws_iam_policy_document" "state_https_only" { identifiers = ["*"] type = "AWS" } - resources = [ - aws_s3_bucket.state.arn, - "${aws_s3_bucket.state.arn}/*", - ] } } -module "kms" { - source = "../modules/kms" - - common_tags = local.common_tags - environment = "backend" - project_name = var.project_name -} - -resource "random_id" "suffix" { - byte_length = 4 -} - resource "aws_dynamodb_table" "state_lock" { - name = "${var.project_name}-terraform-state-lock" + for_each = local.state_environments + billing_mode = "PAY_PER_REQUEST" hash_key = "LockID" + name = "${var.project_name}-terraform-state-lock-${each.key}" tags = merge(local.common_tags, { - Name = "${var.project_name}-terraform-state-lock" + Environment = each.key + Name = "${var.project_name}-terraform-state-lock-${each.key}" }) attribute { @@ -83,7 +89,7 @@ resource "aws_dynamodb_table" "state_lock" { type = "S" } lifecycle { - prevent_destroy = true + prevent_destroy = false } point_in_time_recovery { enabled = true @@ -96,67 +102,108 @@ resource "aws_dynamodb_table" "state_lock" { resource "aws_s3_bucket" "logs" { # NOSONAR bucket = "${var.project_name}-terraform-state-logs-${random_id.suffix.hex}" - - lifecycle { - prevent_destroy = true - } tags = merge(local.common_tags, { Name = "${var.project_name}-terraform-state-logs" }) -} - -resource "aws_s3_bucket" "state" { # NOSONAR - bucket = "${var.project_name}-terraform-state-${random_id.suffix.hex}" - object_lock_enabled = true lifecycle { - prevent_destroy = true + prevent_destroy = false } - tags = merge(local.common_tags, { - Name = "${var.project_name}-terraform-state" - }) } -resource "aws_s3_bucket_lifecycle_configuration" "state" { - bucket = aws_s3_bucket.state.id +resource "aws_s3_bucket_lifecycle_configuration" "logs" { + bucket = aws_s3_bucket.logs.id rule { - id = "delete-old-versions" + id = "expire-logs" status = "Enabled" abort_incomplete_multipart_upload { days_after_initiation = var.abort_incomplete_multipart_upload_days } - noncurrent_version_expiration { - noncurrent_days = var.noncurrent_version_expiration_days + expiration { + days = var.expire_log_days } } } -resource "aws_s3_bucket_lifecycle_configuration" "logs" { +resource "aws_s3_bucket_policy" "logs" { + bucket = aws_s3_bucket.logs.id + policy = data.aws_iam_policy_document.logs.json +} + +resource "aws_s3_bucket_public_access_block" "logs" { + block_public_acls = true + block_public_policy = true + bucket = aws_s3_bucket.logs.id + ignore_public_acls = true + restrict_public_buckets = true +} + +#trivy:ignore:AVD-AWS-0132 +resource "aws_s3_bucket_server_side_encryption_configuration" "logs" { bucket = aws_s3_bucket.logs.id rule { - id = "expire-logs" + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} + +resource "aws_s3_bucket_versioning" "logs" { + bucket = aws_s3_bucket.logs.id + + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket" "state" { # NOSONAR + for_each = local.state_environments + + bucket = "${var.project_name}-terraform-state-${each.key}-${random_id.suffix.hex}" + object_lock_enabled = true + tags = merge(local.common_tags, { + Environment = each.key + Name = "${var.project_name}-terraform-state-${each.key}" + }) + + lifecycle { + prevent_destroy = false + } +} + +resource "aws_s3_bucket_lifecycle_configuration" "state" { + for_each = local.state_environments + + bucket = aws_s3_bucket.state[each.key].id + + rule { + id = "delete-old-versions" status = "Enabled" abort_incomplete_multipart_upload { days_after_initiation = var.abort_incomplete_multipart_upload_days } - expiration { - days = var.expire_log_days + noncurrent_version_expiration { + noncurrent_days = var.noncurrent_version_expiration_days } } } resource "aws_s3_bucket_logging" "state" { - bucket = aws_s3_bucket.state.id + for_each = local.state_environments + + bucket = aws_s3_bucket.state[each.key].id target_bucket = aws_s3_bucket.logs.id - target_prefix = "s3/" + target_prefix = "s3/${each.key}/" } resource "aws_s3_bucket_object_lock_configuration" "state" { - bucket = aws_s3_bucket.state.id + for_each = local.state_environments + + bucket = aws_s3_bucket.state[each.key].id rule { default_retention { @@ -166,45 +213,29 @@ resource "aws_s3_bucket_object_lock_configuration" "state" { } } -resource "aws_s3_bucket_policy" "logs" { - bucket = aws_s3_bucket.logs.id - policy = data.aws_iam_policy_document.logs.json -} - resource "aws_s3_bucket_policy" "state" { - bucket = aws_s3_bucket.state.id - policy = data.aws_iam_policy_document.state_https_only.json -} + for_each = local.state_environments -resource "aws_s3_bucket_public_access_block" "logs" { - block_public_acls = true - block_public_policy = true - bucket = aws_s3_bucket.logs.id - ignore_public_acls = true - restrict_public_buckets = true + bucket = aws_s3_bucket.state[each.key].id + policy = data.aws_iam_policy_document.state_https_only[each.key].json } resource "aws_s3_bucket_public_access_block" "state" { + for_each = local.state_environments + block_public_acls = true block_public_policy = true - bucket = aws_s3_bucket.state.id + bucket = aws_s3_bucket.state[each.key].id ignore_public_acls = true restrict_public_buckets = true } -#trivy:ignore:AVD-AWS-0132 -resource "aws_s3_bucket_server_side_encryption_configuration" "logs" { - bucket = aws_s3_bucket.logs.id - rule { - apply_server_side_encryption_by_default { - sse_algorithm = "AES256" - } - } -} - #trivy:ignore:AVD-AWS-0132 resource "aws_s3_bucket_server_side_encryption_configuration" "state" { - bucket = aws_s3_bucket.state.id + for_each = local.state_environments + + bucket = aws_s3_bucket.state[each.key].id + rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" @@ -213,14 +244,10 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "state" { } resource "aws_s3_bucket_versioning" "state" { - bucket = aws_s3_bucket.state.id - versioning_configuration { - status = "Enabled" - } -} + for_each = local.state_environments + + bucket = aws_s3_bucket.state[each.key].id -resource "aws_s3_bucket_versioning" "logs" { - bucket = aws_s3_bucket.logs.id versioning_configuration { status = "Enabled" } diff --git a/infrastructure/backend/outputs.tf b/infrastructure/backend/outputs.tf index 46ecb4e598..98e73812ca 100644 --- a/infrastructure/backend/outputs.tf +++ b/infrastructure/backend/outputs.tf @@ -1,9 +1,9 @@ -output "dynamodb_table_name" { - description = "The name of the DynamoDB table for Terraform state locking" - value = aws_dynamodb_table.state_lock.name +output "dynamodb_table_names" { + description = "The names of the per-environment DynamoDB tables for Terraform state locking." + value = { for env, table in aws_dynamodb_table.state_lock : env => table.name } } -output "s3_bucket_name" { - description = "The name of the S3 bucket for Terraform state" - value = aws_s3_bucket.state.bucket +output "state_bucket_names" { + description = "The names of the per-environment S3 buckets for Terraform state." + value = { for env, bucket in aws_s3_bucket.state : env => bucket.bucket } } diff --git a/infrastructure/backend/providers.tf b/infrastructure/backend/providers.tf index c9d7ccbdea..3f99c42c16 100644 --- a/infrastructure/backend/providers.tf +++ b/infrastructure/backend/providers.tf @@ -1,3 +1,4 @@ provider "aws" { - region = var.aws_region + profile = "nest-backend" + region = var.aws_region } diff --git a/infrastructure/backend/variables.tf b/infrastructure/backend/variables.tf index a0365669a4..6fef5536a9 100644 --- a/infrastructure/backend/variables.tf +++ b/infrastructure/backend/variables.tf @@ -27,3 +27,16 @@ variable "project_name" { type = string default = "nest" } + +variable "state_environments" { + description = "A list of environments to create separate state buckets for." + type = list(string) + default = ["bootstrap", "staging"] + + validation { + condition = alltrue([ + for env in var.state_environments : contains(["bootstrap", "staging"], env) + ]) + error_message = "Each environment must be 'bootstrap' or 'staging'." + } +} From b2cf613370843946d686622c360b56a26d061f04 Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Tue, 17 Feb 2026 20:51:12 +0530 Subject: [PATCH 02/16] add bootstrap terraform project --- infrastructure/bootstrap/.terraform.lock.hcl | 25 +++++ infrastructure/bootstrap/README.md | 73 ++++++++++++++ infrastructure/bootstrap/backend.tf | 6 ++ infrastructure/bootstrap/main.tf | 99 +++++++++++++++++++ infrastructure/bootstrap/outputs.tf | 4 + infrastructure/bootstrap/providers.tf | 4 + .../bootstrap/terraform.tfbackend.example | 4 + .../bootstrap/terraform.tfvars.example | 2 + infrastructure/bootstrap/variables.tf | 17 ++++ 9 files changed, 234 insertions(+) create mode 100644 infrastructure/bootstrap/.terraform.lock.hcl create mode 100644 infrastructure/bootstrap/README.md create mode 100644 infrastructure/bootstrap/backend.tf create mode 100644 infrastructure/bootstrap/main.tf create mode 100644 infrastructure/bootstrap/outputs.tf create mode 100644 infrastructure/bootstrap/providers.tf create mode 100644 infrastructure/bootstrap/terraform.tfbackend.example create mode 100644 infrastructure/bootstrap/terraform.tfvars.example create mode 100644 infrastructure/bootstrap/variables.tf diff --git a/infrastructure/bootstrap/.terraform.lock.hcl b/infrastructure/bootstrap/.terraform.lock.hcl new file mode 100644 index 0000000000..55057fd2e7 --- /dev/null +++ b/infrastructure/bootstrap/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.22.0" + constraints = "6.22.0" + hashes = [ + "h1:TV1UZ7DzioV1EUY/lMS+eIInU379DA1Q2QwnEGGZMks=", + "zh:0ed7ceb13bade9076021a14f995d07346d3063f4a419a904d5804d76e372bbda", + "zh:195dcde5a4b0def82bc3379053edc13941ff94ea5905808fe575f7c7bbd66693", + "zh:4047c4dba121d29859b72d2155c47f969b41d3c5768f73dff5d8a0cc55f74e52", + "zh:5694f37d6ea69b6f96dfb30d53e66f7a41c1aad214c212b6ffa54bdd799e3b27", + "zh:6cf8bb7d984b1fae9fd10d6ce1e62f6c10751a1040734b75a1f7286609782e49", + "zh:737d0e600dfe2626b4d6fc5dd2b24c0997fd983228a7a607b9176a1894a281a0", + "zh:7d328a195ce36b1170afe6758cf88223c8765620211f5cc0451bdd6899243b4e", + "zh:7edb4bc34baeba92889bd9ed50b34c04b3eeb3d8faa8bb72699c6335a2e95bab", + "zh:8e71836814e95454b00c51f3cb3e10fd78a59f7dc4c5362af64233fee989790d", + "zh:9367f63b23d9ddfab590b2247a8ff5ccf83410cbeca43c6e441c488c45efff4c", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a007de80ffde8539a73ee39fcfbe7ed12e025c98cd29b2110a7383b41a4aad39", + "zh:aae7b7aed8bf3a4bea80a9a2f08fef1adeb748beff236c4a54af93bb6c09a56c", + "zh:b5a16b59d4210c1eaf35c8c027ecdab9e074dd081d602f5112eecdebf2e1866d", + "zh:d479bad0a004e4893bf0ba6c6cd867fefd14000051bbe3de5b44a925e3d46cd5", + ] +} diff --git a/infrastructure/bootstrap/README.md b/infrastructure/bootstrap/README.md new file mode 100644 index 0000000000..fcfe609f1e --- /dev/null +++ b/infrastructure/bootstrap/README.md @@ -0,0 +1,73 @@ +## Inline Permissions +Use the following inline permissions for the `nest-bootstrap` IAM User +*Note*: replace ${AWS_ACCOUNT_ID} and ${AWS_BACKEND_KMS_KEY} with approriate values. + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "S3StateAccess", + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:ListBucket", + "s3:PutObject" + ], + "Resource": [ + "arn:aws:s3:::nest-terraform-state-bootstrap-*", + "arn:aws:s3:::nest-terraform-state-bootstrap-*/*" + ] + }, + { + "Sid": "DynamoDBStateLocking", + "Effect": "Allow", + "Action": [ + "dynamodb:DeleteItem", + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:PutItem" + ], + "Resource": "arn:aws:dynamodb:*:${AWS_ACCOUNT_ID}:table/nest-terraform-state-lock-bootstrap" + }, + { + "Sid": "IAMManagement", + "Effect": "Allow", + "Action": [ + "iam:AttachRolePolicy", + "iam:CreatePolicy", + "iam:CreatePolicyVersion", + "iam:CreateRole", + "iam:DeletePolicy", + "iam:DeletePolicyVersion", + "iam:DeleteRole", + "iam:DetachRolePolicy", + "iam:GetPolicy", + "iam:GetPolicyVersion", + "iam:GetRole", + "iam:ListAttachedRolePolicies", + "iam:ListInstanceProfilesForRole", + "iam:ListPolicyVersions", + "iam:ListRolePolicies", + "iam:TagPolicy", + "iam:TagRole", + "iam:UntagPolicy", + "iam:UntagRole", + "iam:UpdateRole" + ], + "Resource": [ + "arn:aws:iam::${AWS_ACCOUNT_ID}:role/nest-*-terraform", + "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/nest-*-terraform" + ] + }, + { + "Sid": "KMSManagement", + "Effect": "Allow", + "Action": [ + "kms:Decrypt" + ], + "Resource": "${AWS_BACKEND_KMS_KEY}" + } + ] +} +``` diff --git a/infrastructure/bootstrap/backend.tf b/infrastructure/bootstrap/backend.tf new file mode 100644 index 0000000000..864fb653b9 --- /dev/null +++ b/infrastructure/bootstrap/backend.tf @@ -0,0 +1,6 @@ +terraform { + backend "s3" { + encrypt = true + key = "bootstrap/terraform.tfstate" + } +} diff --git a/infrastructure/bootstrap/main.tf b/infrastructure/bootstrap/main.tf new file mode 100644 index 0000000000..5265d436b9 --- /dev/null +++ b/infrastructure/bootstrap/main.tf @@ -0,0 +1,99 @@ +terraform { + required_version = "1.14.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "6.22.0" + } + } +} + +locals { + common_tags = { + Environment = "bootstrap" + ManagedBy = "Terraform" + Project = var.project_name + } + environments = toset(var.environments) +} + +data "aws_caller_identity" "current" {} + +data "aws_iam_policy_document" "terraform" { + for_each = local.environments + + statement { + actions = [ + "s3:GetObject", + "s3:ListBucket", + "s3:PutObject", + ] + effect = "Allow" + resources = [ + "arn:aws:s3:::${var.project_name}-*", + "arn:aws:s3:::${var.project_name}-*/*", + ] + sid = "S3StateAccess" + } + + statement { + actions = [ + "dynamodb:DeleteItem", + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:PutItem", + ] + effect = "Allow" + resources = [ + "arn:aws:dynamodb:*:${data.aws_caller_identity.current.account_id}:table/${var.project_name}-terraform-state-lock-${each.key}", + ] + sid = "DynamoDBStateLocking" + } +} + +resource "aws_iam_policy" "terraform" { + for_each = local.environments + + name = "${var.project_name}-${each.key}-terraform" + policy = data.aws_iam_policy_document.terraform[each.key].json + tags = merge(local.common_tags, { + Environment = each.key + Name = "${var.project_name}-${each.key}-terraform" + }) +} + +resource "aws_iam_role" "terraform" { + for_each = local.environments + + name = "${var.project_name}-${each.key}-terraform" + tags = merge(local.common_tags, { + Environment = each.key + Name = "${var.project_name}-${each.key}-terraform" + }) + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Condition = { + StringEquals = { + "sts:ExternalId" = "${var.project_name}-${each.key}-terraform" + } + } + Effect = "Allow" + Principal = { + AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/${var.project_name}-${each.key}" + } + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "terraform" { + for_each = local.environments + + policy_arn = aws_iam_policy.terraform[each.key].arn + role = aws_iam_role.terraform[each.key].name +} diff --git a/infrastructure/bootstrap/outputs.tf b/infrastructure/bootstrap/outputs.tf new file mode 100644 index 0000000000..904936b111 --- /dev/null +++ b/infrastructure/bootstrap/outputs.tf @@ -0,0 +1,4 @@ +output "terraform_role_arns" { + description = "The ARNs of the Terraform IAM roles, keyed by environment." + value = { for env in local.environments : env => aws_iam_role.terraform[env].arn } +} diff --git a/infrastructure/bootstrap/providers.tf b/infrastructure/bootstrap/providers.tf new file mode 100644 index 0000000000..7c185d6c8f --- /dev/null +++ b/infrastructure/bootstrap/providers.tf @@ -0,0 +1,4 @@ +provider "aws" { + profile = "nest-bootstrap" + region = var.aws_region +} diff --git a/infrastructure/bootstrap/terraform.tfbackend.example b/infrastructure/bootstrap/terraform.tfbackend.example new file mode 100644 index 0000000000..b50462c5a8 --- /dev/null +++ b/infrastructure/bootstrap/terraform.tfbackend.example @@ -0,0 +1,4 @@ +bucket = "${STATE_BUCKET_NAME}" +dynamodb_table = "nest-terraform-state-lock-bootstrap" +profile = "nest-bootstrap" +region = "us-east-2" diff --git a/infrastructure/bootstrap/terraform.tfvars.example b/infrastructure/bootstrap/terraform.tfvars.example new file mode 100644 index 0000000000..6eda53c7a6 --- /dev/null +++ b/infrastructure/bootstrap/terraform.tfvars.example @@ -0,0 +1,2 @@ +aws_region = "us-east-2" +project_name = "nest" diff --git a/infrastructure/bootstrap/variables.tf b/infrastructure/bootstrap/variables.tf new file mode 100644 index 0000000000..471f7eb514 --- /dev/null +++ b/infrastructure/bootstrap/variables.tf @@ -0,0 +1,17 @@ +variable "aws_region" { + description = "The AWS region to deploy resources in." + type = string + default = "us-east-2" +} + +variable "environments" { + description = "The environments to create Terraform roles for." + type = list(string) + default = ["staging"] +} + +variable "project_name" { + description = "The name of the project." + type = string + default = "nest" +} From de4c4599ba0e08d20a683c790f46803af39d91ee Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Tue, 17 Feb 2026 23:10:50 +0530 Subject: [PATCH 03/16] add initial permissions --- infrastructure/INFO.md | 43 ++ infrastructure/README.md | 7 +- infrastructure/bootstrap/README.md | 7 +- infrastructure/bootstrap/main.tf | 486 +++++++++++++++++- infrastructure/staging/providers.tf | 3 +- .../staging/terraform.tfbackend.example | 3 +- 6 files changed, 523 insertions(+), 26 deletions(-) create mode 100644 infrastructure/INFO.md diff --git a/infrastructure/INFO.md b/infrastructure/INFO.md new file mode 100644 index 0000000000..210c8abf39 --- /dev/null +++ b/infrastructure/INFO.md @@ -0,0 +1,43 @@ +# Policies + +- Minimum policies required to use the terraform remote backend: +(Replace ${} variables appropriately) + +```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "S3StateManagement", + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::${TERRAFORM_STATE_BUCKET_NAME}", + "arn:aws:s3:::${TERRAFORM_STATE_BUCKET_NAME}/*" + ] + }, + { + "Sid": "DynamoDBStateManagement", + "Effect": "Allow", + "Action": [ + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:DeleteItem" + ], + "Resource": "arn:aws:dynamodb:${AWS_REGION}:${AWS_ACCOUNT_ID}:table/${TERRAFORM_DYNAMODB_TABLE_NAME}" + }, + { + "Sid": "KMSStateManagement", + "Effect": "Allow", + "Action": [ + "kms:Decrypt" + ], + "Resource": "${AWS_BACKEND_KMS_KEY}" + } + ] +} +``` diff --git a/infrastructure/README.md b/infrastructure/README.md index f4a6551201..af5a553d93 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -9,7 +9,12 @@ Ensure you have the following setup/installed: - Setup Project: [CONTRIBUTING.md](https://github.com/OWASP/Nest/blob/main/CONTRIBUTING.md) - Terraform: [Terraform Documentation](https://developer.hashicorp.com/terraform/docs) - AWS CLI: [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) -- An AWS account with credentials configured locally. +- An AWS account with credential profiles configured locally: + - [nest-backend] + - [nest-bootstrap] + - [nest-staging] - this user must assume the role created by `nest-bootstrap` +Note: Refer to the respective README.md files for more information. +- Read `INFO.md` for information related to policies. ## Setting up the infrastructure diff --git a/infrastructure/bootstrap/README.md b/infrastructure/bootstrap/README.md index fcfe609f1e..b1905bb108 100644 --- a/infrastructure/bootstrap/README.md +++ b/infrastructure/bootstrap/README.md @@ -41,14 +41,17 @@ Use the following inline permissions for the `nest-bootstrap` IAM User "iam:DeletePolicy", "iam:DeletePolicyVersion", "iam:DeleteRole", + "iam:DeleteRolePolicy", "iam:DetachRolePolicy", "iam:GetPolicy", "iam:GetPolicyVersion", "iam:GetRole", + "iam:GetRolePolicy", "iam:ListAttachedRolePolicies", "iam:ListInstanceProfilesForRole", "iam:ListPolicyVersions", "iam:ListRolePolicies", + "iam:PutRolePolicy", "iam:TagPolicy", "iam:TagRole", "iam:UntagPolicy", @@ -56,8 +59,8 @@ Use the following inline permissions for the `nest-bootstrap` IAM User "iam:UpdateRole" ], "Resource": [ - "arn:aws:iam::${AWS_ACCOUNT_ID}:role/nest-*-terraform", - "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/nest-*-terraform" + "arn:aws:iam::652192963764:role/nest-*-terraform", + "arn:aws:iam::652192963764:policy/nest-*-terraform" ] }, { diff --git a/infrastructure/bootstrap/main.tf b/infrastructure/bootstrap/main.tf index 5265d436b9..2d0dc76fc5 100644 --- a/infrastructure/bootstrap/main.tf +++ b/infrastructure/bootstrap/main.tf @@ -25,16 +25,19 @@ data "aws_iam_policy_document" "terraform" { statement { actions = [ - "s3:GetObject", - "s3:ListBucket", - "s3:PutObject", - ] - effect = "Allow" - resources = [ - "arn:aws:s3:::${var.project_name}-*", - "arn:aws:s3:::${var.project_name}-*/*", + "acm:AddTagsToCertificate", + "acm:DeleteCertificate", + "acm:DescribeCertificate", + "acm:ListCertificates", + "acm:ListTagsForCertificate", + "acm:RemoveTagsFromCertificate", + "acm:RequestCertificate", + "acm:ResendValidationEmail", + "acm:UpdateCertificateOptions", ] - sid = "S3StateAccess" + effect = "Allow" + resources = ["arn:aws:acm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:certificate/*"] + sid = "ACMManagement" } statement { @@ -50,17 +53,457 @@ data "aws_iam_policy_document" "terraform" { ] sid = "DynamoDBStateLocking" } -} -resource "aws_iam_policy" "terraform" { - for_each = local.environments + statement { + sid = "EC2Management" + effect = "Allow" + actions = [ + "ec2:AllocateAddress", + "ec2:AssociateRouteTable", + "ec2:AttachInternetGateway", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateFlowLogs", + "ec2:CreateInternetGateway", + "ec2:CreateNatGateway", + "ec2:CreateNetworkAcl", + "ec2:CreateNetworkAclEntry", + "ec2:CreateRoute", + "ec2:CreateRouteTable", + "ec2:CreateSecurityGroup", + "ec2:CreateSubnet", + "ec2:CreateTags", + "ec2:CreateVpc", + "ec2:CreateVpcEndpoint", + "ec2:DeleteFlowLogs", + "ec2:DeleteInternetGateway", + "ec2:DeleteNatGateway", + "ec2:DeleteNetworkAcl", + "ec2:DeleteNetworkAclEntry", + "ec2:DeleteRoute", + "ec2:DeleteRouteTable", + "ec2:DeleteSecurityGroup", + "ec2:DeleteSubnet", + "ec2:DeleteTags", + "ec2:DeleteVpc", + "ec2:DeleteVpcEndpoint", + "ec2:Describe*", + "ec2:DetachInternetGateway", + "ec2:DisassociateRouteTable", + "ec2:ModifySubnetAttribute", + "ec2:ModifyVpcAttribute", + "ec2:ModifyVpcEndpoint", + "ec2:ReleaseAddress", + "ec2:ReplaceNetworkAclAssociation", + "ec2:ReplaceNetworkAclEntry", + "ec2:ReplaceRoute", + "ec2:ReplaceRouteTableAssociation", + "ec2:RevokeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress", + ] + resources = ["*"] + } - name = "${var.project_name}-${each.key}-terraform" - policy = data.aws_iam_policy_document.terraform[each.key].json - tags = merge(local.common_tags, { - Environment = each.key - Name = "${var.project_name}-${each.key}-terraform" - }) + statement { + actions = [ + "ecr:CreateRepository", + "ecr:DeleteLifecyclePolicy", + "ecr:DeleteRepository", + "ecr:DescribeRepositories", + "ecr:GetLifecyclePolicy", + "ecr:GetRepositoryPolicy", + "ecr:ListTagsForResource", + "ecr:PutImageScanningConfiguration", + "ecr:PutLifecyclePolicy", + "ecr:SetRepositoryPolicy", + "ecr:TagResource", + "ecr:UntagResource", + ] + effect = "Allow" + resources = [ + "arn:aws:ecr:*:${data.aws_caller_identity.current.account_id}:repository/${var.project_name}-${each.key}-*", + ] + sid = "ECRManagement" + } + + statement { + sid = "AppAutoscalingManagement" + effect = "Allow" + actions = [ + "application-autoscaling:DeleteScalingPolicy", + "application-autoscaling:DeregisterScalableTarget", + "application-autoscaling:DescribeScalableTargets", + "application-autoscaling:DescribeScalingPolicies", + "application-autoscaling:ListTagsForResource", + "application-autoscaling:PutScalingPolicy", + "application-autoscaling:RegisterScalableTarget", + "application-autoscaling:TagResource", + "application-autoscaling:UntagResource", + ] + resources = ["*"] + } + + statement { + sid = "CloudWatchEventsManagement" + effect = "Allow" + actions = [ + "events:DeleteRule", + "events:DescribeRule", + "events:ListTargetsByRule", + "events:PutRule", + "events:PutTargets", + "events:RemoveTargets", + "events:TagResource", + "events:UntagResource", + ] + resources = ["arn:aws:events:${var.aws_region}:${data.aws_caller_identity.current.account_id}:rule/${var.project_name}-${each.key}-*"] + } + + statement { + sid = "DatabaseManagement" + effect = "Allow" + actions = [ + "elasticache:AddTagsToResource", + "elasticache:CreateCacheSubnetGroup", + "elasticache:CreateReplicationGroup", + "elasticache:DeleteCacheSubnetGroup", + "elasticache:DeleteReplicationGroup", + "elasticache:DescribeCacheClusters", + "elasticache:DescribeCacheSubnetGroups", + "elasticache:DescribeReplicationGroups", + "elasticache:ListTagsForResource", + "elasticache:ModifyCacheSubnetGroup", + "elasticache:ModifyReplicationGroup", + "elasticache:RemoveTagsFromResource", + "rds:AddTagsToResource", + "rds:CreateDBInstance", + "rds:CreateDBSubnetGroup", + "rds:DeleteDBInstance", + "rds:DeleteDBSubnetGroup", + "rds:DescribeDBInstances", + "rds:DescribeDBSubnetGroups", + "rds:ListTagsForResource", + "rds:ModifyDBInstance", + "rds:ModifyDBSubnetGroup", + "rds:RemoveTagsFromResource", + ] + resources = ["*"] + } + + statement { + sid = "EC2ResourceSpecific" + effect = "Allow" + actions = [ + "ec2:AttachInternetGateway", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateFlowLogs", + "ec2:CreateNetworkAcl", + "ec2:CreateNetworkAclEntry", + "ec2:CreateTags", + "ec2:DeleteFlowLogs", + "ec2:DeleteNetworkAcl", + "ec2:DeleteNetworkAclEntry", + "ec2:DeleteSecurityGroup", + "ec2:DeleteTags", + "ec2:DeleteVpc", + "ec2:DescribeFlowLogs", + "ec2:DescribeNetworkAcls", + "ec2:DetachInternetGateway", + "ec2:ReplaceNetworkAclAssociation", + "ec2:ReplaceNetworkAclEntry", + "ec2:RevokeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress", + ] + resources = ["arn:aws:ec2:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*"] + } + + statement { + sid = "IAMPassRole" + effect = "Allow" + actions = [ + "iam:PassRole", + ] + resources = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.project_name}-${each.key}-*"] + condition { + test = "StringEquals" + variable = "iam:PassedToService" + values = [ + "ecs-tasks.amazonaws.com", + "lambda.amazonaws.com", + "rds.amazonaws.com", + ] + } + } + + statement { + sid = "LambdaManagement" + effect = "Allow" + actions = [ + "lambda:AddPermission", + "lambda:CreateAlias", + "lambda:DeleteAlias", + "lambda:GetAlias", + "lambda:GetFunction", + "lambda:GetPolicy", + "lambda:ListTags", + "lambda:ListVersionsByFunction", + "lambda:RemovePermission", + "lambda:TagResource", + "lambda:UntagResource", + "lambda:UpdateAlias", + ] + resources = ["arn:aws:lambda:${var.aws_region}:${data.aws_caller_identity.current.account_id}:function:${var.project_name}-${each.key}-*"] + } + + statement { + sid = "SecretsManagerManagement" + effect = "Allow" + actions = [ + "secretsmanager:CreateSecret", + "secretsmanager:DeleteSecret", + "secretsmanager:DescribeSecret", + "secretsmanager:GetResourcePolicy", + "secretsmanager:GetSecretValue", + "secretsmanager:PutSecretValue", + "secretsmanager:RestoreSecret", + "secretsmanager:RotateSecret", + "secretsmanager:TagResource", + "secretsmanager:UntagResource", + "secretsmanager:UpdateSecret", + ] + resources = ["arn:aws:secretsmanager:${var.aws_region}:${data.aws_caller_identity.current.account_id}:secret:${var.project_name}-${each.key}-*"] + } + + statement { + sid = "ECSClusterManagement" + effect = "Allow" + actions = [ + "ecs:CreateCluster", + "ecs:DeleteCluster", + "ecs:DescribeClusters", + "ecs:ListTagsForResource", + "ecs:PutClusterCapacityProviders", + "ecs:TagResource", + "ecs:UntagResource", + "ecs:UpdateCluster", + ] + resources = ["arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:cluster/${var.project_name}-${each.key}-*"] + } + + statement { + sid = "ECSGlobal" + effect = "Allow" + actions = [ + "ecs:DeregisterTaskDefinition", + "ecs:DescribeTaskDefinition", + "ecs:ListClusters", + "ecs:ListTaskDefinitions", + "ecs:RegisterTaskDefinition", + "ecs:TagResource", + ] + resources = ["*"] + } + + statement { + sid = "ELBManagement" + effect = "Allow" + actions = [ + "elasticloadbalancing:AddTags", + "elasticloadbalancing:CreateListener", + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:CreateTargetGroup", + "elasticloadbalancing:DeleteListener", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:DeleteTargetGroup", + "elasticloadbalancing:Describe*", + "elasticloadbalancing:ModifyListener", + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:RemoveTags", + "elasticloadbalancing:SetSecurityGroups", + ] + resources = ["*"] + } + + statement { + sid = "ECSServiceManagement" + effect = "Allow" + actions = [ + "ecs:CreateService", + "ecs:DeleteService", + "ecs:UpdateService", + "ecs:DescribeServices" + ] + resources = ["arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:service/${var.project_name}-${each.key}-*/*"] + } + + statement { + sid = "ECSTaskDefinition" + effect = "Allow" + actions = [ + "ecs:DescribeTaskDefinition", + "ecs:TagResource", + ] + resources = ["arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:task-definition/${var.project_name}-${each.key}-*:*"] + } + + statement { + sid = "EventBridgeManagement" + actions = [ + "events:DeleteRule", + "events:DescribeRule", + "events:ListTagsForResource", + "events:ListTargetsByRule", + "events:PutRule", + "events:PutTargets", + "events:RemoveTargets", + "events:TagResource", + "events:UntagResource", + ] + effect = "Allow" + resources = [ + "arn:aws:events:*:${data.aws_caller_identity.current.account_id}:rule/${var.project_name}-${each.key}-*", + ] + } + + statement { + sid = "IAMManagement" + actions = [ + "iam:AttachRolePolicy", + "iam:CreatePolicy", + "iam:CreatePolicyVersion", + "iam:CreateRole", + "iam:DeletePolicy", + "iam:DeletePolicyVersion", + "iam:DeleteRole", + "iam:DetachRolePolicy", + "iam:GetPolicy", + "iam:GetPolicyVersion", + "iam:GetRole", + "iam:ListAttachedRolePolicies", + "iam:ListInstanceProfilesForRole", + "iam:ListPolicyVersions", + "iam:ListRolePolicies", + "iam:PassRole", + "iam:TagPolicy", + "iam:TagRole", + "iam:UntagPolicy", + "iam:UntagRole", + "iam:UpdateRole", + "iam:UpdateAssumeRolePolicy", + ] + effect = "Allow" + resources = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/${var.project_name}-${each.key}-*", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.project_name}-${each.key}-*", + ] + } + + statement { + sid = "KMSCreateAll" + actions = [ + "kms:CreateAlias", + "kms:CreateKey", + "kms:Decrypt", + "kms:DeleteAlias", + "kms:DescribeKey", + "kms:DisableKeyRotation", + "kms:EnableKeyRotation", + "kms:GenerateDataKey", + "kms:GetKeyPolicy", + "kms:GetKeyRotationStatus", + "kms:ListAliases", + "kms:ListResourceTags", + "kms:PutKeyPolicy", + "kms:ScheduleKeyDeletion", + "kms:TagResource", + "kms:UntagResource", + "kms:UpdateAlias", + "kms:UpdateKeyDescription", + ] + effect = "Allow" + resources = ["*"] + } + + statement { + actions = [ + "logs:CreateLogGroup", + "logs:DeleteLogGroup", + "logs:DescribeLogGroups", + "logs:ListTagsForResource", + "logs:ListTagsLogGroup", + "logs:PutRetentionPolicy", + "logs:TagLogGroup", + "logs:TagResource", + "logs:UntagLogGroup", + "logs:UntagResource", + ] + effect = "Allow" + resources = ["*"] + sid = "CloudWatchLogsManagement" + } + + statement { + sid = "S3Management" + actions = [ + "s3:CreateBucket", + "s3:DeleteBucket", + "s3:GetAccelerateConfiguration", + "s3:GetBucketAcl", + "s3:GetBucketCors", + "s3:GetBucketLogging", + "s3:GetBucketObjectLockConfiguration", + "s3:GetBucketOwnershipControls", + "s3:GetBucketPolicy", + "s3:GetBucketPublicAccessBlock", + "s3:GetBucketReplication", + "s3:GetBucketRequestPayment", + "s3:GetBucketTagging", + "s3:GetBucketVersioning", + "s3:GetBucketWebsite", + "s3:GetEncryptionConfiguration", + "s3:GetLifecycleConfiguration", + "s3:GetObject", + "s3:GetReplicationConfiguration", + "s3:ListBucket", + "s3:PutBucketLogging", + "s3:PutBucketObjectLockConfiguration", + "s3:PutBucketOwnershipControls", + "s3:PutBucketPolicy", + "s3:PutBucketPublicAccessBlock", + "s3:PutBucketTagging", + "s3:PutBucketVersioning", + "s3:PutEncryptionConfiguration", + "s3:PutLifecycleConfiguration", + "s3:PutObject", + "s3:PutBucketAcl" + ] + effect = "Allow" + resources = [ + "arn:aws:s3:::${var.project_name}-*", + "arn:aws:s3:::${var.project_name}-*/*", + ] + } + + statement { + sid = "SSMManagement" + actions = [ + "ssm:AddTagsToResource", + "ssm:DeleteParameter", + "ssm:DescribeParameters", + "ssm:GetParameter", + "ssm:GetParameters", + "ssm:ListTagsForResource", + "ssm:PutParameter", + "ssm:RemoveTagsFromResource", + ] + effect = "Allow" + resources = [ + "arn:aws:ssm:*:${data.aws_caller_identity.current.account_id}:*", + ] + } } resource "aws_iam_role" "terraform" { @@ -91,9 +534,10 @@ resource "aws_iam_role" "terraform" { }) } -resource "aws_iam_role_policy_attachment" "terraform" { +resource "aws_iam_role_policy" "terraform" { for_each = local.environments - policy_arn = aws_iam_policy.terraform[each.key].arn - role = aws_iam_role.terraform[each.key].name + name = "${var.project_name}-${each.key}-terraform-inline" + role = aws_iam_role.terraform[each.key].id + policy = data.aws_iam_policy_document.terraform[each.key].json } diff --git a/infrastructure/staging/providers.tf b/infrastructure/staging/providers.tf index c9d7ccbdea..1a5fa9c8e9 100644 --- a/infrastructure/staging/providers.tf +++ b/infrastructure/staging/providers.tf @@ -1,3 +1,4 @@ provider "aws" { - region = var.aws_region + profile = "nest-staging" + region = var.aws_region } diff --git a/infrastructure/staging/terraform.tfbackend.example b/infrastructure/staging/terraform.tfbackend.example index 957bf8a97b..8e8699bc8e 100644 --- a/infrastructure/staging/terraform.tfbackend.example +++ b/infrastructure/staging/terraform.tfbackend.example @@ -1,3 +1,4 @@ bucket = "${STATE_BUCKET_NAME}" -dynamodb_table = "nest-terraform-state-lock" +dynamodb_table = "nest-terraform-state-lock-staging" +profile = "nest-staging" region = "us-east-2" From 64c996109eb59e3cc94e5de697237c6a409562db Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Tue, 17 Feb 2026 23:11:14 +0530 Subject: [PATCH 04/16] Update CI/CD --- .github/workflows/run-ci-cd.yaml | 127 ++++++++++++++++++++++++------- 1 file changed, 101 insertions(+), 26 deletions(-) diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index 416fa356c7..c5977698bd 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -441,14 +441,14 @@ jobs: env: RELEASE_VERSION: ${{ needs.set-release-version.outputs.release_version }} environment: staging - if: | - github.repository == 'OWASP/Nest' && - github.ref == 'refs/heads/main' + #if: | + #github.repository == 'OWASP/Nest' && + #github.ref == 'refs/heads/main' needs: - - run-backend-tests - - run-frontend-a11y-tests - - run-frontend-e2e-tests - - run-frontend-unit-tests + #- run-backend-tests + #- run-frontend-a11y-tests + #- run-frontend-e2e-tests + #- run-frontend-unit-tests - run-infrastructure-tests - set-release-version permissions: @@ -488,16 +488,16 @@ jobs: OWASP_UID=1001 cache-from: | type=gha - type=registry,ref=owasp/nest:backend-staging-cache + type=registry,ref=rudransh-shrivastava/nest:backend-staging-cache cache-to: | - type=registry,ref=owasp/nest:backend-staging-cache + type=registry,ref=rudransh-shrivastava/nest:backend-staging-cache context: backend file: docker/backend/Dockerfile load: true platforms: linux/amd64 push: true tags: | - owasp/nest:backend-staging + rudransh-shrivastava/nest:backend-staging ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/nest-staging-backend:latest - name: Prepare frontend public environment @@ -532,7 +532,7 @@ jobs: - name: Get backend image size id: backend-size run: | - IMAGE_NAME="owasp/nest:backend-staging" + IMAGE_NAME="rudransh-shrivastava/nest:backend-staging" RAW_SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}') DISPLAY_SIZE=$(numfmt --to=iec --suffix=B "$RAW_SIZE") echo "human_readable=$DISPLAY_SIZE" >> $GITHUB_OUTPUT @@ -542,9 +542,9 @@ jobs: with: cache-from: | type=gha - type=registry,ref=owasp/nest:frontend-staging-cache + type=registry,ref=rudransh-shrivastava/nest:frontend-staging-cache cache-to: | - type=registry,ref=owasp/nest:frontend-staging-cache + type=registry,ref=rudransh-shrivastava/nest:frontend-staging-cache context: frontend file: docker/frontend/Dockerfile load: true @@ -554,13 +554,13 @@ jobs: RELEASE_VERSION=${{ needs.set-release-version.outputs.release_version }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} tags: | - owasp/nest:frontend-staging + rudransh-shrivastava/nest:frontend-staging ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/nest-staging-frontend:latest - name: Get frontend image size id: frontend-size run: | - IMAGE_NAME="owasp/nest:frontend-staging" + IMAGE_NAME="rudransh-shrivastava/nest:frontend-staging" RAW_SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}') DISPLAY_SIZE=$(numfmt --to=iec --suffix=B "$RAW_SIZE") echo "human_readable=$DISPLAY_SIZE" >> $GITHUB_OUTPUT @@ -598,16 +598,16 @@ jobs: - name: Run Trivy security scan via Makefile run: | - make security-scan-backend-image BACKEND_IMAGE_NAME=owasp/nest:backend-staging - make security-scan-frontend-image FRONTEND_IMAGE_NAME=owasp/nest:frontend-staging + make security-scan-backend-image BACKEND_IMAGE_NAME=rudransh-shrivastava/nest:backend-staging + make security-scan-frontend-image FRONTEND_IMAGE_NAME=rudransh-shrivastava/nest:frontend-staging - name: Generate SBOM for backend image run: | - make sbom-backend-image BACKEND_IMAGE_NAME=owasp/nest:backend-staging + make sbom-backend-image BACKEND_IMAGE_NAME=rudransh-shrivastava/nest:backend-staging - name: Generate SBOM for frontend image run: | - make sbom-frontend-image FRONTEND_IMAGE_NAME=owasp/nest:frontend-staging + make sbom-frontend-image FRONTEND_IMAGE_NAME=rudransh-shrivastava/nest:frontend-staging - name: Upload SBOMs uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f @@ -618,18 +618,87 @@ jobs: frontend-sbom-${{ env.RELEASE_VERSION }}.cdx.json timeout-minutes: 5 + bootstrap-staging-nest: + name: Bootstrap Nest Staging + env: + TF_INPUT: false + TF_IN_AUTOMATION: true + environment: staging + # if: | + # github.repository == 'OWASP/Nest' && + # github.ref == 'refs/heads/main' + #needs: + #- scan-staging-images + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 + with: + aws-access-key-id: ${{ secrets.BOOTSTRAP_AWS_ACCESS_KEY_ID }} + aws-region: ${{ vars.AWS_REGION }} + aws-secret-access-key: ${{ secrets.BOOTSTRAP_AWS_SECRET_ACCESS_KEY }} + + - name: Install Terraform + uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd + with: + terraform_version: 1.14.0 + + - name: Prepare terraform backend + env: + AWS_REGION: ${{ vars.AWS_REGION }} + TF_STATE_BUCKET_NAME: ${{ secrets.BOOTSTRAP_TF_STATE_BUCKET_NAME }} + TF_STATE_DYNAMODB_TABLE_NAME: ${{ secrets.BOOTSTRAP_TF_STATE_DYNAMODB_TABLE_NAME }} + run: | + umask 377 + cat > infrastructure/bootstrap/terraform.tfbackend <<-EOF + bucket="$TF_STATE_BUCKET_NAME" + dynamodb_table="$TF_STATE_DYNAMODB_TABLE_NAME" + region="$AWS_REGION" + EOF + + - name: Prepare terraform variables + env: + AWS_REGION: ${{ vars.AWS_REGION }} + PROJECT_NAME: 'nest' + run: | + umask 377 + cat > infrastructure/bootstrap/terraform.tfvars <<-EOF + aws_region="$AWS_REGION" + project_name="$PROJECT_NAME" + EOF + + - name: Terraform Init + working-directory: infrastructure/bootstrap + run: terraform init -backend-config=terraform.tfbackend + + - name: Terraform Validate + working-directory: infrastructure/bootstrap + run: terraform validate + + - name: Terraform Apply + working-directory: infrastructure/bootstrap + run: terraform apply -auto-approve + timeout-minutes: 10 + plan-staging-nest: name: Plan Nest Staging env: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - if: | - github.repository == 'OWASP/Nest' && - github.ref == 'refs/heads/main' + #if: | + #github.repository == 'OWASP/Nest' && + #github.ref == 'refs/heads/main' needs: - - scan-staging-images + - bootstrap-staging-nest + #- scan-staging-images - set-release-version + permissions: contents: read runs-on: ubuntu-latest @@ -643,6 +712,9 @@ jobs: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-region: ${{ vars.AWS_REGION }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + role-duration-seconds: 1200 + role-session-name: GitHubActions-StagingPlan + role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform - name: Install Terraform uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd @@ -722,9 +794,9 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - if: | - github.repository == 'OWASP/Nest' && - github.ref == 'refs/heads/main' + #if: | + #github.repository == 'OWASP/Nest' && + #github.ref == 'refs/heads/main' needs: - plan-staging-nest permissions: @@ -740,6 +812,9 @@ jobs: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-region: ${{ vars.AWS_REGION }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + role-duration-seconds: 1200 + role-session-name: GitHubActions-StagingPlan + role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform - name: Install Terraform uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd From 0ecbb139e7016741ea55758233e61a1a2f6825f6 Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Tue, 17 Feb 2026 23:15:44 +0530 Subject: [PATCH 05/16] update code --- .github/workflows/run-ci-cd.yaml | 75 +-- .gitignore | 8 +- backend/zappa_settings.template.json | 2 +- cspell/custom-dict.txt | 2 + infrastructure/INFO.md | 43 -- infrastructure/README.md | 68 ++- infrastructure/backend/README.md | 4 +- infrastructure/backend/main.tf | 6 +- infrastructure/backend/providers.tf | 3 +- infrastructure/bootstrap/README.md | 17 +- infrastructure/bootstrap/main.tf | 537 ++++++++++-------- infrastructure/bootstrap/providers.tf | 3 +- .../bootstrap/terraform.tfbackend.example | 1 - infrastructure/staging/README.md | 50 ++ infrastructure/staging/providers.tf | 3 +- .../staging/terraform.tfbackend.example | 1 - 16 files changed, 490 insertions(+), 333 deletions(-) delete mode 100644 infrastructure/INFO.md create mode 100644 infrastructure/staging/README.md diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index c5977698bd..1551d448a0 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -441,14 +441,14 @@ jobs: env: RELEASE_VERSION: ${{ needs.set-release-version.outputs.release_version }} environment: staging - #if: | - #github.repository == 'OWASP/Nest' && - #github.ref == 'refs/heads/main' + if: | + github.repository == 'OWASP/Nest' && + github.ref == 'refs/heads/main' needs: - #- run-backend-tests - #- run-frontend-a11y-tests - #- run-frontend-e2e-tests - #- run-frontend-unit-tests + - run-backend-tests + - run-frontend-a11y-tests + - run-frontend-e2e-tests + - run-frontend-unit-tests - run-infrastructure-tests - set-release-version permissions: @@ -476,6 +476,10 @@ jobs: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-region: ${{ vars.AWS_REGION }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + role-duration-seconds: 1200 + role-external-id: nest-staging-terraform + role-session-name: GitHubActions-BuildStagingImages + role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform - name: Login to Amazon ECR uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 @@ -488,16 +492,16 @@ jobs: OWASP_UID=1001 cache-from: | type=gha - type=registry,ref=rudransh-shrivastava/nest:backend-staging-cache + type=registry,ref=owasp/nest:backend-staging-cache cache-to: | - type=registry,ref=rudransh-shrivastava/nest:backend-staging-cache + type=registry,ref=owasp/nest:backend-staging-cache context: backend file: docker/backend/Dockerfile load: true platforms: linux/amd64 push: true tags: | - rudransh-shrivastava/nest:backend-staging + owasp/nest:backend-staging ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/nest-staging-backend:latest - name: Prepare frontend public environment @@ -532,7 +536,7 @@ jobs: - name: Get backend image size id: backend-size run: | - IMAGE_NAME="rudransh-shrivastava/nest:backend-staging" + IMAGE_NAME="owasp/nest:backend-staging" RAW_SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}') DISPLAY_SIZE=$(numfmt --to=iec --suffix=B "$RAW_SIZE") echo "human_readable=$DISPLAY_SIZE" >> $GITHUB_OUTPUT @@ -542,9 +546,9 @@ jobs: with: cache-from: | type=gha - type=registry,ref=rudransh-shrivastava/nest:frontend-staging-cache + type=registry,ref=owasp/nest:frontend-staging-cache cache-to: | - type=registry,ref=rudransh-shrivastava/nest:frontend-staging-cache + type=registry,ref=owasp/nest:frontend-staging-cache context: frontend file: docker/frontend/Dockerfile load: true @@ -554,13 +558,13 @@ jobs: RELEASE_VERSION=${{ needs.set-release-version.outputs.release_version }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} tags: | - rudransh-shrivastava/nest:frontend-staging + owasp/nest:frontend-staging ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/nest-staging-frontend:latest - name: Get frontend image size id: frontend-size run: | - IMAGE_NAME="rudransh-shrivastava/nest:frontend-staging" + IMAGE_NAME="owasp/nest:frontend-staging" RAW_SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}') DISPLAY_SIZE=$(numfmt --to=iec --suffix=B "$RAW_SIZE") echo "human_readable=$DISPLAY_SIZE" >> $GITHUB_OUTPUT @@ -598,16 +602,16 @@ jobs: - name: Run Trivy security scan via Makefile run: | - make security-scan-backend-image BACKEND_IMAGE_NAME=rudransh-shrivastava/nest:backend-staging - make security-scan-frontend-image FRONTEND_IMAGE_NAME=rudransh-shrivastava/nest:frontend-staging + make security-scan-backend-image BACKEND_IMAGE_NAME=owasp/nest:backend-staging + make security-scan-frontend-image FRONTEND_IMAGE_NAME=owasp/nest:frontend-staging - name: Generate SBOM for backend image run: | - make sbom-backend-image BACKEND_IMAGE_NAME=rudransh-shrivastava/nest:backend-staging + make sbom-backend-image BACKEND_IMAGE_NAME=owasp/nest:backend-staging - name: Generate SBOM for frontend image run: | - make sbom-frontend-image FRONTEND_IMAGE_NAME=rudransh-shrivastava/nest:frontend-staging + make sbom-frontend-image FRONTEND_IMAGE_NAME=owasp/nest:frontend-staging - name: Upload SBOMs uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f @@ -624,11 +628,11 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - # if: | - # github.repository == 'OWASP/Nest' && - # github.ref == 'refs/heads/main' - #needs: - #- scan-staging-images + if: | + github.repository == 'OWASP/Nest' && + github.ref == 'refs/heads/main' + needs: + - scan-staging-images permissions: contents: read runs-on: ubuntu-latest @@ -691,14 +695,13 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - #if: | - #github.repository == 'OWASP/Nest' && - #github.ref == 'refs/heads/main' + if: | + github.repository == 'OWASP/Nest' && + github.ref == 'refs/heads/main' needs: - bootstrap-staging-nest - #- scan-staging-images + - scan-staging-images - set-release-version - permissions: contents: read runs-on: ubuntu-latest @@ -713,6 +716,7 @@ jobs: aws-region: ${{ vars.AWS_REGION }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} role-duration-seconds: 1200 + role-external-id: nest-staging-terraform role-session-name: GitHubActions-StagingPlan role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform @@ -743,7 +747,7 @@ jobs: ECS_USE_FARGATE_SPOT: true ENVIRONMENT: 'staging' FRONTEND_USE_FARGATE_SPOT: true - LAMBDA_FUNCTION_NAME: ${{ secrets.ZAPPA_LAMBDA_FUNCTION_NAME }} + LAMBDA_FUNCTION_NAME: 'nest-staging' PROJECT_NAME: 'nest' run: | umask 377 @@ -794,9 +798,9 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - #if: | - #github.repository == 'OWASP/Nest' && - #github.ref == 'refs/heads/main' + if: | + github.repository == 'OWASP/Nest' && + github.ref == 'refs/heads/main' needs: - plan-staging-nest permissions: @@ -813,7 +817,8 @@ jobs: aws-region: ${{ vars.AWS_REGION }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} role-duration-seconds: 1200 - role-session-name: GitHubActions-StagingPlan + role-external-id: nest-staging-terraform + role-session-name: GitHubActions-StagingDeploy role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform - name: Install Terraform @@ -875,7 +880,7 @@ jobs: - name: Install backend dependencies working-directory: backend - run: poetry sync --no-root --without dev --without test --without video + run: poetry sync --no-root --without test --without video - name: Prepare Zappa settings env: diff --git a/.gitignore b/.gitignore index 5506760f1f..2400fade1e 100644 --- a/.gitignore +++ b/.gitignore @@ -38,10 +38,10 @@ backend/fuzzing_results/ !*.tfvars.example **/.terraform/ backend-sbom-local.cdx.json -backend/*nest-backend-dev*.tar.gz -backend/*nest-backend-dev*.zip -backend/*nest-backend-staging*.tar.gz -backend/*nest-backend-staging*.zip +backend/*nest-dev*.tar.gz +backend/*nest-dev*.zip +backend/*nest-staging*.tar.gz +backend/*nest-staging*.zip backend/data/backup* backend/generated_videos/ backend/staticfiles diff --git a/backend/zappa_settings.template.json b/backend/zappa_settings.template.json index 858ee682f8..09b32a29a4 100644 --- a/backend/zappa_settings.template.json +++ b/backend/zappa_settings.template.json @@ -25,7 +25,7 @@ ], "manage_roles": true, "memory_size": 3008, - "project_name": "nest-backend", + "project_name": "nest", "regex_excludes": [ "/boto3/", "/boto3-", diff --git a/cspell/custom-dict.txt b/cspell/custom-dict.txt index 5d8808d9bf..0faa5c95af 100644 --- a/cspell/custom-dict.txt +++ b/cspell/custom-dict.txt @@ -169,6 +169,7 @@ pyyaml quasis rediss relativedelta +replicationgroup repositorycontributor requirepass rqworker @@ -184,6 +185,7 @@ skillstruck slackbot slideshare speakerdeck +subgrp superfences tfbackend tgz diff --git a/infrastructure/INFO.md b/infrastructure/INFO.md deleted file mode 100644 index 210c8abf39..0000000000 --- a/infrastructure/INFO.md +++ /dev/null @@ -1,43 +0,0 @@ -# Policies - -- Minimum policies required to use the terraform remote backend: -(Replace ${} variables appropriately) - -```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "S3StateManagement", - "Effect": "Allow", - "Action": [ - "s3:GetObject", - "s3:PutObject", - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::${TERRAFORM_STATE_BUCKET_NAME}", - "arn:aws:s3:::${TERRAFORM_STATE_BUCKET_NAME}/*" - ] - }, - { - "Sid": "DynamoDBStateManagement", - "Effect": "Allow", - "Action": [ - "dynamodb:GetItem", - "dynamodb:PutItem", - "dynamodb:DeleteItem" - ], - "Resource": "arn:aws:dynamodb:${AWS_REGION}:${AWS_ACCOUNT_ID}:table/${TERRAFORM_DYNAMODB_TABLE_NAME}" - }, - { - "Sid": "KMSStateManagement", - "Effect": "Allow", - "Action": [ - "kms:Decrypt" - ], - "Resource": "${AWS_BACKEND_KMS_KEY}" - } - ] -} -``` diff --git a/infrastructure/README.md b/infrastructure/README.md index af5a553d93..c99c2f02a8 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -9,18 +9,15 @@ Ensure you have the following setup/installed: - Setup Project: [CONTRIBUTING.md](https://github.com/OWASP/Nest/blob/main/CONTRIBUTING.md) - Terraform: [Terraform Documentation](https://developer.hashicorp.com/terraform/docs) - AWS CLI: [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) -- An AWS account with credential profiles configured locally: - - [nest-backend] - - [nest-bootstrap] - - [nest-staging] - this user must assume the role created by `nest-bootstrap` -Note: Refer to the respective README.md files for more information. -- Read `INFO.md` for information related to policies. +- An AWS account. +Note: Refer to the respective `README.md` files for more information. ## Setting up the infrastructure Follow these steps to set up the infrastructure: 1. **Setup Backend (one-time setup)**: + - Prerequisite: Create a `nest-backend` IAM user with the policies defined in `infrastructure/backend/README.md`. - Navigate to the backend directory: @@ -49,7 +46,56 @@ Follow these steps to set up the infrastructure: > [!NOTE] > It is recommended to not destroy the backend resources unless absolutely necessary. -2. **Setup Main Infrastructure (staging)**: +2. **Bootstrap IAM Role**: + - Prerequisite: Create a `nest-bootstrap` IAM user with the policies defined in `infrastructure/bootstrap/README.md`. + + - Navigate to the bootstrap directory: + + ```bash + cd infrastructure/bootstrap/ + ``` + + - Create a local terraform variables file: + + ```bash + touch terraform.tfvars + ``` + + - Copy the contents from the example file: + + ```bash + cat terraform.tfvars.example > terraform.tfvars + ``` + + - Create a local backend configuration file: + + ```bash + touch terraform.tfbackend + ``` + + - Copy the contents from the example file: + + ```bash + cat terraform.tfbackend.example > terraform.tfbackend + ``` + + > [!NOTE] + > Update the state bucket name in `terraform.tfbackend` with the name of the state bucket (bootstrap) created in the previous step. + + - Initialize Terraform if needed: + + ```bash + terraform init -backend-config=terraform.tfbackend + ``` + + - Apply the changes to create the bootstrap resources: + + ```bash + terraform apply + ``` + +3. **Setup Main Infrastructure (staging)**: + - Prerequisite: Create a `nest-staging` IAM user with the policies defined in `infrastructure/staging/README.md` - Navigate to the main infrastructure directory. If you are in `infrastructure/backend`, you can use: @@ -99,7 +145,7 @@ Follow these steps to set up the infrastructure: terraform apply ``` -3. **Populate Secrets** +4. **Populate Secrets** - Visit the AWS Console > Systems Manager > Parameter Store. - Populate all `DJANGO_*` secrets that have `to-be-set-in-aws-console` value. @@ -161,7 +207,7 @@ The Django backend deployment is managed by Zappa. This includes the IAM roles, - Update `terraform.tfvars` with the Lambda details: ```hcl - lambda_function_name = "nest-backend-staging" + lambda_function_name = "nest-staging" ``` - Apply the changes to create ALB routing: @@ -304,8 +350,8 @@ Migrate and load data into the new database. ```bash aws ecs update-service \ - --cluster owasp-nest-staging-frontend-cluster \ - --service owasp-nest-staging-frontend-service \ + --cluster nest-staging-frontend-cluster \ + --service nest-staging-frontend-service \ --force-new-deployment \ --region us-east-2 ``` diff --git a/infrastructure/backend/README.md b/infrastructure/backend/README.md index f0230acdf9..bd34c8757a 100644 --- a/infrastructure/backend/README.md +++ b/infrastructure/backend/README.md @@ -1,6 +1,6 @@ ## Inline Permissions Use the following inline permissions for the `nest-backend` IAM User -*Note*: replace ${AWS_ACCOUNT_ID} and ${AWS_BACKEND_KMS_KEY} with approriate values. +*Note*: replace ${AWS_ACCOUNT_ID} and ${AWS_BACKEND_KMS_KEY_ARN} with appropriate values. ```json { @@ -82,7 +82,7 @@ Use the following inline permissions for the `nest-backend` IAM User "kms:UpdateAlias", "kms:UpdateKeyDescription" ], - "Resource": "${AWS_BACKEND_KMS_KEY}" + "Resource": "${AWS_BACKEND_KMS_KEY_ARN}" } ] } diff --git a/infrastructure/backend/main.tf b/infrastructure/backend/main.tf index 77cd642f36..f9e7e66747 100644 --- a/infrastructure/backend/main.tf +++ b/infrastructure/backend/main.tf @@ -89,7 +89,7 @@ resource "aws_dynamodb_table" "state_lock" { type = "S" } lifecycle { - prevent_destroy = false + prevent_destroy = true } point_in_time_recovery { enabled = true @@ -107,7 +107,7 @@ resource "aws_s3_bucket" "logs" { # NOSONAR }) lifecycle { - prevent_destroy = false + prevent_destroy = true } } @@ -170,7 +170,7 @@ resource "aws_s3_bucket" "state" { # NOSONAR }) lifecycle { - prevent_destroy = false + prevent_destroy = true } } diff --git a/infrastructure/backend/providers.tf b/infrastructure/backend/providers.tf index 3f99c42c16..c9d7ccbdea 100644 --- a/infrastructure/backend/providers.tf +++ b/infrastructure/backend/providers.tf @@ -1,4 +1,3 @@ provider "aws" { - profile = "nest-backend" - region = var.aws_region + region = var.aws_region } diff --git a/infrastructure/bootstrap/README.md b/infrastructure/bootstrap/README.md index b1905bb108..c9f3abb16e 100644 --- a/infrastructure/bootstrap/README.md +++ b/infrastructure/bootstrap/README.md @@ -1,6 +1,14 @@ +## Users +`bootstrap` creates a role for each environment that IAM users can assume. +These users are listed in the `var.environments` variable. +Ensure your IAM Users follow the naming convention: +- nest-${var.environment} + +Example: `nest-staging`, `nest-bootstrap`, etc. + ## Inline Permissions Use the following inline permissions for the `nest-bootstrap` IAM User -*Note*: replace ${AWS_ACCOUNT_ID} and ${AWS_BACKEND_KMS_KEY} with approriate values. +*Note*: replace ${AWS_ACCOUNT_ID} and ${AWS_BACKEND_KMS_KEY_ARN} with appropriate values. ```json { @@ -56,11 +64,12 @@ Use the following inline permissions for the `nest-bootstrap` IAM User "iam:TagRole", "iam:UntagPolicy", "iam:UntagRole", + "iam:UpdateAssumeRolePolicy", "iam:UpdateRole" ], "Resource": [ - "arn:aws:iam::652192963764:role/nest-*-terraform", - "arn:aws:iam::652192963764:policy/nest-*-terraform" + "arn:aws:iam::${AWS_ACCOUNT_ID}:role/nest-*-terraform", + "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/nest-*-terraform" ] }, { @@ -69,7 +78,7 @@ Use the following inline permissions for the `nest-bootstrap` IAM User "Action": [ "kms:Decrypt" ], - "Resource": "${AWS_BACKEND_KMS_KEY}" + "Resource": "${AWS_BACKEND_KMS_KEY_ARN}" } ] } diff --git a/infrastructure/bootstrap/main.tf b/infrastructure/bootstrap/main.tf index 2d0dc76fc5..50b6124d11 100644 --- a/infrastructure/bootstrap/main.tf +++ b/infrastructure/bootstrap/main.tf @@ -20,14 +20,43 @@ locals { data "aws_caller_identity" "current" {} -data "aws_iam_policy_document" "terraform" { +data "aws_iam_policy_document" "part_one" { for_each = local.environments statement { + sid = "GlobalDiscovery" + effect = "Allow" + actions = [ + "acm:DescribeCertificate", + "application-autoscaling:DescribeScalableTargets", + "application-autoscaling:DescribeScalingPolicies", + "ec2:Describe*", + "ec2:DescribeFlowLogs", + "ec2:DescribeNetworkAcls", + "ecr:DescribeRepositories", + "ecs:DescribeTaskDefinition", + "elasticache:DescribeCacheClusters", + "elasticache:DescribeCacheSubnetGroups", + "elasticache:DescribeReplicationGroups", + "elasticloadbalancing:Describe*", + "kms:DescribeKey", + "lambda:ListFunctions", + "lambda:ListVersionsByFunction", + "logs:DescribeLogGroups", + "rds:DescribeDBInstances", + "rds:DescribeDBSubnetGroups", + "secretsmanager:DescribeSecret", + "ssm:DescribeParameters", + ] + resources = ["*"] + } + + statement { + sid = "ACMManagement" + effect = "Allow" actions = [ "acm:AddTagsToCertificate", "acm:DeleteCertificate", - "acm:DescribeCertificate", "acm:ListCertificates", "acm:ListTagsForCertificate", "acm:RemoveTagsFromCertificate", @@ -35,95 +64,7 @@ data "aws_iam_policy_document" "terraform" { "acm:ResendValidationEmail", "acm:UpdateCertificateOptions", ] - effect = "Allow" resources = ["arn:aws:acm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:certificate/*"] - sid = "ACMManagement" - } - - statement { - actions = [ - "dynamodb:DeleteItem", - "dynamodb:DescribeTable", - "dynamodb:GetItem", - "dynamodb:PutItem", - ] - effect = "Allow" - resources = [ - "arn:aws:dynamodb:*:${data.aws_caller_identity.current.account_id}:table/${var.project_name}-terraform-state-lock-${each.key}", - ] - sid = "DynamoDBStateLocking" - } - - statement { - sid = "EC2Management" - effect = "Allow" - actions = [ - "ec2:AllocateAddress", - "ec2:AssociateRouteTable", - "ec2:AttachInternetGateway", - "ec2:AuthorizeSecurityGroupEgress", - "ec2:AuthorizeSecurityGroupIngress", - "ec2:CreateFlowLogs", - "ec2:CreateInternetGateway", - "ec2:CreateNatGateway", - "ec2:CreateNetworkAcl", - "ec2:CreateNetworkAclEntry", - "ec2:CreateRoute", - "ec2:CreateRouteTable", - "ec2:CreateSecurityGroup", - "ec2:CreateSubnet", - "ec2:CreateTags", - "ec2:CreateVpc", - "ec2:CreateVpcEndpoint", - "ec2:DeleteFlowLogs", - "ec2:DeleteInternetGateway", - "ec2:DeleteNatGateway", - "ec2:DeleteNetworkAcl", - "ec2:DeleteNetworkAclEntry", - "ec2:DeleteRoute", - "ec2:DeleteRouteTable", - "ec2:DeleteSecurityGroup", - "ec2:DeleteSubnet", - "ec2:DeleteTags", - "ec2:DeleteVpc", - "ec2:DeleteVpcEndpoint", - "ec2:Describe*", - "ec2:DetachInternetGateway", - "ec2:DisassociateRouteTable", - "ec2:ModifySubnetAttribute", - "ec2:ModifyVpcAttribute", - "ec2:ModifyVpcEndpoint", - "ec2:ReleaseAddress", - "ec2:ReplaceNetworkAclAssociation", - "ec2:ReplaceNetworkAclEntry", - "ec2:ReplaceRoute", - "ec2:ReplaceRouteTableAssociation", - "ec2:RevokeSecurityGroupEgress", - "ec2:RevokeSecurityGroupIngress", - ] - resources = ["*"] - } - - statement { - actions = [ - "ecr:CreateRepository", - "ecr:DeleteLifecyclePolicy", - "ecr:DeleteRepository", - "ecr:DescribeRepositories", - "ecr:GetLifecyclePolicy", - "ecr:GetRepositoryPolicy", - "ecr:ListTagsForResource", - "ecr:PutImageScanningConfiguration", - "ecr:PutLifecyclePolicy", - "ecr:SetRepositoryPolicy", - "ecr:TagResource", - "ecr:UntagResource", - ] - effect = "Allow" - resources = [ - "arn:aws:ecr:*:${data.aws_caller_identity.current.account_id}:repository/${var.project_name}-${each.key}-*", - ] - sid = "ECRManagement" } statement { @@ -132,8 +73,6 @@ data "aws_iam_policy_document" "terraform" { actions = [ "application-autoscaling:DeleteScalingPolicy", "application-autoscaling:DeregisterScalableTarget", - "application-autoscaling:DescribeScalableTargets", - "application-autoscaling:DescribeScalingPolicies", "application-autoscaling:ListTagsForResource", "application-autoscaling:PutScalingPolicy", "application-autoscaling:RegisterScalableTarget", @@ -147,20 +86,30 @@ data "aws_iam_policy_document" "terraform" { sid = "CloudWatchEventsManagement" effect = "Allow" actions = [ - "events:DeleteRule", - "events:DescribeRule", - "events:ListTargetsByRule", - "events:PutRule", - "events:PutTargets", - "events:RemoveTargets", - "events:TagResource", - "events:UntagResource", + "events:ListRuleNamesByTarget", + ] + resources = ["*"] + } + + statement { + sid = "CloudWatchLogsManagement" + effect = "Allow" + actions = [ + "logs:CreateLogGroup", + "logs:DeleteLogGroup", + "logs:ListTagsForResource", + "logs:ListTagsLogGroup", + "logs:PutRetentionPolicy", + "logs:TagLogGroup", + "logs:TagResource", + "logs:UntagLogGroup", + "logs:UntagResource", ] - resources = ["arn:aws:events:${var.aws_region}:${data.aws_caller_identity.current.account_id}:rule/${var.project_name}-${each.key}-*"] + resources = ["arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*"] } statement { - sid = "DatabaseManagement" + sid = "DatabaseAndCacheManagement" effect = "Allow" actions = [ "elasticache:AddTagsToResource", @@ -168,9 +117,6 @@ data "aws_iam_policy_document" "terraform" { "elasticache:CreateReplicationGroup", "elasticache:DeleteCacheSubnetGroup", "elasticache:DeleteReplicationGroup", - "elasticache:DescribeCacheClusters", - "elasticache:DescribeCacheSubnetGroups", - "elasticache:DescribeReplicationGroups", "elasticache:ListTagsForResource", "elasticache:ModifyCacheSubnetGroup", "elasticache:ModifyReplicationGroup", @@ -180,99 +126,123 @@ data "aws_iam_policy_document" "terraform" { "rds:CreateDBSubnetGroup", "rds:DeleteDBInstance", "rds:DeleteDBSubnetGroup", - "rds:DescribeDBInstances", - "rds:DescribeDBSubnetGroups", "rds:ListTagsForResource", "rds:ModifyDBInstance", "rds:ModifyDBSubnetGroup", "rds:RemoveTagsFromResource", ] - resources = ["*"] + resources = [ + "arn:aws:elasticache:${var.aws_region}:${data.aws_caller_identity.current.account_id}:cluster:${var.project_name}-${each.key}-*", + "arn:aws:elasticache:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parametergroup:*", + "arn:aws:elasticache:${var.aws_region}:${data.aws_caller_identity.current.account_id}:replicationgroup:${var.project_name}-${each.key}-*", + "arn:aws:elasticache:${var.aws_region}:${data.aws_caller_identity.current.account_id}:subnetgroup:${var.project_name}-${each.key}-*", + "arn:aws:rds:${var.aws_region}:${data.aws_caller_identity.current.account_id}:db:${var.project_name}-${each.key}-*", + "arn:aws:rds:${var.aws_region}:${data.aws_caller_identity.current.account_id}:subgrp:${var.project_name}-${each.key}-*" + ] + } + + statement { + sid = "DynamoDBStateLocking" + effect = "Allow" + actions = [ + "dynamodb:DeleteItem", + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:PutItem", + ] + resources = [ + "arn:aws:dynamodb:*:${data.aws_caller_identity.current.account_id}:table/${var.project_name}-terraform-state-lock-${each.key}", + ] } statement { - sid = "EC2ResourceSpecific" + sid = "EC2Management" effect = "Allow" actions = [ + "ec2:AllocateAddress", + "ec2:AssociateRouteTable", "ec2:AttachInternetGateway", "ec2:AuthorizeSecurityGroupEgress", "ec2:AuthorizeSecurityGroupIngress", "ec2:CreateFlowLogs", + "ec2:CreateInternetGateway", + "ec2:CreateNatGateway", "ec2:CreateNetworkAcl", "ec2:CreateNetworkAclEntry", + "ec2:CreateRoute", + "ec2:CreateRouteTable", + "ec2:CreateSecurityGroup", + "ec2:CreateSubnet", "ec2:CreateTags", + "ec2:CreateVpc", + "ec2:CreateVpcEndpoint", "ec2:DeleteFlowLogs", + "ec2:DeleteInternetGateway", + "ec2:DeleteNatGateway", "ec2:DeleteNetworkAcl", "ec2:DeleteNetworkAclEntry", + "ec2:DeleteNetworkInterface", + "ec2:DeleteRoute", + "ec2:DeleteRouteTable", "ec2:DeleteSecurityGroup", + "ec2:DeleteSubnet", "ec2:DeleteTags", "ec2:DeleteVpc", - "ec2:DescribeFlowLogs", - "ec2:DescribeNetworkAcls", + "ec2:DeleteVpcEndpoints", "ec2:DetachInternetGateway", + "ec2:DisassociateAddress", + "ec2:DisassociateRouteTable", + "ec2:ModifySubnetAttribute", + "ec2:ModifyVpcAttribute", + "ec2:ModifyVpcEndpoint", + "ec2:ReleaseAddress", "ec2:ReplaceNetworkAclAssociation", "ec2:ReplaceNetworkAclEntry", + "ec2:ReplaceRoute", + "ec2:ReplaceRouteTableAssociation", "ec2:RevokeSecurityGroupEgress", "ec2:RevokeSecurityGroupIngress", ] - resources = ["arn:aws:ec2:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*"] + resources = ["*"] } statement { - sid = "IAMPassRole" + sid = "ECRAuth" effect = "Allow" actions = [ - "iam:PassRole", + "ecr:BatchCheckLayerAvailability", + "ecr:BatchGetImage", + "ecr:CompleteLayerUpload", + "ecr:GetAuthorizationToken", + "ecr:GetDownloadUrlForLayer", + "ecr:GetRepositoryPolicy", + "ecr:InitiateLayerUpload", + "ecr:ListImages", + "ecr:PutImage", + "ecr:UploadLayerPart", ] - resources = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.project_name}-${each.key}-*"] - condition { - test = "StringEquals" - variable = "iam:PassedToService" - values = [ - "ecs-tasks.amazonaws.com", - "lambda.amazonaws.com", - "rds.amazonaws.com", - ] - } + resources = ["*"] } statement { - sid = "LambdaManagement" + sid = "ECRManagement" effect = "Allow" actions = [ - "lambda:AddPermission", - "lambda:CreateAlias", - "lambda:DeleteAlias", - "lambda:GetAlias", - "lambda:GetFunction", - "lambda:GetPolicy", - "lambda:ListTags", - "lambda:ListVersionsByFunction", - "lambda:RemovePermission", - "lambda:TagResource", - "lambda:UntagResource", - "lambda:UpdateAlias", + "ecr:CreateRepository", + "ecr:DeleteLifecyclePolicy", + "ecr:DeleteRepository", + "ecr:GetLifecyclePolicy", + "ecr:GetRepositoryPolicy", + "ecr:ListTagsForResource", + "ecr:PutImageScanningConfiguration", + "ecr:PutLifecyclePolicy", + "ecr:SetRepositoryPolicy", + "ecr:TagResource", + "ecr:UntagResource", ] - resources = ["arn:aws:lambda:${var.aws_region}:${data.aws_caller_identity.current.account_id}:function:${var.project_name}-${each.key}-*"] - } - - statement { - sid = "SecretsManagerManagement" - effect = "Allow" - actions = [ - "secretsmanager:CreateSecret", - "secretsmanager:DeleteSecret", - "secretsmanager:DescribeSecret", - "secretsmanager:GetResourcePolicy", - "secretsmanager:GetSecretValue", - "secretsmanager:PutSecretValue", - "secretsmanager:RestoreSecret", - "secretsmanager:RotateSecret", - "secretsmanager:TagResource", - "secretsmanager:UntagResource", - "secretsmanager:UpdateSecret", + resources = [ + "arn:aws:ecr:*:${data.aws_caller_identity.current.account_id}:repository/${var.project_name}-${each.key}-*", ] - resources = ["arn:aws:secretsmanager:${var.aws_region}:${data.aws_caller_identity.current.account_id}:secret:${var.project_name}-${each.key}-*"] } statement { @@ -296,7 +266,6 @@ data "aws_iam_policy_document" "terraform" { effect = "Allow" actions = [ "ecs:DeregisterTaskDefinition", - "ecs:DescribeTaskDefinition", "ecs:ListClusters", "ecs:ListTaskDefinitions", "ecs:RegisterTaskDefinition", @@ -306,26 +275,26 @@ data "aws_iam_policy_document" "terraform" { } statement { - sid = "ELBManagement" + sid = "ECSOrchestration" effect = "Allow" actions = [ - "elasticloadbalancing:AddTags", - "elasticloadbalancing:CreateListener", - "elasticloadbalancing:CreateLoadBalancer", - "elasticloadbalancing:CreateTargetGroup", - "elasticloadbalancing:DeleteListener", - "elasticloadbalancing:DeleteLoadBalancer", - "elasticloadbalancing:DeleteTargetGroup", - "elasticloadbalancing:Describe*", - "elasticloadbalancing:ModifyListener", - "elasticloadbalancing:ModifyLoadBalancerAttributes", - "elasticloadbalancing:ModifyTargetGroup", - "elasticloadbalancing:ModifyTargetGroupAttributes", - "elasticloadbalancing:RemoveTags", - "elasticloadbalancing:SetSecurityGroups", + "ecs:RunTask", + "ecs:DescribeTasks", + "ecs:StopTask", + "ecs:DescribeServices", + "ecs:UpdateService" + ] + resources = [ + "arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:task-definition/${var.project_name}-*:*", + "arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:service/${var.project_name}-*/*", + "arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:cluster/${var.project_name}-*", + "arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:task/${var.project_name}-*/*" ] - resources = ["*"] } +} + +data "aws_iam_policy_document" "part_two" { + for_each = local.environments statement { sid = "ECSServiceManagement" @@ -333,8 +302,8 @@ data "aws_iam_policy_document" "terraform" { actions = [ "ecs:CreateService", "ecs:DeleteService", + "ecs:DescribeServices", "ecs:UpdateService", - "ecs:DescribeServices" ] resources = ["arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:service/${var.project_name}-${each.key}-*/*"] } @@ -350,7 +319,34 @@ data "aws_iam_policy_document" "terraform" { } statement { - sid = "EventBridgeManagement" + sid = "ELBManagement" + effect = "Allow" + actions = [ + "elasticloadbalancing:AddTags", + "elasticloadbalancing:CreateListener", + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:CreateRule", + "elasticloadbalancing:CreateTargetGroup", + "elasticloadbalancing:DeleteListener", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:DeleteRule", + "elasticloadbalancing:DeleteTargetGroup", + "elasticloadbalancing:ModifyListener", + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:RemoveTags", + "elasticloadbalancing:SetRulePriorities", + "elasticloadbalancing:SetSecurityGroups", + ] + resources = ["*"] + } + + statement { + sid = "EventBridgeManagement" + effect = "Allow" actions = [ "events:DeleteRule", "events:DescribeRule", @@ -362,14 +358,14 @@ data "aws_iam_policy_document" "terraform" { "events:TagResource", "events:UntagResource", ] - effect = "Allow" resources = [ "arn:aws:events:*:${data.aws_caller_identity.current.account_id}:rule/${var.project_name}-${each.key}-*", ] } statement { - sid = "IAMManagement" + sid = "IAMManagement" + effect = "Allow" actions = [ "iam:AttachRolePolicy", "iam:CreatePolicy", @@ -386,32 +382,52 @@ data "aws_iam_policy_document" "terraform" { "iam:ListInstanceProfilesForRole", "iam:ListPolicyVersions", "iam:ListRolePolicies", - "iam:PassRole", + "iam:PutRolePolicy", "iam:TagPolicy", "iam:TagRole", "iam:UntagPolicy", "iam:UntagRole", - "iam:UpdateRole", "iam:UpdateAssumeRolePolicy", + "iam:UpdateRole", ] - effect = "Allow" resources = [ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/${var.project_name}-${each.key}-*", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/${var.project_name}-*-${each.key}-*", "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.project_name}-${each.key}-*", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.project_name}-*-${each.key}-*", ] } statement { - sid = "KMSCreateAll" + sid = "IAMPassRole" + effect = "Allow" + actions = [ + "iam:PassRole", + ] + resources = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.project_name}-${each.key}-*", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.project_name}-*-${each.key}-*", + ] + condition { + test = "StringEquals" + variable = "iam:PassedToService" + values = [ + "ecs-tasks.amazonaws.com", + "events.amazonaws.com", + "lambda.amazonaws.com", + "rds.amazonaws.com", + "vpc-flow-logs.amazonaws.com" + ] + } + } + + statement { + sid = "KMSManagement" + effect = "Allow" actions = [ - "kms:CreateAlias", "kms:CreateKey", - "kms:Decrypt", - "kms:DeleteAlias", - "kms:DescribeKey", "kms:DisableKeyRotation", "kms:EnableKeyRotation", - "kms:GenerateDataKey", "kms:GetKeyPolicy", "kms:GetKeyRotationStatus", "kms:ListAliases", @@ -419,37 +435,89 @@ data "aws_iam_policy_document" "terraform" { "kms:PutKeyPolicy", "kms:ScheduleKeyDeletion", "kms:TagResource", - "kms:UntagResource", - "kms:UpdateAlias", - "kms:UpdateKeyDescription", + "kms:UntagResource" ] - effect = "Allow" resources = ["*"] } statement { + sid = "KMSKeyUsageAndPolicy" + effect = "Allow" actions = [ - "logs:CreateLogGroup", - "logs:DeleteLogGroup", - "logs:DescribeLogGroups", - "logs:ListTagsForResource", - "logs:ListTagsLogGroup", - "logs:PutRetentionPolicy", - "logs:TagLogGroup", - "logs:TagResource", - "logs:UntagLogGroup", - "logs:UntagResource", + "kms:Decrypt", + "kms:GenerateDataKey", + "kms:UpdateKeyDescription", ] - effect = "Allow" resources = ["*"] - sid = "CloudWatchLogsManagement" + + condition { + test = "ForAnyValue:StringEquals" + variable = "kms:ResourceAliases" + values = [ + "alias/${var.project_name}-backend", + "alias/${var.project_name}-${each.key}" + ] + } + } + + statement { + sid = "KMSAliasManagement" + effect = "Allow" + actions = [ + "kms:CreateAlias", + "kms:DeleteAlias", + "kms:UpdateAlias" + ] + resources = [ + "arn:aws:kms:${var.aws_region}:${data.aws_caller_identity.current.account_id}:alias/${var.project_name}-*", + "arn:aws:kms:${var.aws_region}:${data.aws_caller_identity.current.account_id}:key/*" + ] + } + + statement { + sid = "LambdaManagement" + effect = "Allow" + actions = [ + "lambda:AddPermission", + "lambda:CreateAlias", + "lambda:CreateFunction", + "lambda:DeleteAlias", + "lambda:DeleteFunction", + "lambda:DeleteFunctionConcurrency", + "lambda:GetAlias", + "lambda:GetFunction", + "lambda:GetFunctionCodeSigningConfig", + "lambda:GetFunctionConcurrency", + "lambda:GetFunctionConfiguration", + "lambda:GetFunctionUrlConfig", + "lambda:GetPolicy", + "lambda:InvokeFunction", + "lambda:ListFunctionUrlConfigs", + "lambda:ListTags", + "lambda:ListVersionsByFunction", + "lambda:PublishVersion", + "lambda:PutFunctionConcurrency", + "lambda:RemovePermission", + "lambda:TagResource", + "lambda:UntagResource", + "lambda:UpdateAlias", + "lambda:UpdateFunctionCode", + "lambda:UpdateFunctionConfiguration", + ] + resources = [ + "arn:aws:lambda:${var.aws_region}:${data.aws_caller_identity.current.account_id}:function:${var.project_name}-${each.key}", + "arn:aws:lambda:${var.aws_region}:${data.aws_caller_identity.current.account_id}:function:${var.project_name}-${each.key}:*", + ] } statement { - sid = "S3Management" + sid = "S3Management" + effect = "Allow" actions = [ "s3:CreateBucket", "s3:DeleteBucket", + "s3:DeleteBucketPolicy", + "s3:DeleteObject", "s3:GetAccelerateConfiguration", "s3:GetBucketAcl", "s3:GetBucketCors", @@ -458,7 +526,6 @@ data "aws_iam_policy_document" "terraform" { "s3:GetBucketOwnershipControls", "s3:GetBucketPolicy", "s3:GetBucketPublicAccessBlock", - "s3:GetBucketReplication", "s3:GetBucketRequestPayment", "s3:GetBucketTagging", "s3:GetBucketVersioning", @@ -468,6 +535,8 @@ data "aws_iam_policy_document" "terraform" { "s3:GetObject", "s3:GetReplicationConfiguration", "s3:ListBucket", + "s3:ListBucketVersions", + "s3:PutBucketAcl", "s3:PutBucketLogging", "s3:PutBucketObjectLockConfiguration", "s3:PutBucketOwnershipControls", @@ -478,9 +547,7 @@ data "aws_iam_policy_document" "terraform" { "s3:PutEncryptionConfiguration", "s3:PutLifecycleConfiguration", "s3:PutObject", - "s3:PutBucketAcl" ] - effect = "Allow" resources = [ "arn:aws:s3:::${var.project_name}-*", "arn:aws:s3:::${var.project_name}-*/*", @@ -488,21 +555,36 @@ data "aws_iam_policy_document" "terraform" { } statement { - sid = "SSMManagement" + sid = "SecretsManagerManagement" + effect = "Allow" + actions = [ + "secretsmanager:CreateSecret", + "secretsmanager:DeleteSecret", + "secretsmanager:GetResourcePolicy", + "secretsmanager:GetSecretValue", + "secretsmanager:PutSecretValue", + "secretsmanager:RestoreSecret", + "secretsmanager:RotateSecret", + "secretsmanager:TagResource", + "secretsmanager:UntagResource", + "secretsmanager:UpdateSecret", + ] + resources = ["arn:aws:secretsmanager:${var.aws_region}:${data.aws_caller_identity.current.account_id}:secret:${var.project_name}-${each.key}-*"] + } + + statement { + sid = "SSMManagement" + effect = "Allow" actions = [ "ssm:AddTagsToResource", "ssm:DeleteParameter", - "ssm:DescribeParameters", "ssm:GetParameter", "ssm:GetParameters", "ssm:ListTagsForResource", "ssm:PutParameter", "ssm:RemoveTagsFromResource", ] - effect = "Allow" - resources = [ - "arn:aws:ssm:*:${data.aws_caller_identity.current.account_id}:*", - ] + resources = ["arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/${var.project_name}/${each.key}/*"] } } @@ -519,12 +601,7 @@ resource "aws_iam_role" "terraform" { Version = "2012-10-17" Statement = [ { - Action = "sts:AssumeRole" - Condition = { - StringEquals = { - "sts:ExternalId" = "${var.project_name}-${each.key}-terraform" - } - } + Action = ["sts:AssumeRole", "sts:TagSession"] Effect = "Allow" Principal = { AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/${var.project_name}-${each.key}" @@ -534,10 +611,26 @@ resource "aws_iam_role" "terraform" { }) } -resource "aws_iam_role_policy" "terraform" { +resource "aws_iam_policy" "part_one" { for_each = local.environments + name = "${var.project_name}-${each.key}-part-one-terraform" + policy = data.aws_iam_policy_document.part_one[each.key].json +} + +resource "aws_iam_policy" "part_two" { + for_each = local.environments + name = "${var.project_name}-${each.key}-part-two-terraform" + policy = data.aws_iam_policy_document.part_two[each.key].json +} + +resource "aws_iam_role_policy_attachment" "attach_part_one" { + for_each = local.environments + role = aws_iam_role.terraform[each.key].name + policy_arn = aws_iam_policy.part_one[each.key].arn +} - name = "${var.project_name}-${each.key}-terraform-inline" - role = aws_iam_role.terraform[each.key].id - policy = data.aws_iam_policy_document.terraform[each.key].json +resource "aws_iam_role_policy_attachment" "attach_part_two" { + for_each = local.environments + role = aws_iam_role.terraform[each.key].name + policy_arn = aws_iam_policy.part_two[each.key].arn } diff --git a/infrastructure/bootstrap/providers.tf b/infrastructure/bootstrap/providers.tf index 7c185d6c8f..c9d7ccbdea 100644 --- a/infrastructure/bootstrap/providers.tf +++ b/infrastructure/bootstrap/providers.tf @@ -1,4 +1,3 @@ provider "aws" { - profile = "nest-bootstrap" - region = var.aws_region + region = var.aws_region } diff --git a/infrastructure/bootstrap/terraform.tfbackend.example b/infrastructure/bootstrap/terraform.tfbackend.example index b50462c5a8..e161a953fc 100644 --- a/infrastructure/bootstrap/terraform.tfbackend.example +++ b/infrastructure/bootstrap/terraform.tfbackend.example @@ -1,4 +1,3 @@ bucket = "${STATE_BUCKET_NAME}" dynamodb_table = "nest-terraform-state-lock-bootstrap" -profile = "nest-bootstrap" region = "us-east-2" diff --git a/infrastructure/staging/README.md b/infrastructure/staging/README.md new file mode 100644 index 0000000000..69fca0d875 --- /dev/null +++ b/infrastructure/staging/README.md @@ -0,0 +1,50 @@ +## Inline Permissions +Use the following inline permissions for the `nest-staging` IAM User +*Note*: replace ${...} with appropriate values. + +```json +{ + "Version": "2012-10-17", + "Statement": [ { + "Sid": "S3StateManagement", + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::${TERRAFORM_STATE_BUCKET_NAME}", + "arn:aws:s3:::${TERRAFORM_STATE_BUCKET_NAME}/*" + ] + }, + { + "Sid": "DynamoDBStateManagement", + "Effect": "Allow", + "Action": [ + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:DeleteItem" + ], + "Resource": "arn:aws:dynamodb:${AWS_REGION}:${AWS_ACCOUNT_ID}:table/${TERRAFORM_DYNAMODB_TABLE_NAME}" + }, + { + "Sid": "KMSStateManagement", + "Effect": "Allow", + "Action": [ + "kms:Decrypt" + ], + "Resource": "${AWS_BACKEND_KMS_KEY_ARN}" + }, + { + "Sid": "STSStateManagement", + "Effect": "Allow", + "Action": [ + "sts:AssumeRole", + "sts:TagSession" + ], + "Resource": "arn:aws:iam::${AWS_ACCOUNT_ID}:role/nest-staging-terraform" + } + ] +} +``` diff --git a/infrastructure/staging/providers.tf b/infrastructure/staging/providers.tf index 1a5fa9c8e9..c9d7ccbdea 100644 --- a/infrastructure/staging/providers.tf +++ b/infrastructure/staging/providers.tf @@ -1,4 +1,3 @@ provider "aws" { - profile = "nest-staging" - region = var.aws_region + region = var.aws_region } diff --git a/infrastructure/staging/terraform.tfbackend.example b/infrastructure/staging/terraform.tfbackend.example index 8e8699bc8e..060e98590f 100644 --- a/infrastructure/staging/terraform.tfbackend.example +++ b/infrastructure/staging/terraform.tfbackend.example @@ -1,4 +1,3 @@ bucket = "${STATE_BUCKET_NAME}" dynamodb_table = "nest-terraform-state-lock-staging" -profile = "nest-staging" region = "us-east-2" From 5a3e6539eaf89cecc65c35dabc40e47075926632 Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Wed, 18 Feb 2026 21:54:52 +0530 Subject: [PATCH 06/16] bot comments --- .github/workflows/run-ci-cd.yaml | 8 +- infrastructure/backend/README.md | 12 ++- infrastructure/backend/main.tf | 3 + infrastructure/bootstrap/main.tf | 102 +++++++++--------- .../bootstrap/terraform.tfvars.example | 1 + infrastructure/bootstrap/variables.tf | 5 + 6 files changed, 78 insertions(+), 53 deletions(-) diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index 1551d448a0..637bb2b5e5 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -477,7 +477,7 @@ jobs: aws-region: ${{ vars.AWS_REGION }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} role-duration-seconds: 1200 - role-external-id: nest-staging-terraform + role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }} role-session-name: GitHubActions-BuildStagingImages role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform @@ -669,11 +669,13 @@ jobs: env: AWS_REGION: ${{ vars.AWS_REGION }} PROJECT_NAME: 'nest' + AWS_ROLE_EXTERNAL_ID: ${{ secrets.AWS_ROLE_EXTERNAL_ID }} run: | umask 377 cat > infrastructure/bootstrap/terraform.tfvars <<-EOF aws_region="$AWS_REGION" project_name="$PROJECT_NAME" + aws_role_external_id="$AWS_ROLE_EXTERNAL_ID" EOF - name: Terraform Init @@ -716,7 +718,7 @@ jobs: aws-region: ${{ vars.AWS_REGION }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} role-duration-seconds: 1200 - role-external-id: nest-staging-terraform + role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }} role-session-name: GitHubActions-StagingPlan role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform @@ -817,7 +819,7 @@ jobs: aws-region: ${{ vars.AWS_REGION }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} role-duration-seconds: 1200 - role-external-id: nest-staging-terraform + role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }} role-session-name: GitHubActions-StagingDeploy role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform diff --git a/infrastructure/backend/README.md b/infrastructure/backend/README.md index bd34c8757a..66e068a0b8 100644 --- a/infrastructure/backend/README.md +++ b/infrastructure/backend/README.md @@ -60,20 +60,28 @@ Use the following inline permissions for the `nest-backend` IAM User ], "Resource": "arn:aws:dynamodb:*:${AWS_ACCOUNT_ID}:table/nest-terraform-state-lock-*" }, + { + "Sid": "KMSCRUDManagement", + "Effect": "Allow", + "Action": [ + "kms:CreateKey", + "kms:ListAliases", + "kms:ListKeys" + ], + "Resource": "*" + }, { "Sid": "KMSManagement", "Effect": "Allow", "Action": [ "kms:CreateAlias", "kms:CreateGrant", - "kms:CreateKey", "kms:DeleteAlias", "kms:DescribeKey", "kms:DisableKeyRotation", "kms:EnableKeyRotation", "kms:GetKeyPolicy", "kms:GetKeyRotationStatus", - "kms:ListAliases", "kms:ListResourceTags", "kms:PutKeyPolicy", "kms:ScheduleKeyDeletion", diff --git a/infrastructure/backend/main.tf b/infrastructure/backend/main.tf index f9e7e66747..c175d9a187 100644 --- a/infrastructure/backend/main.tf +++ b/infrastructure/backend/main.tf @@ -121,6 +121,9 @@ resource "aws_s3_bucket_lifecycle_configuration" "logs" { abort_incomplete_multipart_upload { days_after_initiation = var.abort_incomplete_multipart_upload_days } + noncurrent_version_expiration { + noncurrent_days = var.noncurrent_version_expiration_days + } expiration { days = var.expire_log_days } diff --git a/infrastructure/bootstrap/main.tf b/infrastructure/bootstrap/main.tf index 50b6124d11..392f5b5f47 100644 --- a/infrastructure/bootstrap/main.tf +++ b/infrastructure/bootstrap/main.tf @@ -39,6 +39,7 @@ data "aws_iam_policy_document" "part_one" { "elasticache:DescribeCacheSubnetGroups", "elasticache:DescribeReplicationGroups", "elasticloadbalancing:Describe*", + "events:ListRuleNamesByTarget", "kms:DescribeKey", "lambda:ListFunctions", "lambda:ListVersionsByFunction", @@ -82,15 +83,6 @@ data "aws_iam_policy_document" "part_one" { resources = ["*"] } - statement { - sid = "CloudWatchEventsManagement" - effect = "Allow" - actions = [ - "events:ListRuleNamesByTarget", - ] - resources = ["*"] - } - statement { sid = "CloudWatchLogsManagement" effect = "Allow" @@ -109,7 +101,21 @@ data "aws_iam_policy_document" "part_one" { } statement { - sid = "DatabaseAndCacheManagement" + sid = "DynamoDBStateLocking" + effect = "Allow" + actions = [ + "dynamodb:DeleteItem", + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:PutItem", + ] + resources = [ + "arn:aws:dynamodb:*:${data.aws_caller_identity.current.account_id}:table/${var.project_name}-terraform-state-lock-${each.key}", + ] + } + + statement { + sid = "ElastiCacheManagement" effect = "Allow" actions = [ "elasticache:AddTagsToResource", @@ -121,37 +127,12 @@ data "aws_iam_policy_document" "part_one" { "elasticache:ModifyCacheSubnetGroup", "elasticache:ModifyReplicationGroup", "elasticache:RemoveTagsFromResource", - "rds:AddTagsToResource", - "rds:CreateDBInstance", - "rds:CreateDBSubnetGroup", - "rds:DeleteDBInstance", - "rds:DeleteDBSubnetGroup", - "rds:ListTagsForResource", - "rds:ModifyDBInstance", - "rds:ModifyDBSubnetGroup", - "rds:RemoveTagsFromResource", ] resources = [ "arn:aws:elasticache:${var.aws_region}:${data.aws_caller_identity.current.account_id}:cluster:${var.project_name}-${each.key}-*", "arn:aws:elasticache:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parametergroup:*", "arn:aws:elasticache:${var.aws_region}:${data.aws_caller_identity.current.account_id}:replicationgroup:${var.project_name}-${each.key}-*", "arn:aws:elasticache:${var.aws_region}:${data.aws_caller_identity.current.account_id}:subnetgroup:${var.project_name}-${each.key}-*", - "arn:aws:rds:${var.aws_region}:${data.aws_caller_identity.current.account_id}:db:${var.project_name}-${each.key}-*", - "arn:aws:rds:${var.aws_region}:${data.aws_caller_identity.current.account_id}:subgrp:${var.project_name}-${each.key}-*" - ] - } - - statement { - sid = "DynamoDBStateLocking" - effect = "Allow" - actions = [ - "dynamodb:DeleteItem", - "dynamodb:DescribeTable", - "dynamodb:GetItem", - "dynamodb:PutItem", - ] - resources = [ - "arn:aws:dynamodb:*:${data.aws_caller_identity.current.account_id}:table/${var.project_name}-terraform-state-lock-${each.key}", ] } @@ -210,16 +191,7 @@ data "aws_iam_policy_document" "part_one" { sid = "ECRAuth" effect = "Allow" actions = [ - "ecr:BatchCheckLayerAvailability", - "ecr:BatchGetImage", - "ecr:CompleteLayerUpload", "ecr:GetAuthorizationToken", - "ecr:GetDownloadUrlForLayer", - "ecr:GetRepositoryPolicy", - "ecr:InitiateLayerUpload", - "ecr:ListImages", - "ecr:PutImage", - "ecr:UploadLayerPart", ] resources = ["*"] } @@ -228,17 +200,26 @@ data "aws_iam_policy_document" "part_one" { sid = "ECRManagement" effect = "Allow" actions = [ + "ecr:BatchCheckLayerAvailability", + "ecr:BatchGetImage", + "ecr:CompleteLayerUpload", "ecr:CreateRepository", "ecr:DeleteLifecyclePolicy", "ecr:DeleteRepository", + "ecr:GetDownloadUrlForLayer", "ecr:GetLifecyclePolicy", "ecr:GetRepositoryPolicy", + "ecr:GetRepositoryPolicy", + "ecr:InitiateLayerUpload", + "ecr:ListImages", "ecr:ListTagsForResource", + "ecr:PutImage", "ecr:PutImageScanningConfiguration", "ecr:PutLifecyclePolicy", "ecr:SetRepositoryPolicy", "ecr:TagResource", "ecr:UntagResource", + "ecr:UploadLayerPart", ] resources = [ "arn:aws:ecr:*:${data.aws_caller_identity.current.account_id}:repository/${var.project_name}-${each.key}-*", @@ -291,10 +272,6 @@ data "aws_iam_policy_document" "part_one" { "arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:task/${var.project_name}-*/*" ] } -} - -data "aws_iam_policy_document" "part_two" { - for_each = local.environments statement { sid = "ECSServiceManagement" @@ -317,6 +294,10 @@ data "aws_iam_policy_document" "part_two" { ] resources = ["arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:task-definition/${var.project_name}-${each.key}-*:*"] } +} + +data "aws_iam_policy_document" "part_two" { + for_each = local.environments statement { sid = "ELBManagement" @@ -510,6 +491,26 @@ data "aws_iam_policy_document" "part_two" { ] } + statement { + sid = "RDSManagement" + effect = "Allow" + actions = [ + "rds:AddTagsToResource", + "rds:CreateDBInstance", + "rds:CreateDBSubnetGroup", + "rds:DeleteDBInstance", + "rds:DeleteDBSubnetGroup", + "rds:ListTagsForResource", + "rds:ModifyDBInstance", + "rds:ModifyDBSubnetGroup", + "rds:RemoveTagsFromResource", + ] + resources = [ + "arn:aws:rds:${var.aws_region}:${data.aws_caller_identity.current.account_id}:db:${var.project_name}-${each.key}-*", + "arn:aws:rds:${var.aws_region}:${data.aws_caller_identity.current.account_id}:subgrp:${var.project_name}-${each.key}-*" + ] + } + statement { sid = "S3Management" effect = "Allow" @@ -602,6 +603,11 @@ resource "aws_iam_role" "terraform" { Statement = [ { Action = ["sts:AssumeRole", "sts:TagSession"] + Condition = { + StringEquals = { + "sts:ExternalId" = var.aws_role_external_id + } + } Effect = "Allow" Principal = { AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/${var.project_name}-${each.key}" diff --git a/infrastructure/bootstrap/terraform.tfvars.example b/infrastructure/bootstrap/terraform.tfvars.example index 6eda53c7a6..5e9fafb7d0 100644 --- a/infrastructure/bootstrap/terraform.tfvars.example +++ b/infrastructure/bootstrap/terraform.tfvars.example @@ -1,2 +1,3 @@ aws_region = "us-east-2" +aws_role_external_id = ${AWS_ROLE_EXTERNAL_ID} project_name = "nest" diff --git a/infrastructure/bootstrap/variables.tf b/infrastructure/bootstrap/variables.tf index 471f7eb514..92a107259c 100644 --- a/infrastructure/bootstrap/variables.tf +++ b/infrastructure/bootstrap/variables.tf @@ -4,6 +4,11 @@ variable "aws_region" { default = "us-east-2" } +variable "aws_role_external_id" { + description = "The external ID for role assumption." + type = string +} + variable "environments" { description = "The environments to create Terraform roles for." type = list(string) From 46496c7040b9c1da0a65b76de742adaa5da8af01 Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Wed, 18 Feb 2026 22:31:48 +0530 Subject: [PATCH 07/16] bot suggestions --- .github/workflows/run-ci-cd.yaml | 20 ++++++++++++++++---- infrastructure/backend/README.md | 2 +- infrastructure/bootstrap/main.tf | 1 - 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index 637bb2b5e5..f8c10d71c4 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -476,7 +476,7 @@ jobs: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-region: ${{ vars.AWS_REGION }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - role-duration-seconds: 1200 + role-duration-seconds: 3600 role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }} role-session-name: GitHubActions-BuildStagingImages role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform @@ -686,9 +686,21 @@ jobs: working-directory: infrastructure/bootstrap run: terraform validate + - name: Terraform Plan + working-directory: infrastructure/bootstrap + run: terraform plan -out=tfplan + + - name: Show plan in summary + working-directory: infrastructure/bootstrap + run: | + echo "## Bootstrap Terraform Plan Output" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + terraform show -no-color tfplan >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + - name: Terraform Apply working-directory: infrastructure/bootstrap - run: terraform apply -auto-approve + run: terraform apply -auto-approve tfplan timeout-minutes: 10 plan-staging-nest: @@ -717,7 +729,7 @@ jobs: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-region: ${{ vars.AWS_REGION }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - role-duration-seconds: 1200 + role-duration-seconds: 3600 role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }} role-session-name: GitHubActions-StagingPlan role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform @@ -818,7 +830,7 @@ jobs: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-region: ${{ vars.AWS_REGION }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - role-duration-seconds: 1200 + role-duration-seconds: 3600 role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }} role-session-name: GitHubActions-StagingDeploy role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform diff --git a/infrastructure/backend/README.md b/infrastructure/backend/README.md index 66e068a0b8..3813f39b0a 100644 --- a/infrastructure/backend/README.md +++ b/infrastructure/backend/README.md @@ -61,7 +61,7 @@ Use the following inline permissions for the `nest-backend` IAM User "Resource": "arn:aws:dynamodb:*:${AWS_ACCOUNT_ID}:table/nest-terraform-state-lock-*" }, { - "Sid": "KMSCRUDManagement", + "Sid": "KMSCreateManagement", "Effect": "Allow", "Action": [ "kms:CreateKey", diff --git a/infrastructure/bootstrap/main.tf b/infrastructure/bootstrap/main.tf index 392f5b5f47..27e3522372 100644 --- a/infrastructure/bootstrap/main.tf +++ b/infrastructure/bootstrap/main.tf @@ -209,7 +209,6 @@ data "aws_iam_policy_document" "part_one" { "ecr:GetDownloadUrlForLayer", "ecr:GetLifecyclePolicy", "ecr:GetRepositoryPolicy", - "ecr:GetRepositoryPolicy", "ecr:InitiateLayerUpload", "ecr:ListImages", "ecr:ListTagsForResource", From ed61069a4de05520bccef49192cede284a0563ab Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Wed, 18 Feb 2026 22:34:25 +0530 Subject: [PATCH 08/16] run ci/cd --- .github/workflows/run-ci-cd.yaml | 118 ++++++++++++++++--------------- 1 file changed, 62 insertions(+), 56 deletions(-) diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index f8c10d71c4..0265fd7e3b 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -441,16 +441,16 @@ jobs: env: RELEASE_VERSION: ${{ needs.set-release-version.outputs.release_version }} environment: staging - if: | - github.repository == 'OWASP/Nest' && - github.ref == 'refs/heads/main' + #if: | + #github.repository == 'OWASP/Nest' && + #github.ref == 'refs/heads/main' needs: - - run-backend-tests - - run-frontend-a11y-tests - - run-frontend-e2e-tests - - run-frontend-unit-tests - - run-infrastructure-tests - - set-release-version + #- run-backend-tests + #- run-frontend-a11y-tests + #- run-frontend-e2e-tests + #- run-frontend-unit-tests + #- run-infrastructure-tests + #- set-release-version permissions: contents: read runs-on: ubuntu-latest @@ -490,18 +490,18 @@ jobs: build-args: | OWASP_GID=1001 OWASP_UID=1001 - cache-from: | - type=gha - type=registry,ref=owasp/nest:backend-staging-cache - cache-to: | - type=registry,ref=owasp/nest:backend-staging-cache + #cache-from: | + #type=gha + #type=registry,ref=owasp/nest:backend-staging-cache + #cache-to: | + #type=registry,ref=owasp/nest:backend-staging-cache context: backend file: docker/backend/Dockerfile load: true platforms: linux/amd64 push: true + #owasp/nest:backend-staging tags: | - owasp/nest:backend-staging ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/nest-staging-backend:latest - name: Prepare frontend public environment @@ -533,22 +533,22 @@ jobs: NEXT_PUBLIC_SENTRY_DSN=$NEXT_PUBLIC_SENTRY_DSN EOF - - name: Get backend image size - id: backend-size - run: | - IMAGE_NAME="owasp/nest:backend-staging" - RAW_SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}') - DISPLAY_SIZE=$(numfmt --to=iec --suffix=B "$RAW_SIZE") - echo "human_readable=$DISPLAY_SIZE" >> $GITHUB_OUTPUT + #- name: Get backend image size + #id: backend-size + #run: | + #IMAGE_NAME="owasp/nest:backend-staging" + #RAW_SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}') + #DISPLAY_SIZE=$(numfmt --to=iec --suffix=B "$RAW_SIZE") + #echo "human_readable=$DISPLAY_SIZE" >> $GITHUB_OUTPUT - name: Build frontend image uses: docker/build-push-action@601a80b39c9405e50806ae38af30926f9d957c47 with: - cache-from: | - type=gha - type=registry,ref=owasp/nest:frontend-staging-cache - cache-to: | - type=registry,ref=owasp/nest:frontend-staging-cache + #cache-from: | + #type=gha + #type=registry,ref=owasp/nest:frontend-staging-cache + #cache-to: | + #type=registry,ref=owasp/nest:frontend-staging-cache context: frontend file: docker/frontend/Dockerfile load: true @@ -557,26 +557,26 @@ jobs: secrets: | RELEASE_VERSION=${{ needs.set-release-version.outputs.release_version }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + # owasp/nest:frontend-staging tags: | - owasp/nest:frontend-staging ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/nest-staging-frontend:latest - - name: Get frontend image size - id: frontend-size - run: | - IMAGE_NAME="owasp/nest:frontend-staging" - RAW_SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}') - DISPLAY_SIZE=$(numfmt --to=iec --suffix=B "$RAW_SIZE") - echo "human_readable=$DISPLAY_SIZE" >> $GITHUB_OUTPUT - - - name: Create Docker image size report - run: | - { - echo "## Docker Image Size Report" - echo "" - echo "**Backend:** ${{ steps.backend-size.outputs.human_readable }}" - echo "**Frontend:** ${{ steps.frontend-size.outputs.human_readable }}" - } >> $GITHUB_STEP_SUMMARY + #- name: Get frontend image size + #id: frontend-size + #run: | + #IMAGE_NAME="owasp/nest:frontend-staging" + #RAW_SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}') + #DISPLAY_SIZE=$(numfmt --to=iec --suffix=B "$RAW_SIZE") + #echo "human_readable=$DISPLAY_SIZE" >> $GITHUB_OUTPUT + + #- name: Create Docker image size report + #run: | + #{ + #echo "## Docker Image Size Report" + #echo "" + #echo "**Backend:** ${{ steps.backend-size.outputs.human_readable }}" + #echo "**Frontend:** ${{ steps.frontend-size.outputs.human_readable }}" + #} >> $GITHUB_STEP_SUMMARY timeout-minutes: 10 scan-staging-images: @@ -628,11 +628,11 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - if: | - github.repository == 'OWASP/Nest' && - github.ref == 'refs/heads/main' - needs: - - scan-staging-images + #if: | + #github.repository == 'OWASP/Nest' && + #github.ref == 'refs/heads/main' + #needs: + #- scan-staging-images permissions: contents: read runs-on: ubuntu-latest @@ -709,12 +709,12 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - if: | - github.repository == 'OWASP/Nest' && - github.ref == 'refs/heads/main' + #if: | + #github.repository == 'OWASP/Nest' && + #github.ref == 'refs/heads/main' needs: - bootstrap-staging-nest - - scan-staging-images + #- scan-staging-images - set-release-version permissions: contents: read @@ -775,6 +775,12 @@ jobs: frontend_use_fargate_spot=$FRONTEND_USE_FARGATE_SPOT lambda_function_name="$LAMBDA_FUNCTION_NAME" project_name="$PROJECT_NAME" + + secret_recovery_window_in_days = 0 + db_backup_retention_period = 0 + db_deletion_protection = false + db_skip_final_snapshot = true + EOF - name: Terraform Init @@ -812,9 +818,9 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - if: | - github.repository == 'OWASP/Nest' && - github.ref == 'refs/heads/main' + #if: | + #github.repository == 'OWASP/Nest' && + #github.ref == 'refs/heads/main' needs: - plan-staging-nest permissions: From 244fde9e3e635a85ca33993d1a3db186958266c7 Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Wed, 18 Feb 2026 22:35:51 +0530 Subject: [PATCH 09/16] run ci/cd --- .github/workflows/run-ci-cd.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index 0265fd7e3b..ad160a7079 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -450,7 +450,7 @@ jobs: #- run-frontend-e2e-tests #- run-frontend-unit-tests #- run-infrastructure-tests - #- set-release-version + - set-release-version permissions: contents: read runs-on: ubuntu-latest From f2acb34dda15948c020eaca0259ace1aa3996321 Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Wed, 18 Feb 2026 22:49:00 +0530 Subject: [PATCH 10/16] Run ci/cd --- .github/workflows/run-ci-cd.yaml | 3 +++ infrastructure/bootstrap/main.tf | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index ad160a7079..5ed1fe11b7 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -479,6 +479,7 @@ jobs: role-duration-seconds: 3600 role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }} role-session-name: GitHubActions-BuildStagingImages + role-skip-session-tagging: true role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform - name: Login to Amazon ECR @@ -732,6 +733,7 @@ jobs: role-duration-seconds: 3600 role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }} role-session-name: GitHubActions-StagingPlan + role-skip-session-tagging: true role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform - name: Install Terraform @@ -839,6 +841,7 @@ jobs: role-duration-seconds: 3600 role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }} role-session-name: GitHubActions-StagingDeploy + role-skip-session-tagging: true role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/nest-staging-terraform - name: Install Terraform diff --git a/infrastructure/bootstrap/main.tf b/infrastructure/bootstrap/main.tf index 27e3522372..f2924fb4a5 100644 --- a/infrastructure/bootstrap/main.tf +++ b/infrastructure/bootstrap/main.tf @@ -601,7 +601,7 @@ resource "aws_iam_role" "terraform" { Version = "2012-10-17" Statement = [ { - Action = ["sts:AssumeRole", "sts:TagSession"] + Action = ["sts:AssumeRole"] Condition = { StringEquals = { "sts:ExternalId" = var.aws_role_external_id From 3ce855d2f93a01367843232e748cac87df4b83f2 Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Wed, 18 Feb 2026 22:54:52 +0530 Subject: [PATCH 11/16] revert CI/CD changes --- .github/workflows/run-ci-cd.yaml | 116 +++++++++++++++---------------- 1 file changed, 55 insertions(+), 61 deletions(-) diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index 5ed1fe11b7..8817469077 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -441,15 +441,15 @@ jobs: env: RELEASE_VERSION: ${{ needs.set-release-version.outputs.release_version }} environment: staging - #if: | - #github.repository == 'OWASP/Nest' && - #github.ref == 'refs/heads/main' + if: | + github.repository == 'OWASP/Nest' && + github.ref == 'refs/heads/main' needs: - #- run-backend-tests - #- run-frontend-a11y-tests - #- run-frontend-e2e-tests - #- run-frontend-unit-tests - #- run-infrastructure-tests + - run-backend-tests + - run-frontend-a11y-tests + - run-frontend-e2e-tests + - run-frontend-unit-tests + - run-infrastructure-tests - set-release-version permissions: contents: read @@ -491,18 +491,18 @@ jobs: build-args: | OWASP_GID=1001 OWASP_UID=1001 - #cache-from: | - #type=gha - #type=registry,ref=owasp/nest:backend-staging-cache - #cache-to: | - #type=registry,ref=owasp/nest:backend-staging-cache + cache-from: | + type=gha + type=registry,ref=owasp/nest:backend-staging-cache + cache-to: | + type=registry,ref=owasp/nest:backend-staging-cache context: backend file: docker/backend/Dockerfile load: true platforms: linux/amd64 push: true - #owasp/nest:backend-staging tags: | + owasp/nest:backend-staging ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/nest-staging-backend:latest - name: Prepare frontend public environment @@ -534,22 +534,22 @@ jobs: NEXT_PUBLIC_SENTRY_DSN=$NEXT_PUBLIC_SENTRY_DSN EOF - #- name: Get backend image size - #id: backend-size - #run: | - #IMAGE_NAME="owasp/nest:backend-staging" - #RAW_SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}') - #DISPLAY_SIZE=$(numfmt --to=iec --suffix=B "$RAW_SIZE") - #echo "human_readable=$DISPLAY_SIZE" >> $GITHUB_OUTPUT + - name: Get backend image size + id: backend-size + run: | + IMAGE_NAME="owasp/nest:backend-staging" + RAW_SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}') + DISPLAY_SIZE=$(numfmt --to=iec --suffix=B "$RAW_SIZE") + echo "human_readable=$DISPLAY_SIZE" >> $GITHUB_OUTPUT - name: Build frontend image uses: docker/build-push-action@601a80b39c9405e50806ae38af30926f9d957c47 with: - #cache-from: | - #type=gha - #type=registry,ref=owasp/nest:frontend-staging-cache - #cache-to: | - #type=registry,ref=owasp/nest:frontend-staging-cache + cache-from: | + type=gha + type=registry,ref=owasp/nest:frontend-staging-cache + cache-to: | + type=registry,ref=owasp/nest:frontend-staging-cache context: frontend file: docker/frontend/Dockerfile load: true @@ -558,26 +558,26 @@ jobs: secrets: | RELEASE_VERSION=${{ needs.set-release-version.outputs.release_version }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} - # owasp/nest:frontend-staging tags: | + owasp/nest:frontend-staging ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/nest-staging-frontend:latest - #- name: Get frontend image size - #id: frontend-size - #run: | - #IMAGE_NAME="owasp/nest:frontend-staging" - #RAW_SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}') - #DISPLAY_SIZE=$(numfmt --to=iec --suffix=B "$RAW_SIZE") - #echo "human_readable=$DISPLAY_SIZE" >> $GITHUB_OUTPUT - - #- name: Create Docker image size report - #run: | - #{ - #echo "## Docker Image Size Report" - #echo "" - #echo "**Backend:** ${{ steps.backend-size.outputs.human_readable }}" - #echo "**Frontend:** ${{ steps.frontend-size.outputs.human_readable }}" - #} >> $GITHUB_STEP_SUMMARY + - name: Get frontend image size + id: frontend-size + run: | + IMAGE_NAME="owasp/nest:frontend-staging" + RAW_SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}') + DISPLAY_SIZE=$(numfmt --to=iec --suffix=B "$RAW_SIZE") + echo "human_readable=$DISPLAY_SIZE" >> $GITHUB_OUTPUT + + - name: Create Docker image size report + run: | + { + echo "## Docker Image Size Report" + echo "" + echo "**Backend:** ${{ steps.backend-size.outputs.human_readable }}" + echo "**Frontend:** ${{ steps.frontend-size.outputs.human_readable }}" + } >> $GITHUB_STEP_SUMMARY timeout-minutes: 10 scan-staging-images: @@ -629,11 +629,11 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - #if: | - #github.repository == 'OWASP/Nest' && - #github.ref == 'refs/heads/main' - #needs: - #- scan-staging-images + if: | + github.repository == 'OWASP/Nest' && + github.ref == 'refs/heads/main' + needs: + - scan-staging-images permissions: contents: read runs-on: ubuntu-latest @@ -710,12 +710,12 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - #if: | - #github.repository == 'OWASP/Nest' && - #github.ref == 'refs/heads/main' + if: | + github.repository == 'OWASP/Nest' && + github.ref == 'refs/heads/main' needs: - bootstrap-staging-nest - #- scan-staging-images + - scan-staging-images - set-release-version permissions: contents: read @@ -777,12 +777,6 @@ jobs: frontend_use_fargate_spot=$FRONTEND_USE_FARGATE_SPOT lambda_function_name="$LAMBDA_FUNCTION_NAME" project_name="$PROJECT_NAME" - - secret_recovery_window_in_days = 0 - db_backup_retention_period = 0 - db_deletion_protection = false - db_skip_final_snapshot = true - EOF - name: Terraform Init @@ -820,9 +814,9 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - #if: | - #github.repository == 'OWASP/Nest' && - #github.ref == 'refs/heads/main' + if: | + github.repository == 'OWASP/Nest' && + github.ref == 'refs/heads/main' needs: - plan-staging-nest permissions: From 226a1e7cb19b947d4aa0c6bb4a9c50660be3f2ed Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Thu, 19 Feb 2026 00:39:05 +0530 Subject: [PATCH 12/16] improve s3 bucket and dynamodb table names --- infrastructure/backend/README.md | 8 ++-- infrastructure/backend/main.tf | 45 ++++++++++++------ infrastructure/bootstrap/README.md | 6 +-- infrastructure/bootstrap/main.tf | 6 +-- .../bootstrap/terraform.tfbackend.example | 2 +- infrastructure/staging/README.md | 47 ++++++++++--------- infrastructure/staging/main.tf | 4 +- .../staging/terraform.tfbackend.example | 2 +- 8 files changed, 69 insertions(+), 51 deletions(-) diff --git a/infrastructure/backend/README.md b/infrastructure/backend/README.md index 3813f39b0a..86f31f1b25 100644 --- a/infrastructure/backend/README.md +++ b/infrastructure/backend/README.md @@ -1,6 +1,7 @@ ## Inline Permissions Use the following inline permissions for the `nest-backend` IAM User *Note*: replace ${AWS_ACCOUNT_ID} and ${AWS_BACKEND_KMS_KEY_ARN} with appropriate values. +*Note*: use "*" instead of `AWS_BACKEND_KMS_KEY_ARN` on first `terraform apply`. ```json { @@ -12,6 +13,7 @@ Use the following inline permissions for the `nest-backend` IAM User "Action": [ "s3:CreateBucket", "s3:DeleteBucket", + "s3:DeleteBucketPolicy", "s3:GetAccelerateConfiguration", "s3:GetBucketAcl", "s3:GetBucketCors", @@ -39,8 +41,8 @@ Use the following inline permissions for the `nest-backend` IAM User "s3:PutObject" ], "Resource": [ - "arn:aws:s3:::nest-terraform-state-*", - "arn:aws:s3:::nest-terraform-state-*/*" + "arn:aws:s3:::nest-*-terraform-state*", + "arn:aws:s3:::nest-*-terraform-state*/*" ] }, { @@ -58,7 +60,7 @@ Use the following inline permissions for the `nest-backend` IAM User "dynamodb:UpdateContinuousBackups", "dynamodb:UpdateTable" ], - "Resource": "arn:aws:dynamodb:*:${AWS_ACCOUNT_ID}:table/nest-terraform-state-lock-*" + "Resource": "arn:aws:dynamodb:*:${AWS_ACCOUNT_ID}:table/nest-*-terraform-state-lock" }, { "Sid": "KMSCreateManagement", diff --git a/infrastructure/backend/main.tf b/infrastructure/backend/main.tf index c175d9a187..e16b5a71ad 100644 --- a/infrastructure/backend/main.tf +++ b/infrastructure/backend/main.tf @@ -34,10 +34,12 @@ resource "random_id" "suffix" { } data "aws_iam_policy_document" "logs" { + for_each = local.state_environments + statement { actions = ["s3:PutObject"] effect = "Allow" - resources = ["${aws_s3_bucket.logs.arn}/*"] + resources = ["${aws_s3_bucket.logs[each.key].arn}/*"] sid = "s3-log-delivery" principals { @@ -78,10 +80,10 @@ resource "aws_dynamodb_table" "state_lock" { billing_mode = "PAY_PER_REQUEST" hash_key = "LockID" - name = "${var.project_name}-terraform-state-lock-${each.key}" + name = "${var.project_name}-${each.key}-terraform-state-lock" tags = merge(local.common_tags, { Environment = each.key - Name = "${var.project_name}-terraform-state-lock-${each.key}" + Name = "${var.project_name}-${each.key}-terraform-state-lock" }) attribute { @@ -101,9 +103,12 @@ resource "aws_dynamodb_table" "state_lock" { } resource "aws_s3_bucket" "logs" { # NOSONAR - bucket = "${var.project_name}-terraform-state-logs-${random_id.suffix.hex}" + for_each = local.state_environments + + bucket = "${var.project_name}-${each.key}-terraform-state-logs-${random_id.suffix.hex}" tags = merge(local.common_tags, { - Name = "${var.project_name}-terraform-state-logs" + Environment = each.key + Name = "${var.project_name}-${each.key}-terraform-state-logs" }) lifecycle { @@ -112,7 +117,9 @@ resource "aws_s3_bucket" "logs" { # NOSONAR } resource "aws_s3_bucket_lifecycle_configuration" "logs" { - bucket = aws_s3_bucket.logs.id + for_each = local.state_environments + + bucket = aws_s3_bucket.logs[each.key].id rule { id = "expire-logs" @@ -131,21 +138,27 @@ resource "aws_s3_bucket_lifecycle_configuration" "logs" { } resource "aws_s3_bucket_policy" "logs" { - bucket = aws_s3_bucket.logs.id - policy = data.aws_iam_policy_document.logs.json + for_each = local.state_environments + + bucket = aws_s3_bucket.logs[each.key].id + policy = data.aws_iam_policy_document.logs[each.key].json } resource "aws_s3_bucket_public_access_block" "logs" { + for_each = local.state_environments + block_public_acls = true block_public_policy = true - bucket = aws_s3_bucket.logs.id + bucket = aws_s3_bucket.logs[each.key].id ignore_public_acls = true restrict_public_buckets = true } #trivy:ignore:AVD-AWS-0132 resource "aws_s3_bucket_server_side_encryption_configuration" "logs" { - bucket = aws_s3_bucket.logs.id + for_each = local.state_environments + + bucket = aws_s3_bucket.logs[each.key].id rule { apply_server_side_encryption_by_default { @@ -155,7 +168,9 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "logs" { } resource "aws_s3_bucket_versioning" "logs" { - bucket = aws_s3_bucket.logs.id + for_each = local.state_environments + + bucket = aws_s3_bucket.logs[each.key].id versioning_configuration { status = "Enabled" @@ -165,11 +180,11 @@ resource "aws_s3_bucket_versioning" "logs" { resource "aws_s3_bucket" "state" { # NOSONAR for_each = local.state_environments - bucket = "${var.project_name}-terraform-state-${each.key}-${random_id.suffix.hex}" + bucket = "${var.project_name}-${each.key}-terraform-state-${random_id.suffix.hex}" object_lock_enabled = true tags = merge(local.common_tags, { Environment = each.key - Name = "${var.project_name}-terraform-state-${each.key}" + Name = "${var.project_name}-${each.key}-terraform-state" }) lifecycle { @@ -199,8 +214,8 @@ resource "aws_s3_bucket_logging" "state" { for_each = local.state_environments bucket = aws_s3_bucket.state[each.key].id - target_bucket = aws_s3_bucket.logs.id - target_prefix = "s3/${each.key}/" + target_bucket = aws_s3_bucket.logs[each.key].id + target_prefix = "s3/" } resource "aws_s3_bucket_object_lock_configuration" "state" { diff --git a/infrastructure/bootstrap/README.md b/infrastructure/bootstrap/README.md index c9f3abb16e..ae6a1abc5b 100644 --- a/infrastructure/bootstrap/README.md +++ b/infrastructure/bootstrap/README.md @@ -23,8 +23,8 @@ Use the following inline permissions for the `nest-bootstrap` IAM User "s3:PutObject" ], "Resource": [ - "arn:aws:s3:::nest-terraform-state-bootstrap-*", - "arn:aws:s3:::nest-terraform-state-bootstrap-*/*" + "arn:aws:s3:::nest-bootstrap-terraform-state-*", + "arn:aws:s3:::nest-bootstrap-terraform-state-*/*" ] }, { @@ -36,7 +36,7 @@ Use the following inline permissions for the `nest-bootstrap` IAM User "dynamodb:GetItem", "dynamodb:PutItem" ], - "Resource": "arn:aws:dynamodb:*:${AWS_ACCOUNT_ID}:table/nest-terraform-state-lock-bootstrap" + "Resource": "arn:aws:dynamodb:*:${AWS_ACCOUNT_ID}:table/nest-bootstrap-terraform-state-lock" }, { "Sid": "IAMManagement", diff --git a/infrastructure/bootstrap/main.tf b/infrastructure/bootstrap/main.tf index f2924fb4a5..313902dc94 100644 --- a/infrastructure/bootstrap/main.tf +++ b/infrastructure/bootstrap/main.tf @@ -110,7 +110,7 @@ data "aws_iam_policy_document" "part_one" { "dynamodb:PutItem", ] resources = [ - "arn:aws:dynamodb:*:${data.aws_caller_identity.current.account_id}:table/${var.project_name}-terraform-state-lock-${each.key}", + "arn:aws:dynamodb:*:${data.aws_caller_identity.current.account_id}:table/${var.project_name}-${each.key}-terraform-state-lock", ] } @@ -549,8 +549,8 @@ data "aws_iam_policy_document" "part_two" { "s3:PutObject", ] resources = [ - "arn:aws:s3:::${var.project_name}-*", - "arn:aws:s3:::${var.project_name}-*/*", + "arn:aws:s3:::${var.project_name}-${each.key}-*", + "arn:aws:s3:::${var.project_name}-${each.key}*/*", ] } diff --git a/infrastructure/bootstrap/terraform.tfbackend.example b/infrastructure/bootstrap/terraform.tfbackend.example index e161a953fc..4141551d28 100644 --- a/infrastructure/bootstrap/terraform.tfbackend.example +++ b/infrastructure/bootstrap/terraform.tfbackend.example @@ -1,3 +1,3 @@ bucket = "${STATE_BUCKET_NAME}" -dynamodb_table = "nest-terraform-state-lock-bootstrap" +dynamodb_table = "nest-bootstrap-terraform-state-lock" region = "us-east-2" diff --git a/infrastructure/staging/README.md b/infrastructure/staging/README.md index 69fca0d875..ad363098ea 100644 --- a/infrastructure/staging/README.md +++ b/infrastructure/staging/README.md @@ -5,29 +5,30 @@ Use the following inline permissions for the `nest-staging` IAM User ```json { "Version": "2012-10-17", - "Statement": [ { - "Sid": "S3StateManagement", - "Effect": "Allow", - "Action": [ - "s3:GetObject", - "s3:PutObject", - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::${TERRAFORM_STATE_BUCKET_NAME}", - "arn:aws:s3:::${TERRAFORM_STATE_BUCKET_NAME}/*" - ] - }, - { - "Sid": "DynamoDBStateManagement", - "Effect": "Allow", - "Action": [ - "dynamodb:GetItem", - "dynamodb:PutItem", - "dynamodb:DeleteItem" - ], - "Resource": "arn:aws:dynamodb:${AWS_REGION}:${AWS_ACCOUNT_ID}:table/${TERRAFORM_DYNAMODB_TABLE_NAME}" - }, + "Statement": [ + { + "Sid": "S3StateManagement", + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::nest-staging-terraform-state-*", + "arn:aws:s3:::nest-staging-terraform-state-*/*" + ] + }, + { + "Sid": "DynamoDBStateManagement", + "Effect": "Allow", + "Action": [ + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:DeleteItem" + ], + "Resource": "arn:aws:dynamodb:ap-south-1:${AWS_ACCOUNT_ID}:table/nest-staging-terraform-state-lock" + }, { "Sid": "KMSStateManagement", "Effect": "Allow", diff --git a/infrastructure/staging/main.tf b/infrastructure/staging/main.tf index e13f117f1b..4a49e135b3 100644 --- a/infrastructure/staging/main.tf +++ b/infrastructure/staging/main.tf @@ -15,8 +15,8 @@ locals { ManagedBy = "Terraform" Project = var.project_name } - fixtures_bucket_name = coalesce(var.fixtures_bucket_name, "${var.project_name}-fixtures") - zappa_bucket_name = coalesce(var.zappa_bucket_name, "${var.project_name}-zappa-deployments") + fixtures_bucket_name = coalesce(var.fixtures_bucket_name, "${var.project_name}-${var.environment}-fixtures") + zappa_bucket_name = coalesce(var.zappa_bucket_name, "${var.project_name}-${var.environment}-zappa-deployments") } module "alb" { diff --git a/infrastructure/staging/terraform.tfbackend.example b/infrastructure/staging/terraform.tfbackend.example index 060e98590f..f42bcce998 100644 --- a/infrastructure/staging/terraform.tfbackend.example +++ b/infrastructure/staging/terraform.tfbackend.example @@ -1,3 +1,3 @@ bucket = "${STATE_BUCKET_NAME}" -dynamodb_table = "nest-terraform-state-lock-staging" +dynamodb_table = "nest-staging-terraform-state-lock" region = "us-east-2" From b70c940417653efaa6aa550dc99fae935c3fb420 Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Thu, 19 Feb 2026 00:47:47 +0530 Subject: [PATCH 13/16] update code --- infrastructure/bootstrap/main.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/bootstrap/main.tf b/infrastructure/bootstrap/main.tf index 313902dc94..d2faeeda75 100644 --- a/infrastructure/bootstrap/main.tf +++ b/infrastructure/bootstrap/main.tf @@ -311,6 +311,7 @@ data "aws_iam_policy_document" "part_two" { "elasticloadbalancing:DeleteLoadBalancer", "elasticloadbalancing:DeleteRule", "elasticloadbalancing:DeleteTargetGroup", + "elasticloadbalancing:DeregisterTargets", "elasticloadbalancing:ModifyListener", "elasticloadbalancing:ModifyLoadBalancerAttributes", "elasticloadbalancing:ModifyRule", From a58552808feb83a7e20ad70507f4fc4975521ef8 Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Thu, 19 Feb 2026 00:51:06 +0530 Subject: [PATCH 14/16] update code --- infrastructure/bootstrap/main.tf | 2 +- infrastructure/bootstrap/variables.tf | 1 + infrastructure/staging/README.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/infrastructure/bootstrap/main.tf b/infrastructure/bootstrap/main.tf index d2faeeda75..3d26c6a472 100644 --- a/infrastructure/bootstrap/main.tf +++ b/infrastructure/bootstrap/main.tf @@ -551,7 +551,7 @@ data "aws_iam_policy_document" "part_two" { ] resources = [ "arn:aws:s3:::${var.project_name}-${each.key}-*", - "arn:aws:s3:::${var.project_name}-${each.key}*/*", + "arn:aws:s3:::${var.project_name}-${each.key}-*/*", ] } diff --git a/infrastructure/bootstrap/variables.tf b/infrastructure/bootstrap/variables.tf index 92a107259c..e31de7854f 100644 --- a/infrastructure/bootstrap/variables.tf +++ b/infrastructure/bootstrap/variables.tf @@ -6,6 +6,7 @@ variable "aws_region" { variable "aws_role_external_id" { description = "The external ID for role assumption." + sensitive = true type = string } diff --git a/infrastructure/staging/README.md b/infrastructure/staging/README.md index ad363098ea..e4938aa2cc 100644 --- a/infrastructure/staging/README.md +++ b/infrastructure/staging/README.md @@ -27,7 +27,7 @@ Use the following inline permissions for the `nest-staging` IAM User "dynamodb:PutItem", "dynamodb:DeleteItem" ], - "Resource": "arn:aws:dynamodb:ap-south-1:${AWS_ACCOUNT_ID}:table/nest-staging-terraform-state-lock" + "Resource": "arn:aws:dynamodb:${AWS_REGION}:${AWS_ACCOUNT_ID}:table/nest-staging-terraform-state-lock" }, { "Sid": "KMSStateManagement", From d43fc1cd52428568c3686e19e70c5c31025571d5 Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Thu, 19 Feb 2026 00:54:15 +0530 Subject: [PATCH 15/16] run ci/cd --- .github/workflows/run-ci-cd.yaml | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index 8817469077..6b6dfc43b3 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -629,11 +629,11 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - if: | - github.repository == 'OWASP/Nest' && - github.ref == 'refs/heads/main' - needs: - - scan-staging-images + #if: | + #github.repository == 'OWASP/Nest' && + #github.ref == 'refs/heads/main' + #needs: + #- scan-staging-images permissions: contents: read runs-on: ubuntu-latest @@ -710,12 +710,12 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - if: | - github.repository == 'OWASP/Nest' && - github.ref == 'refs/heads/main' + #if: | + #github.repository == 'OWASP/Nest' && + #github.ref == 'refs/heads/main' needs: - bootstrap-staging-nest - - scan-staging-images + #- scan-staging-images - set-release-version permissions: contents: read @@ -777,6 +777,12 @@ jobs: frontend_use_fargate_spot=$FRONTEND_USE_FARGATE_SPOT lambda_function_name="$LAMBDA_FUNCTION_NAME" project_name="$PROJECT_NAME" + + secret_recovery_window_in_days = 0 + db_backup_retention_period = 0 + db_deletion_protection = false + db_skip_final_snapshot = true + EOF - name: Terraform Init @@ -814,9 +820,9 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - if: | - github.repository == 'OWASP/Nest' && - github.ref == 'refs/heads/main' + #if: | + #github.repository == 'OWASP/Nest' && + #github.ref == 'refs/heads/main' needs: - plan-staging-nest permissions: From 1924052f7198f309f4eb94c78d79d425f69e5f61 Mon Sep 17 00:00:00 2001 From: Rudransh Shrivastava Date: Thu, 19 Feb 2026 00:58:14 +0530 Subject: [PATCH 16/16] Revert "run ci/cd" This reverts commit d43fc1cd52428568c3686e19e70c5c31025571d5. --- .github/workflows/run-ci-cd.yaml | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index 6b6dfc43b3..8817469077 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -629,11 +629,11 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - #if: | - #github.repository == 'OWASP/Nest' && - #github.ref == 'refs/heads/main' - #needs: - #- scan-staging-images + if: | + github.repository == 'OWASP/Nest' && + github.ref == 'refs/heads/main' + needs: + - scan-staging-images permissions: contents: read runs-on: ubuntu-latest @@ -710,12 +710,12 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - #if: | - #github.repository == 'OWASP/Nest' && - #github.ref == 'refs/heads/main' + if: | + github.repository == 'OWASP/Nest' && + github.ref == 'refs/heads/main' needs: - bootstrap-staging-nest - #- scan-staging-images + - scan-staging-images - set-release-version permissions: contents: read @@ -777,12 +777,6 @@ jobs: frontend_use_fargate_spot=$FRONTEND_USE_FARGATE_SPOT lambda_function_name="$LAMBDA_FUNCTION_NAME" project_name="$PROJECT_NAME" - - secret_recovery_window_in_days = 0 - db_backup_retention_period = 0 - db_deletion_protection = false - db_skip_final_snapshot = true - EOF - name: Terraform Init @@ -820,9 +814,9 @@ jobs: TF_INPUT: false TF_IN_AUTOMATION: true environment: staging - #if: | - #github.repository == 'OWASP/Nest' && - #github.ref == 'refs/heads/main' + if: | + github.repository == 'OWASP/Nest' && + github.ref == 'refs/heads/main' needs: - plan-staging-nest permissions: