diff --git a/.github/workflows/environment-main-deploy.yaml b/.github/workflows/environment-main-deploy.yaml index 0c9cc4bb..b99628ba 100644 --- a/.github/workflows/environment-main-deploy.yaml +++ b/.github/workflows/environment-main-deploy.yaml @@ -31,10 +31,8 @@ jobs: with: path: terraform/environment/wildsea variables: | - aws_account="${{ vars.AWS_ACCOUNT }}" - aws_region="${{ vars.AWS_REGION }}" - state_bucket="${{ vars.STATE_BUCKET }}" environment="${{ vars.ENVIRONMENT }}" + saml_metadata_url="${{ secrets.SAML_METADATA_URL }}" backend_config: bucket=${{ vars.STATE_BUCKET }} key=${{ vars.ENVIRONMENT }}/terraform.tfstate diff --git a/.github/workflows/environment-main-plan.yaml b/.github/workflows/environment-main-plan.yaml index c973dc72..02997971 100644 --- a/.github/workflows/environment-main-plan.yaml +++ b/.github/workflows/environment-main-plan.yaml @@ -29,9 +29,6 @@ jobs: with: path: terraform/environment/wildsea variables: | - aws_account="${{ vars.AWS_ACCOUNT }}" - aws_region="${{ vars.AWS_REGION }}" - state_bucket="${{ vars.STATE_BUCKET }}" environment="${{ vars.ENVIRONMENT }}" saml_metadata_url="${{ secrets.SAML_METADATA_URL }}" backend_config: diff --git a/.gitignore b/.gitignore index 93aff3e1..998dfc7d 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ override.tf.json .terraformrc terraform.rc .validate +.apply +.plan diff --git a/Makefile b/Makefile index 8efbbfe5..7eddd28f 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,11 @@ default: all -TERRAFORM_ENVIRONMENTS := aws github wildsea +TERRAFORM_ENVIRONMENTS := aws github wildsea aws-dev wildsea-dev TERRAFOM_VALIDATE := $(addsuffix /.validate,$(addprefix terraform/environment/, $(TERRAFORM_ENVIRONMENTS))) +ACCOUNT_ID := $(shell aws sts get-caller-identity --query 'Account' --output text) +AWS_REGION ?= "ap-southeast-2" +RO_ROLE = arn:aws:iam::$(ACCOUNT_ID):role/GitHubAction-Wildsea-ro-dev +RW_ROLE = arn:aws:iam::$(ACCOUNT_ID):role/GitHubAction-Wildsea-rw-dev all: $(TERRAFOM_VALIDATE) @@ -9,3 +13,26 @@ terraform/environment/%/.validate: terraform/environment/%/*.tf cd terraform/environment/$* ; terraform fmt cd terraform/environment/$* ; terraform validate touch $@ + +.PHONY: dev +dev: terraform/environment/aws-dev/.apply terraform/environment/wildsea-dev/.apply + @true + +terraform/environment/aws-dev/.apply: terraform/environment/aws-dev/*.tf terraform/module/iac-roles/*.tf + ./terraform/environment/aws-dev/deploy.sh $(ACCOUNT_ID) dev + touch $@ + +terraform/environment/wildsea-dev/.plan: terraform/environment/wildsea-dev/*.tf terraform/module/wildsea/*.tf terraform/environment/wildsea-dev/.terraform + cd terraform/environment/wildsea-dev ; ../../../scripts/run-as.sh $(RO_ROLE) \ + terraform plan -out=./plan + +terraform/environment/wildsea-dev/.apply: terraform/environment/wildsea-dev/.plan + cd terraform/environment/wildsea-dev ; ../../../scripts/run-as.sh $(RW_ROLE) \ + terraform apply ./plan + touch $@ + +terraform/environment/wildsea-dev/.terraform: terraform/environment/wildsea-dev/*.tf terraform/module/wildsea/*.tf + cd terraform/environment/wildsea-dev ; terraform init \ + -backend-config=bucket=terraform-state-$(ACCOUNT_ID) \ + -backend-config=key=dev/terraform.tfstate \ + -backend-config=region=$(AWS_REGION) diff --git a/README.md b/README.md index 632ca5ce..a9f627ce 100644 --- a/README.md +++ b/README.md @@ -81,3 +81,12 @@ to the URL you got earlier. Since you require the details of the Cognito pool before creating the Jumpcloud setup, you will need to re-run the deployment after adding the secrets. + +## Development Environment + +After having set up the AWS Account, use `AWS_PROFILE= make dev` to +deploy a development version. If this is a different AWS Account from the real +deployment, you will need to create an S3 bucket for the state, in the same way +as you did for the real deployment. + +Development environments will not use Jumpcloud, but instead use Cognito. diff --git a/scripts/run-as.sh b/scripts/run-as.sh new file mode 100755 index 00000000..bfe9643e --- /dev/null +++ b/scripts/run-as.sh @@ -0,0 +1,13 @@ +#!/bin/bash -eu + +ROLE_ARN=$1 +shift +COMMAND=$@ + +CREDS=$(aws sts assume-role --role-arn "$ROLE_ARN" --role-session-name "terraform-${RANDOM}" --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text) + +export AWS_ACCESS_KEY_ID="$(echo "${CREDS}" | awk '{print $1}')" +export AWS_SECRET_ACCESS_KEY="$(echo "${CREDS}" | awk '{print $2}')" +export AWS_SESSION_TOKEN="$(echo "${CREDS}" | awk '{print $3}')" + +exec $COMMAND diff --git a/terraform/environment/aws-dev/.terraform.lock.hcl b/terraform/environment/aws-dev/.terraform.lock.hcl new file mode 100644 index 00000000..169a35a3 --- /dev/null +++ b/terraform/environment/aws-dev/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.62.0" + hashes = [ + "h1:8tevkFG+ea/sNZYiQ2GQ02hknPcWBukxkrpjRCodQC0=", + "zh:1f366cbcda72fb123015439a42ab19f96e10ce4edb404273f4e1b7e06da20b73", + "zh:25f098454a34b483279e0382b24b4f42e51c067222c6e797eda5d3ec33b9beb1", + "zh:4b59d48b527e3cefd73f196853bfc265b3e1e57b55c1c8a2d12ff6e3534b4f07", + "zh:7bb88c1ca95e2b3f0f1fe8636925133b9813fc5b137cc467ba6a233ddf4b360e", + "zh:8a93dece40e816c92647e762839d0370e9cad2aa21dc4ca95baee9385f116459", + "zh:8dfe82c55ab8f633c1e2a39c687e9ca8c892d1c2005bf5166ac396ce868ecd05", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a754952d69b4860480d5207390e3ab42350c964dbca9a5ac0c6912dd24b4c11d", + "zh:b2a4dbf4abee0e9ec18c5d323b99defdcd3c681f8c4306fb6e02cff7de038f85", + "zh:b57d84be258b571c04271015f03858ab215768b82e47c11ecd86e789d577030a", + "zh:be811b03289407c8d59e6b199bf16e6071165565ffe502148172d0886cf849c4", + "zh:d4144c7366c840eff1ac15ba13d96063f798f0983d24053a832362033624fe6f", + "zh:d88612856d453c4e10c49c76e4ef522b7d068b4f7c3e2e0b03dd74540986eecd", + "zh:e8bd231a5d0786cc4aab8471bb6dabd5a5df1c598afda077a9f27987ada57b67", + "zh:ffb40a66b4d000a8ee4c54227eeb998f887ad867419c3af7d3981587788de074", + ] +} diff --git a/terraform/environment/aws-dev/deploy.sh b/terraform/environment/aws-dev/deploy.sh new file mode 120000 index 00000000..31e54d34 --- /dev/null +++ b/terraform/environment/aws-dev/deploy.sh @@ -0,0 +1 @@ +../aws/deploy.sh \ No newline at end of file diff --git a/terraform/environment/aws-dev/main.tf b/terraform/environment/aws-dev/main.tf new file mode 100644 index 00000000..6d0e9b80 --- /dev/null +++ b/terraform/environment/aws-dev/main.tf @@ -0,0 +1,58 @@ +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +variable "app_name" { + default = "Wildsea" +} + +variable "action_prefix" { + default = "GitHubAction" +} + +variable "repo" { + description = "Repository name" + type = string + default = "wildsea" +} + +variable "state_bucket" { + description = "State Bucket to use for deploys" + type = string +} + +variable "environment" { + description = "Unique name for the deployment" + type = string + default = "primary" +} + +terraform { + backend "s3" { + // region, bucket and key come from -backend-config + } +} + +locals { + prefix = "${var.app_name}-${var.environment}" +} + +provider "aws" { + default_tags { + tags = { + Application = "${var.app_name}-setup-${var.environment}" + } + } +} + +module "iac-roles" { + source = "../../module/iac-roles" + app_name = var.app_name + environment = var.environment + action_prefix = var.action_prefix + workspace = "none" + repo = var.repo + state_bucket_arn = "arn:${data.aws_partition.current.id}:s3:::${var.state_bucket}" + oidc_type = "AWS" + oidc_arn = data.aws_caller_identity.current.account_id +} diff --git a/terraform/environment/aws/main.tf b/terraform/environment/aws/main.tf index d5acbd4f..fc690912 100644 --- a/terraform/environment/aws/main.tf +++ b/terraform/environment/aws/main.tf @@ -49,3 +49,29 @@ provider "aws" { } } } + +import { + to = module.state-bucket.aws_s3_bucket.state + id = var.state_bucket +} + +module "iac-roles" { + source = "../../module/iac-roles" + app_name = var.app_name + environment = var.environment + action_prefix = var.action_prefix + workspace = var.workspace + repo = var.repo + state_bucket_arn = module.state-bucket.arn + oidc_arn = module.oidc.oidc_arn + oidc_type = "Federated" +} + +module "state-bucket" { + source = "../../module/state-bucket" + state_bucket = var.state_bucket +} + +module "oidc" { + source = "../../module/oidc" +} diff --git a/terraform/environment/wildsea-dev/.terraform.lock.hcl b/terraform/environment/wildsea-dev/.terraform.lock.hcl new file mode 100644 index 00000000..169a35a3 --- /dev/null +++ b/terraform/environment/wildsea-dev/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.62.0" + hashes = [ + "h1:8tevkFG+ea/sNZYiQ2GQ02hknPcWBukxkrpjRCodQC0=", + "zh:1f366cbcda72fb123015439a42ab19f96e10ce4edb404273f4e1b7e06da20b73", + "zh:25f098454a34b483279e0382b24b4f42e51c067222c6e797eda5d3ec33b9beb1", + "zh:4b59d48b527e3cefd73f196853bfc265b3e1e57b55c1c8a2d12ff6e3534b4f07", + "zh:7bb88c1ca95e2b3f0f1fe8636925133b9813fc5b137cc467ba6a233ddf4b360e", + "zh:8a93dece40e816c92647e762839d0370e9cad2aa21dc4ca95baee9385f116459", + "zh:8dfe82c55ab8f633c1e2a39c687e9ca8c892d1c2005bf5166ac396ce868ecd05", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a754952d69b4860480d5207390e3ab42350c964dbca9a5ac0c6912dd24b4c11d", + "zh:b2a4dbf4abee0e9ec18c5d323b99defdcd3c681f8c4306fb6e02cff7de038f85", + "zh:b57d84be258b571c04271015f03858ab215768b82e47c11ecd86e789d577030a", + "zh:be811b03289407c8d59e6b199bf16e6071165565ffe502148172d0886cf849c4", + "zh:d4144c7366c840eff1ac15ba13d96063f798f0983d24053a832362033624fe6f", + "zh:d88612856d453c4e10c49c76e4ef522b7d068b4f7c3e2e0b03dd74540986eecd", + "zh:e8bd231a5d0786cc4aab8471bb6dabd5a5df1c598afda077a9f27987ada57b67", + "zh:ffb40a66b4d000a8ee4c54227eeb998f887ad867419c3af7d3981587788de074", + ] +} diff --git a/terraform/environment/wildsea-dev/main.tf b/terraform/environment/wildsea-dev/main.tf new file mode 100644 index 00000000..0fb7a6d3 --- /dev/null +++ b/terraform/environment/wildsea-dev/main.tf @@ -0,0 +1,32 @@ +variable "saml_metadata_url" { + description = "SAML Metadata URL" + type = string + sensitive = true + default = "" +} + +locals { + app_name = "Wildsea" + prefix = "${local.app_name}-dev" +} + +terraform { + backend "s3" { + // region, bucket and key come from -backend-config + } +} + +provider "aws" { + default_tags { + tags = { + Application = local.prefix + } + } +} + +module "wildsea" { + source = "../../module/wildsea" + + saml_metadata_url = var.saml_metadata_url + prefix = local.prefix +} diff --git a/terraform/environment/wildsea-dev/plan b/terraform/environment/wildsea-dev/plan new file mode 100644 index 00000000..881d2b3f Binary files /dev/null and b/terraform/environment/wildsea-dev/plan differ diff --git a/terraform/environment/wildsea/main.tf b/terraform/environment/wildsea/main.tf index a570694a..b9254771 100644 --- a/terraform/environment/wildsea/main.tf +++ b/terraform/environment/wildsea/main.tf @@ -1,24 +1,3 @@ -data "aws_region" "current" {} -data "aws_partition" "current" {} -data "aws_caller_identity" "current" {} - -variable "aws_account" { - description = "ID of the AWS Account" - type = string - sensitive = true -} - -variable "aws_region" { - description = "AWS Region name" - type = string - sensitive = true -} - -variable "state_bucket" { - description = "Name of the S3 state bucket" - type = string -} - variable "environment" { description = "Name of the Environment" type = string @@ -49,3 +28,10 @@ provider "aws" { } } } + +module "wildsea" { + source = "../../module/wildsea" + + saml_metadata_url = var.saml_metadata_url + prefix = local.prefix +} diff --git a/terraform/module/iac-roles/main.tf b/terraform/module/iac-roles/main.tf new file mode 100644 index 00000000..9bfe8adf --- /dev/null +++ b/terraform/module/iac-roles/main.tf @@ -0,0 +1,48 @@ +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +variable "app_name" { + default = "Wildsea" +} + +variable "action_prefix" { + default = "GitHubAction" +} + +variable "workspace" { + description = "Github Organisation name" + type = string +} + +variable "repo" { + description = "Repository name" + type = string + default = "wildsea" +} + +variable "environment" { + description = "Unique name for the deployment" + type = string + default = "primary" +} + +variable "state_bucket_arn" { + description = "ARN of the state bucket" + type = string +} + +variable "oidc_arn" { + description = "ARN of the OIDC provider" + type = string +} + +variable "oidc_type" { + description = "Type of principal for the OIDC Provider" + type = string + default = "Federated" +} + +locals { + prefix = "${var.app_name}-${var.environment}" +} diff --git a/terraform/environment/aws/policy.tf b/terraform/module/iac-roles/policy.tf similarity index 57% rename from terraform/environment/aws/policy.tf rename to terraform/module/iac-roles/policy.tf index b4be3b45..362656ef 100644 --- a/terraform/environment/aws/policy.tf +++ b/terraform/module/iac-roles/policy.tf @@ -5,7 +5,7 @@ data "aws_iam_policy_document" "ro" { "s3:GetObject" ] resources = [ - "${aws_s3_bucket.state.arn}/${var.environment}/terraform.tfstate" + "${var.state_bucket_arn}/${var.environment}/terraform.tfstate" ] } @@ -15,7 +15,7 @@ data "aws_iam_policy_document" "ro" { "s3:ListBucket" ] resources = [ - aws_s3_bucket.state.arn + var.state_bucket_arn ] } @@ -38,7 +38,17 @@ data "aws_iam_policy_document" "ro" { "cognito-idp:Get*", ] resources = [ - "arn:${data.aws_partition.current.id}:cognito-idp:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:userpool/${var.app_name}-${var.environment}", + "arn:${data.aws_partition.current.id}:cognito-idp:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:userpool/*", + ] + } + + statement { + sid = "CognitoIdpGlobal" + actions = [ + "cognito-idp:DescribeUserPoolDomain", + ] + resources = [ + "*" ] } @@ -50,7 +60,19 @@ data "aws_iam_policy_document" "ro" { "cognito-identity:Get*", ] resources = [ - "arn:${data.aws_partition.current.id}:cognito-idp:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:identitypool/${var.app_name}-${var.environment}", + "arn:${data.aws_partition.current.id}:cognito-identity:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:identitypool/*", + ] + } + + statement { + actions = [ + "iam:GetRole", + "iam:List*", + "iam:GetPolicy*" + ] + resources = [ + "arn:${data.aws_partition.current.id}:iam::${data.aws_caller_identity.current.account_id}:role/${local.prefix}-*", + "arn:${data.aws_partition.current.id}:iam::${data.aws_caller_identity.current.account_id}:policy/${local.prefix}-*", ] } } @@ -62,12 +84,12 @@ data "aws_iam_policy_document" "rw" { "s3:PutObject" ] resources = [ - "${aws_s3_bucket.state.arn}/${var.environment}/terraform.tfstate" + "${var.state_bucket_arn}/${var.environment}/terraform.tfstate" ] } statement { - sid = "DynamodbNoItem" + sid = "DynamodbNoItem" effect = "Deny" actions = [ "dynamodb:DeleteItem", @@ -93,43 +115,56 @@ data "aws_iam_policy_document" "rw" { } statement { - sid = "CognitoIdpCreate" + sid = "CognitoIdp" actions = [ "cognito-idp:Create*", + "cognito-idp:TagResource", + "cognito-idp:UntagResource", + "cognito-idp:Delete*", ] resources = [ "arn:${data.aws_partition.current.id}:cognito-idp:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:userpool/*", ] - condition { - test = "StringEquals" - variable = "aws:RequestTag/Application" - values = [ local.prefix ] - } } statement { - sid = "CognitoIdp" + sid = "CognitoIdentity" actions = [ - "cognito-idp:Create*", - "cognito-idp:Delete*", - "cognito-idp:TagResource", - "cognito-idp:UntagResource", + "cognito-identity:Update*", + "cognito-identity:Delete*", ] resources = [ - "arn:${data.aws_partition.current.id}:cognito-idp:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:userpool/${var.app_name}-${var.environment}", + "arn:${data.aws_partition.current.id}:cognito-identity:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:identitypool/*", ] } + statement { - sid = "CognitoIdentity" + sid = "CognitoIdentityGlobal" actions = [ - "cognito-identity:Create*", - "cognito-identity:Update*", - "cognito-identity:Delete*", + "cognito-identity:CreateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", "cognito-identity:TagResource", "cognito-identity:UntagResource", ] resources = [ - "arn:${data.aws_partition.current.id}:cognito-idp:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:identitypool/${var.app_name}-${var.environment}", + "*" + ] + } + + statement { + actions = [ + "iam:CreateRole", + "iam:CreatePolicy", + "iam:DeleteRole", + "iam:DeletePolicy", + "iam:TagRole", + "iam:TagPolicy", + "iam:PassRole", + "iam:Attach*", + ] + resources = [ + "arn:${data.aws_partition.current.id}:iam::${data.aws_caller_identity.current.account_id}:role/${local.prefix}-*", + "arn:${data.aws_partition.current.id}:iam::${data.aws_caller_identity.current.account_id}:policy/${local.prefix}-*", ] } } @@ -142,8 +177,8 @@ data "aws_iam_policy_document" "rw_boundary" { "s3:PutObject" ] resources = [ - "${aws_s3_bucket.state.arn}/${var.environment}/terraform.tfstate", - "arn:${data.aws_partition.current.id}:s3:::${var.app_name}-${var.environment}-*/*" + "${var.state_bucket_arn}/${var.environment}/terraform.tfstate", + "arn:${data.aws_partition.current.id}:s3:::${lower(var.app_name)}-${var.environment}-*/*" ] } @@ -153,7 +188,8 @@ data "aws_iam_policy_document" "rw_boundary" { "s3:ListBucket" ] resources = [ - "arn:${data.aws_partition.current.id}:s3:::${var.app_name}-${var.environment}-*/*" + "${var.state_bucket_arn}/${var.environment}/terraform.tfstate", + "arn:${data.aws_partition.current.id}:s3:::${lower(var.app_name)}-${var.environment}-*/*" ] } @@ -181,16 +217,26 @@ data "aws_iam_policy_document" "rw_boundary" { statement { sid = "CognitoIdp" actions = [ + "cognito-idp:Create*", + "cognito-idp:TagResource", + "cognito-idp:UntagResource", "cognito-idp:List*", "cognito-idp:Describe*", "cognito-idp:Get*", - "cognito-idp:Create*", "cognito-idp:Delete*", - "cognito-idp:TagResource", - "cognito-idp:UntagResource", ] resources = [ - "arn:${data.aws_partition.current.id}:cognito-idp:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:userpool/${var.app_name}-${var.environment}", + "arn:${data.aws_partition.current.id}:cognito-idp:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:userpool/*", + ] + } + + statement { + sid = "CognitoIdpGlobal" + actions = [ + "cognito-idp:DescribeUserPoolDomain", + ] + resources = [ + "*" ] } @@ -200,14 +246,44 @@ data "aws_iam_policy_document" "rw_boundary" { "cognito-identity:List*", "cognito-identity:Describe*", "cognito-identity:Get*", - "cognito-identity:Create*", "cognito-identity:Update*", "cognito-identity:Delete*", + ] + resources = [ + "arn:${data.aws_partition.current.id}:cognito-identity:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:identitypool/*", + ] + } + + statement { + sid = "CognitoIdentityGlobal" + actions = [ + "cognito-identity:CreateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", "cognito-identity:TagResource", "cognito-identity:UntagResource", ] resources = [ - "arn:${data.aws_partition.current.id}:cognito-idp:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:identitypool/${var.app_name}-${var.environment}", + "*" + ] + } + + statement { + actions = [ + "iam:CreateRole", + "iam:CreatePolicy", + "iam:DeleteRole", + "iam:DeletePolicy", + "iam:TagRole", + "iam:TagPolicy", + "iam:GetRole", + "iam:List*", + "iam:GetPolicy*", + "iam:PassRole", + "iam:Attach*", + ] + resources = [ + "arn:${data.aws_partition.current.id}:iam::${data.aws_caller_identity.current.account_id}:role/${local.prefix}-*", + "arn:${data.aws_partition.current.id}:iam::${data.aws_caller_identity.current.account_id}:policy/${local.prefix}-*", ] } } diff --git a/terraform/environment/aws/roles.tf b/terraform/module/iac-roles/roles.tf similarity index 54% rename from terraform/environment/aws/roles.tf rename to terraform/module/iac-roles/roles.tf index 22c395ef..cbcfdbcd 100644 --- a/terraform/environment/aws/roles.tf +++ b/terraform/module/iac-roles/roles.tf @@ -1,13 +1,3 @@ -resource "aws_iam_openid_connect_provider" "oidc" { - url = "https://token.actions.githubusercontent.com" - client_id_list = [ - "sts.${data.aws_partition.current.dns_suffix}" - ] - thumbprint_list = [ - "d89e3bd43d5d909b47a18977aa9d5ce36cee184c" - ] -} - resource "aws_iam_role" "ro" { name = "${var.action_prefix}-${var.app_name}-ro-${var.environment}" assume_role_policy = data.aws_iam_policy_document.ro_assume.json @@ -19,20 +9,26 @@ resource "aws_iam_role" "ro" { data "aws_iam_policy_document" "ro_assume" { statement { - actions = ["sts:AssumeRoleWithWebIdentity"] + actions = [ var.oidc_type == "Federated" ? "sts:AssumeRoleWithWebIdentity" : "sts:AssumeRole" ] principals { - type = "Federated" - identifiers = [aws_iam_openid_connect_provider.oidc.arn] + type = var.oidc_type + identifiers = [var.oidc_arn] } - condition { - test = "StringEquals" - variable = "token.actions.githubusercontent.com:aud" - values = ["sts.${data.aws_partition.current.dns_suffix}"] + dynamic "condition" { + for_each = var.oidc_type == "Federated" ? toset([1]) : toset([]) + content { + test = "StringEquals" + variable = "token.actions.githubusercontent.com:aud" + values = ["sts.${data.aws_partition.current.dns_suffix}"] + } } - condition { - test = "StringEquals" - variable = "token.actions.githubusercontent.com:sub" - values = ["repo:${var.workspace}/${var.repo}:environment:${var.environment}-ro"] + dynamic "condition" { + for_each = var.oidc_type == "Federated" ? toset([1]) : toset([]) + content { + test = "StringEquals" + variable = "token.actions.githubusercontent.com:sub" + values = ["repo:${var.workspace}/${var.repo}:environment:${var.environment}-ro"] + } } } } @@ -63,20 +59,26 @@ resource "aws_iam_role" "rw" { data "aws_iam_policy_document" "rw_assume" { statement { - actions = ["sts:AssumeRoleWithWebIdentity"] + actions = [ var.oidc_type == "Federated" ? "sts:AssumeRoleWithWebIdentity" : "sts:AssumeRole" ] principals { - type = "Federated" - identifiers = [aws_iam_openid_connect_provider.oidc.arn] + type = var.oidc_type + identifiers = [var.oidc_arn] } - condition { - test = "StringEquals" - variable = "token.actions.githubusercontent.com:aud" - values = ["sts.${data.aws_partition.current.dns_suffix}"] + dynamic "condition" { + for_each = var.oidc_type == "Federated" ? toset([1]) : toset([]) + content { + test = "StringEquals" + variable = "token.actions.githubusercontent.com:aud" + values = ["sts.${data.aws_partition.current.dns_suffix}"] + } } - condition { - test = "StringEquals" - variable = "token.actions.githubusercontent.com:sub" - values = ["repo:${var.workspace}/${var.repo}:environment:${var.environment}-rw"] + dynamic "condition" { + for_each = var.oidc_type == "Federated" ? toset([1]) : toset([]) + content { + test = "StringEquals" + variable = "token.actions.githubusercontent.com:sub" + values = ["repo:${var.workspace}/${var.repo}:environment:${var.environment}-rw"] + } } } } diff --git a/terraform/module/oidc/main.tf b/terraform/module/oidc/main.tf new file mode 100644 index 00000000..d3e0c5f2 --- /dev/null +++ b/terraform/module/oidc/main.tf @@ -0,0 +1,15 @@ +data "aws_partition" "current" {} + +resource "aws_iam_openid_connect_provider" "oidc" { + url = "https://token.actions.githubusercontent.com" + client_id_list = [ + "sts.${data.aws_partition.current.dns_suffix}" + ] + thumbprint_list = [ + "d89e3bd43d5d909b47a18977aa9d5ce36cee184c" + ] +} + +output "oidc_arn" { + value = aws_iam_openid_connect_provider.oidc.arn +} diff --git a/terraform/module/state-bucket/main.tf b/terraform/module/state-bucket/main.tf new file mode 100644 index 00000000..654e52a9 --- /dev/null +++ b/terraform/module/state-bucket/main.tf @@ -0,0 +1,8 @@ +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +variable "state_bucket" { + description = "State Bucket to use for deploys" + type = string +} diff --git a/terraform/environment/aws/s3.tf b/terraform/module/state-bucket/s3.tf similarity index 94% rename from terraform/environment/aws/s3.tf rename to terraform/module/state-bucket/s3.tf index 9a2ac0c9..3e6f0de6 100644 --- a/terraform/environment/aws/s3.tf +++ b/terraform/module/state-bucket/s3.tf @@ -1,8 +1,3 @@ -import { - to = aws_s3_bucket.state - id = var.state_bucket -} - resource "aws_s3_bucket" "state" { # checkov:skip=CKV_AWS_18:Access logging is overkill for us # checkov:skip=CKV_AWS_144:Cross-Region replication not required @@ -59,3 +54,8 @@ resource "aws_s3_bucket_versioning" "state" { status = "Enabled" } } + +output "arn" { + description = "ARN of the state bucket" + value = aws_s3_bucket.state.arn +} diff --git a/terraform/environment/wildsea/cognito.tf b/terraform/module/wildsea/cognito.tf similarity index 52% rename from terraform/environment/wildsea/cognito.tf rename to terraform/module/wildsea/cognito.tf index da76baae..7f6f5932 100644 --- a/terraform/environment/wildsea/cognito.tf +++ b/terraform/module/wildsea/cognito.tf @@ -1,5 +1,5 @@ resource "aws_cognito_user_pool" "cognito" { - name = local.prefix + name = var.prefix admin_create_user_config { allow_admin_create_user_only = true @@ -7,6 +7,7 @@ resource "aws_cognito_user_pool" "cognito" { } resource "aws_cognito_identity_provider" "idp" { + for_each = var.saml_metadata_url == "" ? toset([]) : toset([1]) user_pool_id = aws_cognito_user_pool.cognito.id provider_name = "SAML" provider_type = "SAML" @@ -24,20 +25,20 @@ resource "aws_cognito_identity_provider" "idp" { } resource "aws_cognito_user_pool_client" "cognito" { - name = local.prefix + name = var.prefix user_pool_id = aws_cognito_user_pool.cognito.id generate_secret = true - explicit_auth_flows = ["USER_PASSWORD_AUTH", "ALLOW_USER_PASSWORD_AUTH", "ALLOW_USER_SRP_AUTH"] + explicit_auth_flows = ["ALLOW_REFRESH_TOKEN_AUTH", "ALLOW_USER_PASSWORD_AUTH", "ALLOW_USER_SRP_AUTH"] allowed_oauth_flows_user_pool_client = true - callback_urls = ["TODO"] - logout_urls = ["TODO"] + callback_urls = ["https://TODO"] + logout_urls = ["https://TODO"] allowed_oauth_flows = ["code", "implicit"] allowed_oauth_scopes = ["openid"] - supported_identity_providers = [aws_cognito_identity_provider.idp.provider_name] + supported_identity_providers = [ var.saml_metadata_url == "" ? "COGNITO" : aws_cognito_identity_provider.idp[0].provider_name ] } resource "aws_cognito_identity_pool" "cognito" { - identity_pool_name = local.prefix + identity_pool_name = var.prefix allow_unauthenticated_identities = false allow_classic_flow = false @@ -51,12 +52,57 @@ resource "aws_cognito_identity_pool" "cognito" { resource "aws_cognito_identity_pool_roles_attachment" "cognito" { identity_pool_id = aws_cognito_identity_pool.cognito.id roles = { - "authenticated" = "TODO" + "authenticated" = aws_iam_role.cognito.arn } } +resource "aws_iam_role" "cognito" { + name = "${var.prefix}-user" + assume_role_policy = data.aws_iam_policy_document.cognito_assume.json +} + +data "aws_iam_policy_document" "cognito_assume" { + statement { + effect = "Allow" + actions = ["sts:AssumeRoleWithWebIdentity"] + principals { + type = "Federated" + identifiers = ["cognito-identity.${data.aws_partition.current.dns_suffix}"] + } + condition { + test = "StringEquals" + variable = "cognito-identity.${data.aws_partition.current.dns_suffix}:aud" + values = [aws_cognito_identity_pool.cognito.id] + } + condition { + test = "ForAnyValue:StringLike" + variable = "cognito-identity.${data.aws_partition.current.dns_suffix}:amr" + values = ["authenticated"] + } + } +} + +resource "aws_iam_policy" "cognito" { + name = "${var.prefix}-user" + policy = data.aws_iam_policy_document.cognito.json +} + +data "aws_iam_policy_document" "cognito" { + statement { + actions = [ + "cognito-identity:GetCredentialsForIdentity", + ] + resources = ["*"] # checkov:skip=CKV_AWS_107:Has to be wildcard + } +} + +resource "aws_iam_role_policy_attachment" "cognito" { + role = aws_iam_role.cognito.name + policy_arn = aws_iam_policy.cognito.arn +} + resource "aws_cognito_user_pool_domain" "cognito" { - domain = local.prefix + domain = lower(var.prefix) user_pool_id = aws_cognito_user_pool.cognito.id } diff --git a/terraform/module/wildsea/main.tf b/terraform/module/wildsea/main.tf new file mode 100644 index 00000000..a8e354f4 --- /dev/null +++ b/terraform/module/wildsea/main.tf @@ -0,0 +1,13 @@ +data "aws_region" "current" {} +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} + +variable "prefix" { + description = "Resource name prefix" + type = string +} + +variable "saml_metadata_url" { + description = "SAML metadata URL" + type = string +} diff --git a/terraform/environment/wildsea/table.tf b/terraform/module/wildsea/table.tf similarity index 87% rename from terraform/environment/wildsea/table.tf rename to terraform/module/wildsea/table.tf index 1f88ceae..61795358 100644 --- a/terraform/environment/wildsea/table.tf +++ b/terraform/module/wildsea/table.tf @@ -1,7 +1,7 @@ # nosemgrep: aws-dynamodb-table-unencrypted // AWS Key is fine resource "aws_dynamodb_table" "table" { # checkov:skip=CKV_AWS_119:AWS Key is fine - name = local.prefix + name = var.prefix billing_mode = "PAY_PER_REQUEST" deletion_protection_enabled = true hash_key = "PK" @@ -22,6 +22,6 @@ resource "aws_dynamodb_table" "table" { } tags = { - Name = local.prefix + Name = var.prefix } }