From 10e1475bd5df920560474f81d653f76a1ac5a1c5 Mon Sep 17 00:00:00 2001
From: shanice-skylight <150187231+shanice-skylight@users.noreply.github.com>
Date: Mon, 6 Jan 2025 19:32:24 -0500
Subject: [PATCH] Transition Dev environment Infrastructure to ECS (#222)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
.github/workflows/ecs_terraform.yaml | 88 ++++
.github/workflows/terraform_plan.yaml | 76 ++++
.github/workflows/tflint.yaml | 55 +++
.github/workflows/trivy.yaml | 33 ++
.gitignore | 7 +
terraform/implementation/ecs/README.md | 45 ++
terraform/implementation/ecs/_local.tf | 8 +
terraform/implementation/ecs/_variable.tf | 135 ++++++
terraform/implementation/ecs/backend.tf | 41 ++
terraform/implementation/ecs/deploy.sh | 150 +++++++
terraform/implementation/ecs/dev.tfvars | 3 +
terraform/implementation/ecs/main.tf | 207 ++++++++++
terraform/implementation/ecs/output.tf | 25 ++
terraform/implementation/ecs/provider.tf | 3 +
.../ecs/terraform.tfvars.example | 13 +
terraform/implementation/setup/README.md | 44 ++
terraform/implementation/setup/_variable.tf | 23 ++
terraform/implementation/setup/backend.tf | 29 ++
terraform/implementation/setup/main.tf | 44 ++
terraform/implementation/setup/setup.sh | 133 ++++++
.../setup/terraform.tfvars.example | 16 +
terraform/modules/oidc/README.md | 62 +++
terraform/modules/oidc/_data.tf | 388 ++++++++++++++++++
terraform/modules/oidc/_local.tf | 6 +
terraform/modules/oidc/_output.tf | 3 +
terraform/modules/oidc/_variable.tf | 59 +++
terraform/modules/oidc/main.tf | 54 +++
terraform/modules/oidc/provider.tf | 13 +
terraform/modules/tfstate/README.md | 43 ++
terraform/modules/tfstate/_output.tf | 7 +
terraform/modules/tfstate/_variable.tf | 24 ++
terraform/modules/tfstate/main.tf | 45 ++
terraform/modules/tfstate/provider.tf | 9 +
terraform/utilities/tfdocs.sh | 6 +
terraform/utilities/tffmt.sh | 3 +
terraform/utilities/tflint.sh | 6 +
36 files changed, 1906 insertions(+)
create mode 100644 .github/workflows/ecs_terraform.yaml
create mode 100644 .github/workflows/terraform_plan.yaml
create mode 100644 .github/workflows/tflint.yaml
create mode 100644 .github/workflows/trivy.yaml
create mode 100644 terraform/implementation/ecs/README.md
create mode 100644 terraform/implementation/ecs/_local.tf
create mode 100644 terraform/implementation/ecs/_variable.tf
create mode 100644 terraform/implementation/ecs/backend.tf
create mode 100755 terraform/implementation/ecs/deploy.sh
create mode 100644 terraform/implementation/ecs/dev.tfvars
create mode 100644 terraform/implementation/ecs/main.tf
create mode 100644 terraform/implementation/ecs/output.tf
create mode 100644 terraform/implementation/ecs/provider.tf
create mode 100644 terraform/implementation/ecs/terraform.tfvars.example
create mode 100644 terraform/implementation/setup/README.md
create mode 100644 terraform/implementation/setup/_variable.tf
create mode 100644 terraform/implementation/setup/backend.tf
create mode 100644 terraform/implementation/setup/main.tf
create mode 100755 terraform/implementation/setup/setup.sh
create mode 100644 terraform/implementation/setup/terraform.tfvars.example
create mode 100644 terraform/modules/oidc/README.md
create mode 100644 terraform/modules/oidc/_data.tf
create mode 100644 terraform/modules/oidc/_local.tf
create mode 100644 terraform/modules/oidc/_output.tf
create mode 100644 terraform/modules/oidc/_variable.tf
create mode 100644 terraform/modules/oidc/main.tf
create mode 100644 terraform/modules/oidc/provider.tf
create mode 100644 terraform/modules/tfstate/README.md
create mode 100644 terraform/modules/tfstate/_output.tf
create mode 100644 terraform/modules/tfstate/_variable.tf
create mode 100644 terraform/modules/tfstate/main.tf
create mode 100644 terraform/modules/tfstate/provider.tf
create mode 100755 terraform/utilities/tfdocs.sh
create mode 100755 terraform/utilities/tffmt.sh
create mode 100755 terraform/utilities/tflint.sh
diff --git a/.github/workflows/ecs_terraform.yaml b/.github/workflows/ecs_terraform.yaml
new file mode 100644
index 000000000..9a3c3ca9d
--- /dev/null
+++ b/.github/workflows/ecs_terraform.yaml
@@ -0,0 +1,88 @@
+name: Terraform Plan & Terraform Apply
+run-name: Terraform plan & apply ${{ inputs.workspace }} by @${{ github.actor }}
+
+on:
+ merge_group:
+ types:
+ - checks_requested
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+ inputs:
+ workspace:
+ description: "The workspace to terraform against"
+ required: true
+ type: string
+ default: "dev"
+
+concurrency:
+ group: ${{ github.event.inputs.workspace }}-terraform
+ cancel-in-progress: false
+
+permissions:
+ id-token: write
+ contents: read
+
+env:
+ workspace: dev
+
+jobs:
+ terraform:
+ name: Run Terraform
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
+ # this may need to be updated if you change the directory you are working with
+ # ./terraform/implementation/dev || ./terraform/implementation/prod for example
+ # this practice is recommended to keep the terraform code organized while reducing the risk of conflicts
+ working-directory: ./terraform/implementation/ecs
+ steps:
+ - name: Check Out Changes
+ uses: actions/checkout@v4
+
+ - name: Setup Terraform
+ uses: hashicorp/setup-terraform@v3.1.2
+ with:
+ terraform_version: "1.9.8"
+
+ - name: configure aws credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
+ aws-region: ${{ secrets.AWS_REGION }}
+
+ - name: Terraform
+ env:
+ BUCKET: ${{ secrets.TFSTATE_BUCKET }}
+ DYNAMODB_TABLE: ${{ secrets.TFSTATE_DYNAMODB_TABLE }}
+ REGION: ${{ vars.region }}
+ WORKSPACE: ${{ env.workspace }}
+ UMLS_API_KEY: ${{ secrets.UMLS_API_KEY }}
+ ERSD_API_KEY: ${{ secrets.ERSD_API_KEY}}
+ TLS_CERT: ${{ secrets.TLS_CERT}}
+ TLS_KEY: ${{ secrets.TLS_KEY}}
+ shell: bash
+ run: |
+ rm -rf .terraform .terraform.lock.hcl
+ terraform init \
+ -var-file="$WORKSPACE.tfvars" \
+ -backend-config "bucket=$BUCKET" \
+ -backend-config "dynamodb_table=$DYNAMODB_TABLE" \
+ -backend-config "region=$REGION" \
+ || (echo "terraform init failed, exiting..." && exit 1)
+ terraform workspace select "$WORKSPACE"
+ terraform apply -auto-approve -target=aws_acm_certificate.cloudflare_cert \
+ -var-file="$WORKSPACE.tfvars" \
+ -var "umls_api_key=${UMLS_API_KEY}" \
+ -var "ersd_api_key=${ERSD_API_KEY}" \
+ -var "qc_tls_key=${TLS_KEY}" \
+ -var "qc_tls_cert=${TLS_CERT}"
+ terraform plan \
+ -var-file="$WORKSPACE.tfvars" \
+ -var "umls_api_key=${UMLS_API_KEY}" \
+ -var "ersd_api_key=${ERSD_API_KEY}" \
+ -var "qc_tls_key=${TLS_KEY}" \
+ -var "qc_tls_cert=${TLS_CERT}"
+ terraform apply -replace="module.ecs.dockerless_remote_image.dibbs"
diff --git a/.github/workflows/terraform_plan.yaml b/.github/workflows/terraform_plan.yaml
new file mode 100644
index 000000000..27a95ac40
--- /dev/null
+++ b/.github/workflows/terraform_plan.yaml
@@ -0,0 +1,76 @@
+name: Ad-hoc Terraform Plan
+run-name: Terraform plan ${{ inputs.workspace }} by @${{ github.actor }}
+
+on:
+ workflow_dispatch:
+ inputs:
+ workspace:
+ description: "The workspace to terraform against"
+ required: true
+ type: string
+ default: "dev"
+
+concurrency:
+ group: ${{ github.event.inputs.workspace }}-terraform
+ cancel-in-progress: false
+
+permissions:
+ id-token: write
+ contents: read
+
+env:
+ workspace: dev
+
+jobs:
+ terraform:
+ name: Run Terraform
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
+ # this may need to be updated if you change the directory you are working with
+ # ./terraform/implementation/dev || ./terraform/implementation/prod for example
+ # this practice is recommended to keep the terraform code organized while reducing the risk of conflicts
+ working-directory: ./terraform/implementation/ecs
+ steps:
+ - name: Check Out Changes
+ uses: actions/checkout@v4
+
+ - name: Setup Terraform
+ uses: hashicorp/setup-terraform@v3.1.2
+ with:
+ terraform_version: "1.9.8"
+
+ - name: configure aws credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
+ aws-region: ${{ secrets.AWS_REGION }}
+
+ - name: Terraform
+ env:
+ # ACTION: ${{ env.terraform_action }}
+ BUCKET: ${{ secrets.TFSTATE_BUCKET }}
+ DYNAMODB_TABLE: ${{ secrets.TFSTATE_DYNAMODB_TABLE }}
+ REGION: ${{ vars.region }}
+ WORKSPACE: ${{ env.workspace }}
+ UMLS_API_KEY: ${{ secrets.UMLS_API_KEY }}
+ ERSD_API_KEY: ${{ secrets.ERSD_API_KEY}}
+ TLS_CERT: ${{ secrets.TLS_CERT}}
+ TLS_KEY: ${{ secrets.TLS_KEY}}
+ shell: bash
+ run: |
+ rm -rf .terraform .terraform.lock.hcl
+ terraform init \
+ -var-file="$WORKSPACE.tfvars" \
+ -backend-config "bucket=$BUCKET" \
+ -backend-config "dynamodb_table=$DYNAMODB_TABLE" \
+ -backend-config "region=$REGION" \
+ || (echo "terraform init failed, exiting..." && exit 1)
+ terraform workspace select "$WORKSPACE"
+ terraform plan \
+ -var-file="$WORKSPACE.tfvars" \
+ -var "umls_api_key=${UMLS_API_KEY}" \
+ -var "ersd_api_key=${ERSD_API_KEY}" \
+ -var "qc_tls_key=${TLS_KEY}" \
+ -var "qc_tls_cert=${TLS_CERT}"
diff --git a/.github/workflows/tflint.yaml b/.github/workflows/tflint.yaml
new file mode 100644
index 000000000..2ecfa7b11
--- /dev/null
+++ b/.github/workflows/tflint.yaml
@@ -0,0 +1,55 @@
+name: Terraform Linting
+on:
+ pull_request:
+ branches:
+ - "**"
+ push:
+ branches:
+ - main
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ tflint:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ dirs:
+ [
+ terraform/modules/oidc,
+ terraform/modules/tfstate,
+ terraform/implementation/setup,
+ terraform/implementation/ecs,
+ ]
+
+ steps:
+ - uses: actions/checkout@v4
+ name: Checkout source code
+
+ - uses: actions/cache@v4
+ name: Cache plugin dir
+ with:
+ path: ~/.tflint.d/plugins
+ key: ${{ matrix.dirs }}-tflint-${{ hashFiles('.tflint.hcl') }}
+
+ - uses: terraform-linters/setup-tflint@v4
+ name: Setup TFLint
+ with:
+ tflint_version: v0.52.0
+
+ - name: Show version
+ run: tflint --version
+
+ - name: Init TFLint
+ run: tflint --init
+ # If rate limiting becomes an issue, setup a GitHub token and enable it as an environment variable
+ # env:
+ # https://github.com/terraform-linters/tflint/blob/master/docs/user-guide/plugins.md#avoiding-rate-limiting
+ # GITHUB_TOKEN: ${{ github.token }}
+
+ - name: Run TFLint
+ working-directory: ${{ github.workspace }}/${{matrix.dirs}}
+ run: tflint -f compact
diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml
new file mode 100644
index 000000000..5c45cf4c6
--- /dev/null
+++ b/.github/workflows/trivy.yaml
@@ -0,0 +1,33 @@
+name: Trivy Security Scan
+
+on:
+ pull_request:
+ branches:
+ - "**"
+ push:
+ branches:
+ - main
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ trivy:
+ name: trivy
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Run Trivy vulnerability scanner
+ uses: aquasecurity/trivy-action@0.28.0
+ with:
+ scan-type: "fs"
+ scan-ref: "terraform/modules/"
+ scanners: "vuln,secret,config"
+ ignore-unfixed: false
+ exit-code: "1"
+ format: "table"
+ severity: "CRITICAL,HIGH"
diff --git a/.gitignore b/.gitignore
index 7b750b314..00a3aa1e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -84,3 +84,10 @@ build/
.env
.local.env
+
+tmp_remote_image_*
+
+# Local .terraform directories
+**/.terraform/*
+.terraform/modules/ecs
+terraform/implementation/ecs/.terraform/*
\ No newline at end of file
diff --git a/terraform/implementation/ecs/README.md b/terraform/implementation/ecs/README.md
new file mode 100644
index 000000000..55ece9041
--- /dev/null
+++ b/terraform/implementation/ecs/README.md
@@ -0,0 +1,45 @@
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.9.0 |
+| [aws](#requirement\_aws) | =5.56.1 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 5.56.1 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [ecs](#module\_ecs) | CDCgov/dibbs-ecr-viewer/aws | 0.3.0 |
+| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | 5.16.0 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_acm_certificate.this](https://registry.terraform.io/providers/hashicorp/aws/5.56.1/docs/data-sources/acm_certificate) | data source |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [availability\_zones](#input\_availability\_zones) | The availability zones to use | `list(string)` |
[
"us-east-1a",
"us-east-1b",
"us-east-1c"
]
| no |
+| [internal](#input\_internal) | Flag to determine if the several AWS resources are public (intended for external access, public internet) or private (only intended to be accessed within your AWS VPC or avaiable with other means, a transit gateway for example). | `bool` | `true` | no |
+| [owner](#input\_owner) | The owner of the infrastructure | `string` | `"skylight"` | no |
+| [phdi\_version](#input\_phdi\_version) | PHDI container image version | `string` | `"v1.7.6"` | no |
+| [private\_subnets](#input\_private\_subnets) | The private subnets | `list(string)` | [
"176.24.1.0/24",
"176.24.3.0/24"
]
| no |
+| [project](#input\_project) | The project name | `string` | `"dibbs"` | no |
+| [public\_subnets](#input\_public\_subnets) | The public subnets | `list(string)` | [
"176.24.2.0/24",
"176.24.4.0/24"
]
| no |
+| [region](#input\_region) | AWS region | `string` | `"us-east-1"` | no |
+| [vpc\_cidr](#input\_vpc\_cidr) | The CIDR block for the VPC | `string` | `"176.24.0.0/16"` | no |
+
+## Outputs
+
+No outputs.
+
\ No newline at end of file
diff --git a/terraform/implementation/ecs/_local.tf b/terraform/implementation/ecs/_local.tf
new file mode 100644
index 000000000..8e1dc9455
--- /dev/null
+++ b/terraform/implementation/ecs/_local.tf
@@ -0,0 +1,8 @@
+locals {
+ vpc_name = "${var.project}-${var.owner}-${terraform.workspace}"
+ tags = {
+ project = var.project
+ owner = var.owner
+ workspace = terraform.workspace
+ }
+}
diff --git a/terraform/implementation/ecs/_variable.tf b/terraform/implementation/ecs/_variable.tf
new file mode 100644
index 000000000..8f3f82fd6
--- /dev/null
+++ b/terraform/implementation/ecs/_variable.tf
@@ -0,0 +1,135 @@
+variable "availability_zones" {
+ description = "The availability zones to use"
+ type = list(string)
+ default = ["us-east-1a", "us-east-1b", "us-east-1c"]
+}
+
+variable "internal" {
+ description = "Flag to determine if the several AWS resources are public (intended for external access, public internet) or private (only intended to be accessed within your AWS VPC or avaiable with other means, a transit gateway for example)."
+ type = bool
+ default = false
+}
+
+variable "owner" {
+ description = "The owner of the infrastructure"
+ type = string
+ default = "skylight"
+}
+
+# Manually update to set the version you want to run
+# variable "phdi_version" {
+# description = "PHDI container image version"
+# type = string
+# default = "v1.7.6"
+# }
+
+variable "private_subnets" {
+ description = "The private subnets"
+ type = list(string)
+ default = ["176.24.1.0/24", "176.24.3.0/24"]
+}
+
+variable "project" {
+ description = "The project name"
+ type = string
+ default = "qc"
+}
+
+variable "public_subnets" {
+ description = "The public subnets"
+ type = list(string)
+ default = ["176.24.2.0/24", "176.24.4.0/24"]
+}
+
+variable "region" {
+ description = "AWS region"
+ type = string
+ default = "us-east-1"
+}
+
+variable "vpc_cidr" {
+ description = "The CIDR block for the VPC"
+ type = string
+ default = "176.24.0.0/16"
+}
+
+variable "qc_db_name" {
+ type = string
+ description = "The name of the tefca database"
+ default = "queryconnector_db"
+}
+# Note: only lowercase alphanumeric characters and hyphens allowed in "identifier"
+variable "db_identifier" {
+ type = string
+ description = "Name of RDS Instance"
+ default = "qc-db"
+}
+
+variable "db_username" {
+ type = string
+ description = "Username of RDS Instance"
+ default = "qcDbUser"
+}
+
+variable "db_engine_type" {
+ type = string
+ description = "Engine of RDS Instance"
+ default = "postgres"
+}
+
+variable "db_engine_version" {
+ type = string
+ description = "Engine Version of RDS Instance"
+ default = "16.3"
+}
+
+variable "db_instance_class"{
+ type = string
+ description = "The instance type of the RDS instance"
+ default = "db.t3.micro"
+}
+
+
+variable "db_family" {
+ type = string
+ description = "RDS Family"
+ default = "postgres16"
+}
+
+variable "fhir_url" {
+ type = string
+ description = "URL for FHIR server"
+ default = "undefined"
+}
+
+
+variable "cred_manager" {
+ type = string
+ description = "URL for Credentials Manager"
+ default = "undefined"
+}
+
+variable "umls_api_key" {
+ type= string
+ description = "Key for the UMLS API"
+ sensitive = true
+}
+
+variable "ersd_api_key" {
+ type= string
+ description = "Key for the ERSD API"
+ sensitive = true
+}
+
+variable "qc_tls_key" {
+ type= string
+ description = "Key for the Cloudfare cert for domain: queryconnector.dev"
+ sensitive = true
+}
+
+variable "qc_tls_cert" {
+ type= string
+ description = "Certificate importing from Cloudfare: queryconnector.dev"
+ sensitive = true
+}
+
diff --git a/terraform/implementation/ecs/backend.tf b/terraform/implementation/ecs/backend.tf
new file mode 100644
index 000000000..edce4ad5f
--- /dev/null
+++ b/terraform/implementation/ecs/backend.tf
@@ -0,0 +1,41 @@
+
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 5.56.1" # Adjust based on your needs
+ }
+ random = {
+ source = "hashicorp/random"
+ version = "3.6.3"
+ }
+ null = {
+ source = "hashicorp/null"
+ version = "~> 3.2.0"
+ }
+ dockerless = {
+ source = "nullstone-io/dockerless"
+ version = "0.1.1" # Specify a valid version
+ }
+ }
+
+
+ backend "s3" {
+ key = "remote_tfstate"
+ encrypt = true
+ # dynamodb_table
+ # bucket
+ # region
+ }
+}
+
+provider "aws" {
+ region = "us-east-1"
+ default_tags {
+ tags = {
+ owner = var.owner
+ environment = terraform.workspace
+ project = var.project
+ }
+ }
+}
diff --git a/terraform/implementation/ecs/deploy.sh b/terraform/implementation/ecs/deploy.sh
new file mode 100755
index 000000000..d41e8b971
--- /dev/null
+++ b/terraform/implementation/ecs/deploy.sh
@@ -0,0 +1,150 @@
+#!/bin/bash
+
+# Load environment variables from .env file
+if [ -f .env ]; then
+ export $(cat .env | xargs)
+fi
+
+# set default values
+WORKSPACE="${WORKSPACE:-}"
+BUCKET="${BUCKET:-}"
+DYNAMODB_TABLE="${DYNAMODB_TABLE:-}"
+REGION="${REGION:-}"
+TERRAFORM_ROLE="${TERRAFORM_ROLE:-}"
+CI=false
+
+# parse command line arguments
+while [[ $# -gt 0 ]]
+do
+ key="$1"
+
+ case $key in
+ -env|--env|-e)
+ WORKSPACE="$2"
+ shift
+ shift
+ ;;
+ -bucket|--bucket|-b)
+ BUCKET="$2"
+ shift
+ shift
+ ;;
+ -dynamodb-table|--dynamodb-table|-d)
+ DYNAMODB_TABLE="$2"
+ shift
+ shift
+ ;;
+ -region|--region|-r)
+ REGION="$2"
+ shift
+ shift
+ ;;
+ -terraform-role|--terraform-role)
+ TERRAFORM_ROLE="$2"
+ shift
+ shift
+ ;;
+ -ci|--ci)
+ CI=true
+ shift
+ ;;
+ -h|--help)
+ echo "Usage: ./deploy.sh [OPTIONS]"
+ echo "Options:"
+ echo " -e, --env | Set the environment (e.g., production, staging) [REQUIRED]"
+ echo " -b, --bucket | Set the bucket name [REQUIRED]"
+ echo " -d, --dynamodb-table | Set the DynamoDB table name [REQUIRED]"
+ echo " -r, --region | Set the AWS region [REQUIRED]"
+ echo " -ci, --ci | Skip creating files and assume all arguments have values"
+ echo " -h, --help | Show help"
+ exit 0
+ ;;
+ *)
+ echo "Invalid argument: $1"
+ exit 1
+ ;;
+ esac
+done
+
+if ! command -v terraform &> /dev/null; then
+ echo "Terraform is not installed. Please install Terraform and try again."
+ exit 1
+fi
+
+if [ -z "$WORKSPACE" ] || [ -z "$BUCKET" ] || [ -z "$DYNAMODB_TABLE" ] || [ -z "$REGION" ]; then
+ echo "Missing required arguments. Please provide all the required arguments."
+ echo "WORKSPACE: $WORKSPACE"
+ echo "BUCKET: $BUCKET"
+ echo "DYNAMODB_TABLE: $DYNAMODB_TABLE"
+ echo "REGION: $REGION"
+ ./deploy.sh -h
+ exit 1
+fi
+
+if [ "$CI" = false ]; then
+ if [ ! -f "$WORKSPACE.tfvars" ]; then
+ echo "Creating $WORKSPACE.tfvars"
+ touch "$WORKSPACE.tfvars"
+ fi
+
+ if ! grep -q "owner" "$WORKSPACE.tfvars"; then
+ read -p "Who is the owner of this infrastructure? ( default=skylight ): " owner_choice
+ owner_choice=${owner_choice:-skylight}
+ echo "owner = \"$owner_choice\"" >> "$WORKSPACE.tfvars"
+ fi
+
+ if ! grep -q "project" "$WORKSPACE.tfvars"; then
+ read -p "What is this project called? ( default=dibbs ): " project_choice
+ project_choice=${project_choice:-dibbs}
+ echo "project = \"$project_choice\"" >> "$WORKSPACE.tfvars"
+ fi
+
+ if ! grep -q "region" "$WORKSPACE.tfvars"; then
+ read -p "What aws region are you setting up in? ( default=us-east-1 ): " region_choice
+ region_choice=${region_choice:-us-east-1}
+ echo "region = \"$region_choice\"" >> "$WORKSPACE.tfvars"
+ fi
+fi
+
+echo "Running Terraform with the following variables:"
+echo "Environment: $WORKSPACE"
+echo "Terraform Workspace: $WORKSPACE"
+echo "Bucket: $BUCKET"
+echo "DynamoDB Table: $DYNAMODB_TABLE"
+echo "Region: $REGION"
+cat "$WORKSPACE.tfvars"
+echo ""
+
+terraform init \
+ -var-file="$WORKSPACE.tfvars" \
+ -backend-config "bucket=$BUCKET" \
+ -backend-config "dynamodb_table=$DYNAMODB_TABLE" \
+ -backend-config "region=$REGION" \
+ || (echo "terraform init failed, exiting..." && exit 1)
+
+
+# Check if workspace exists
+if terraform workspace list | grep -q "$WORKSPACE"; then
+ echo "Selecting $WORKSPACE terraform workspace"
+ terraform workspace select "$WORKSPACE"
+else
+ if [ "$CI" = false ]; then
+ read -p "Workspace '$WORKSPACE' does not exist. Do you want to create it? (y/n): " choice
+ if [[ $choice =~ ^[Yy]$ ]]; then
+ echo "Creating '$WORKSPACE' terraform workspace"
+ terraform workspace new "$WORKSPACE"
+ else
+ echo "Workspace creation cancelled."
+ exit 1
+ fi
+ else
+ echo "Creating '$WORKSPACE' terraform workspace"
+ terraform workspace new "$WORKSPACE"
+ fi
+fi
+
+if [ "$CI" = false ]; then
+ terraform apply -var-file="$WORKSPACE.tfvars"
+else
+ terraform apply -auto-approve -var-file="$WORKSPACE.tfvars"
+fi
diff --git a/terraform/implementation/ecs/dev.tfvars b/terraform/implementation/ecs/dev.tfvars
new file mode 100644
index 000000000..5c9893de1
--- /dev/null
+++ b/terraform/implementation/ecs/dev.tfvars
@@ -0,0 +1,3 @@
+owner = "skylight"
+project = "qc"
+region = "us-east-1"
diff --git a/terraform/implementation/ecs/main.tf b/terraform/implementation/ecs/main.tf
new file mode 100644
index 000000000..a4944302a
--- /dev/null
+++ b/terraform/implementation/ecs/main.tf
@@ -0,0 +1,207 @@
+resource "aws_acm_certificate" "cloudflare_cert" {
+ private_key = var.qc_tls_key # Private key from Cloudflare
+ certificate_body = var.qc_tls_cert # Public cert from Cloudflare
+}
+
+data "aws_caller_identity" "current" {}
+
+module "vpc" {
+ source = "terraform-aws-modules/vpc/aws"
+ version = "5.16.0"
+
+ name = local.vpc_name
+ cidr = var.vpc_cidr
+ azs = var.availability_zones
+ private_subnets = var.private_subnets
+ public_subnets = var.public_subnets
+ # if internal is true, then the VPC will not have a NAT or internet gateway
+ enable_nat_gateway = var.internal ? false : true
+ single_nat_gateway = var.internal ? false : true
+ create_igw = var.internal ? false : true
+ tags = local.tags
+}
+
+module "ecs" {
+ source = "CDCgov/dibbs-ecr-viewer/aws"
+ version = "0.3.0"
+
+ public_subnet_ids = flatten(module.vpc.public_subnets)
+ private_subnet_ids = flatten(module.vpc.private_subnets)
+ vpc_id = module.vpc.vpc_id
+ region = var.region
+
+ owner = var.owner
+ project = var.project
+ tags = local.tags
+
+
+ phdi_version = "main"
+
+ service_data = {
+ query-connector = {
+ short_name = "qc",
+ fargate_cpu = 512,
+ fargate_memory = 1024,
+ min_capacity = 1,
+ max_capacity = 5,
+ app_repo = "ghcr.io/cdcgov/dibbs-query-connector",
+ app_image = "${terraform.workspace}-query-connector",
+ app_version = "main",
+ container_port = 3000,
+ host_port = 3000,
+ public = true,
+ registry_url = "${data.aws_caller_identity.current.account_id}.dkr.ecr.${var.region}.amazonaws.com",
+ env_vars = [
+ {
+ name = "AWS_REGION",
+ value = var.region
+ },
+ {
+ name = "HOSTNAME",
+ value = "0.0.0.0"
+ },
+ {
+ name = "fhir_url"
+ value = var.fhir_url
+ },
+ {
+ name = "cred_manager"
+ value = var.cred_manager
+ },
+ {
+ name = "DATABASE_URL"
+ value = "postgresql://${aws_db_instance.qc_db.username}:${aws_db_instance.qc_db.password}@${aws_db_instance.qc_db.endpoint}/${aws_db_instance.qc_db.db_name}"
+ },
+ {
+ name = "FLYWAY_URL"
+ value = "jdbc:postgresql://${aws_db_instance.qc_db.endpoint}/${aws_db_instance.qc_db.db_name}"
+
+ },
+ {
+ name = "FLYWAY_PASSWORD"
+ value = aws_db_instance.qc_db.password
+ },
+ {
+ name = "FLYWAY_USER"
+ value = aws_db_instance.qc_db.username
+ },
+ {
+ name = "UMLS_API_KEY"
+ value = var.umls_api_key
+ },
+ {
+ name = "ERSD_API_KEY"
+ value = var.ersd_api_key
+ },
+ ]
+ }
+ }
+
+
+ # If intent is to pull from the phdi GHCR, set disable_ecr to true (default is false)
+ # disable_ecr = true
+
+ # If the intent is to make the ecr-viewer availabble on the public internet, set internal to false (default is true)
+ # This requires an internet gateway to be present in the VPC.
+ internal = var.internal
+
+ # If the intent is to enable https and port 443, pass the arn of the cert in AWS certificate manager. This cert will be applied to the load balancer. (default is "")
+ certificate_arn = aws_acm_certificate.cloudflare_cert.arn
+
+ # If the intent is to disable authentication, set ecr_viewer_app_env to "test" (default is "prod")
+ # ecr_viewer_app_env = "test"
+
+ # To disable autoscaling, set enable_autoscaling to false (default is true)
+ enable_autoscaling = false
+
+ # If intent is to use a metadata database for polutating the ecr-viewer library, setup the database data object to connect to the database (supported databases are postgres and sqlserver)
+ # Postgresql database example
+ # postgres_database_data = {
+ # non_integrated_viewer = "true"
+ # metadata_database_type = "postgres"
+ # metadata_database_schema = "core" # (core or extended)
+ # secrets_manager_postgres_database_url_name = "prod/testSecret"
+ # }
+ # SqlServer database example
+ # sqlserver_database_data = {
+ # non_integrated_viewer = "true"
+ # metadata_database_type = "sqlserver"
+ # metadata_database_schema = "core" # (core or extended)
+ # secrets_manager_sqlserver_user_name = "prod/testSecret"
+ # secrets_manager_sqlserver_password_name = "prod/testSecret"
+ # secrets_manager_sqlserver_host_name = "prod/testSecret"
+ # }
+}
+
+
+resource "aws_db_instance" "qc_db" {
+ allocated_storage = "10"
+ db_name = var.qc_db_name
+ identifier = var.db_identifier
+ engine = var.db_engine_type
+ engine_version = var.db_engine_version
+ enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
+ instance_class = var.db_instance_class
+ username = var.db_username
+ password = random_password.setup_rds_password.result
+ parameter_group_name = aws_db_parameter_group.this.name
+ skip_final_snapshot = true
+ db_subnet_group_name = aws_db_subnet_group.this.name
+ vpc_security_group_ids = [aws_security_group.db_sg.id]
+}
+
+# Create a DB subnet group
+resource "aws_db_subnet_group" "this" {
+ name = "${var.db_identifier}-subnet-group"
+ subnet_ids = module.vpc.private_subnets
+
+}
+
+# Create a parameter group to configure Postgres RDS parameters
+resource "aws_db_parameter_group" "this" {
+ name = "${var.db_identifier}-pg"
+ family = var.db_family
+
+ parameter {
+ name = "log_connections"
+ value = "1"
+ }
+ parameter {
+ name = "rds.force_ssl"
+ value = "0"
+ }
+}
+
+resource "aws_security_group" "db_sg" {
+ vpc_id = module.vpc.vpc_id
+
+ # Allow inbound traffic on port 5432 for PostgreSQL from within the VPC
+ ingress {
+ from_port = 5432
+ to_port = 5432
+ protocol = "tcp"
+ cidr_blocks = ["176.24.0.0/16"]
+ }
+
+ # Allow all outbound traffic
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+
+ tags = {
+ Name = "${var.db_identifier}-security-group"
+ }
+}
+
+# TODO: Update for Production to AWS Secrets Manager
+# This resource's attribute(s) default value is true
+resource "random_password" "setup_rds_password" {
+ length = 13 #update as needed
+
+ # Character set that excludes problematic characters like quotes, backslashes, etc.
+ override_special = "[]{}"
+}
+
diff --git a/terraform/implementation/ecs/output.tf b/terraform/implementation/ecs/output.tf
new file mode 100644
index 000000000..4b5235013
--- /dev/null
+++ b/terraform/implementation/ecs/output.tf
@@ -0,0 +1,25 @@
+# output "qc_db_role_arn" {
+# value = aws_iam_role.db_role_for_tefca_viewer.arn
+# }
+
+
+
+output "qc_db_connection_string" {
+ value = "postgresql://${aws_db_instance.qc_db.username}:${aws_db_instance.qc_db.password}@${aws_db_instance.qc_db.endpoint}/${aws_db_instance.qc_db.db_name}"
+ sensitive = true
+}
+
+output "qc_jdbc_db_url" {
+ value = "jdbc:postgresql://${aws_db_instance.qc_db.endpoint}/${aws_db_instance.qc_db.db_name}"
+ sensitive = true
+}
+
+output "qc_jdbc_db_user" {
+ value = aws_db_instance.qc_db.username
+ sensitive = true
+}
+
+output "qc_jdbc_db_password" {
+ value = aws_db_instance.qc_db.password
+ sensitive = true
+}
\ No newline at end of file
diff --git a/terraform/implementation/ecs/provider.tf b/terraform/implementation/ecs/provider.tf
new file mode 100644
index 000000000..77fe6006a
--- /dev/null
+++ b/terraform/implementation/ecs/provider.tf
@@ -0,0 +1,3 @@
+terraform {
+ required_version = "~> 1.9.0"
+}
diff --git a/terraform/implementation/ecs/terraform.tfvars.example b/terraform/implementation/ecs/terraform.tfvars.example
new file mode 100644
index 000000000..33e03d06f
--- /dev/null
+++ b/terraform/implementation/ecs/terraform.tfvars.example
@@ -0,0 +1,13 @@
+/*
+* Terraform variables file, copy this file to .tfvars ( dev.tfvars, prod.tfvars ) and fill in the values you require.
+* Remove the comments and replace the values with the desired values for your environment.
+* This tfvars file is used to configure the environment for the Terraform implementation.
+* required:
+* - owner: The owner of the environment
+* - project: The name of the project
+* - region: The region to launch the AWS resources in
+*/
+
+owner = "John"
+project = "JurassicPark"
+region = "us-east-1"
diff --git a/terraform/implementation/setup/README.md b/terraform/implementation/setup/README.md
new file mode 100644
index 000000000..98e607471
--- /dev/null
+++ b/terraform/implementation/setup/README.md
@@ -0,0 +1,44 @@
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.9.0 |
+| [aws](#requirement\_aws) | ~> 5.56.1 |
+| [local](#requirement\_local) | ~> 2.5.0 |
+| [random](#requirement\_random) | ~> 3.6.3 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [local](#provider\_local) | 2.5.2 |
+| [random](#provider\_random) | 3.6.3 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [oidc](#module\_oidc) | ../../modules/oidc | n/a |
+| [tfstate](#module\_tfstate) | ../../modules/tfstate | n/a |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [local_file.setup_env](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
+| [random_string.setup](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [oidc\_github\_repo](#input\_oidc\_github\_repo) | The GitHub repository for OIDC | `string` | `""` | no |
+| [owner](#input\_owner) | The owner of the project | `string` | `"skylight"` | no |
+| [project](#input\_project) | The name of the project | `string` | `"dibbs"` | no |
+| [region](#input\_region) | The AWS region where resources are created | `string` | `"us-east-1"` | no |
+
+## Outputs
+
+No outputs.
+
\ No newline at end of file
diff --git a/terraform/implementation/setup/_variable.tf b/terraform/implementation/setup/_variable.tf
new file mode 100644
index 000000000..48d81c02d
--- /dev/null
+++ b/terraform/implementation/setup/_variable.tf
@@ -0,0 +1,23 @@
+variable "oidc_github_repo" {
+ description = "The GitHub repository for OIDC"
+ type = string
+ default = ""
+}
+
+variable "owner" {
+ description = "The owner of the project"
+ type = string
+ default = "skylight"
+}
+
+variable "project" {
+ description = "The name of the project"
+ type = string
+ default = "dibbs"
+}
+
+variable "region" {
+ type = string
+ description = "The AWS region where resources are created"
+ default = "us-east-1"
+}
diff --git a/terraform/implementation/setup/backend.tf b/terraform/implementation/setup/backend.tf
new file mode 100644
index 000000000..3c14f160e
--- /dev/null
+++ b/terraform/implementation/setup/backend.tf
@@ -0,0 +1,29 @@
+terraform {
+ backend "s3" {}
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 5.56.1"
+ }
+ random = {
+ source = "hashicorp/random"
+ version = "~> 3.6.3"
+ }
+ local = {
+ source = "hashicorp/local"
+ version = "~> 2.5.0"
+ }
+ }
+ required_version = "~> 1.9.0"
+}
+
+provider "aws" {
+ region = "us-east-1"
+ default_tags {
+ tags = {
+ owner = "skylight"
+ environment = "tfstate"
+ project = "dibbs"
+ }
+ }
+}
diff --git a/terraform/implementation/setup/main.tf b/terraform/implementation/setup/main.tf
new file mode 100644
index 000000000..bce7cdfb3
--- /dev/null
+++ b/terraform/implementation/setup/main.tf
@@ -0,0 +1,44 @@
+resource "random_string" "setup" {
+ length = 8
+ special = false
+ upper = false
+}
+
+module "tfstate" {
+ source = "../../modules/tfstate"
+ identifier = random_string.setup.result
+ owner = var.owner
+ project = var.project
+}
+
+# GitHub OIDC for prod
+module "oidc" {
+ source = "../../modules/oidc"
+
+ # The github repo that will be used for OIDC
+ oidc_github_repo = var.oidc_github_repo
+
+ # These variables must match the values that you'll be using for your ECS module call in the /ecs module
+ region = var.region
+ owner = var.owner
+ project = var.project
+
+ # This variable must match the name of the terraform workspace that you'll be using for your ECS module call in the /ecs module
+ workspace = "dev"
+
+ # state_bucket_arn = module.tfstate.aws_s3_bucket.tfstate.arn
+ state_bucket_arn = module.tfstate.state_bucket.arn
+ # dynamodb_table_arn = aws_dynamodb_table.tfstate_lock.arn
+ dynamodb_table_arn = module.tfstate.dynamodb_table.arn
+}
+
+resource "local_file" "setup_env" {
+ content = <<-EOT
+ WORKSPACE="${terraform.workspace}"
+ BUCKET="${module.tfstate.state_bucket.bucket}"
+ DYNAMODB_TABLE="${module.tfstate.dynamodb_table.arn}"
+ REGION="${var.region}"
+ TERRAFORM_ROLE="${module.oidc.role.arn}"
+ EOT
+ filename = ".env"
+}
\ No newline at end of file
diff --git a/terraform/implementation/setup/setup.sh b/terraform/implementation/setup/setup.sh
new file mode 100755
index 000000000..bff48acf4
--- /dev/null
+++ b/terraform/implementation/setup/setup.sh
@@ -0,0 +1,133 @@
+#!/bin/bash
+
+WORKSPACE=tfstate
+
+# write a function with aruments to set the backend
+set_backend () {
+ region=$(grep "region" "$WORKSPACE.tfvars" | cut -d'=' -f2 | tr -d ' "')
+ owner=$(grep "owner" "$WORKSPACE.tfvars" | cut -d'=' -f2 | tr -d ' "')
+ project=$(grep "project" "$WORKSPACE.tfvars" | cut -d'=' -f2 | tr -d ' "')
+cat > backend.tf < /dev/null; then
+ echo "Terraform is not installed. Please install Terraform."
+ exit 1
+fi
+
+if [ ! -f "$WORKSPACE.tfvars" ]; then
+ echo "Creating $WORKSPACE.tfvars"
+ touch "$WORKSPACE.tfvars"
+fi
+
+if ! grep -q "owner" "$WORKSPACE.tfvars"; then
+ read -p "Who is the owner of this infrastructure? ( default=skylight ): " owner_choice
+ owner_choice=${owner_choice:-skylight}
+ echo "owner = \"$owner_choice\"" >> "$WORKSPACE.tfvars"
+fi
+
+if ! grep -q "project" "$WORKSPACE.tfvars"; then
+ read -p "What is this project called? ( default=dibbs ): " project_choice
+ project_choice=${project_choice:-dibbs}
+ echo "project = \"$project_choice\"" >> "$WORKSPACE.tfvars"
+fi
+
+if ! grep -q "region" "$WORKSPACE.tfvars"; then
+ read -p "What aws region are you setting up in? ( default=us-east-1 ): " region_choice
+ region_choice=${region_choice:-us-east-1}
+ echo "region = \"$region_choice\"" >> "$WORKSPACE.tfvars"
+fi
+
+if ! grep -q "oidc_github_repo" "$WORKSPACE.tfvars"; then
+ read -p "Do you want to setup a GitHub OIDC role? (y/n): " github_choice
+ if [[ "$github_choice" =~ ^[Yy]$ ]]; then
+ read -p "What is the organization/repo value for assume role? ( default=\"\" ): " repo_choice
+ repo_choice=${repo_choice:-""}
+ echo "oidc_github_repo = \"$repo_choice\"" >> "$WORKSPACE.tfvars"
+ fi
+fi
+
+echo "Running Terraform with the following variables:"
+cat "$WORKSPACE.tfvars"
+
+if [ "$USE_S3_BACKEND" == "true" ]; then
+ terraform init \
+ -var-file="$WORKSPACE.tfvars" \
+ -backend-config "encrypt=true" \
+ -backend-config "key=setup_tfstate" \
+ -backend-config "bucket=$BUCKET" \
+ -backend-config "dynamodb_table=$DYNAMODB_TABLE" \
+ -backend-config "region=$REGION"
+else
+ set_backend "local"
+ terraform init -var-file="$WORKSPACE.tfvars"
+fi
+
+# Check if workspace exists
+if terraform workspace list | grep -q "$WORKSPACE"; then
+ echo "Selecting $WORKSPACE terraform workspace"
+ terraform workspace select "$WORKSPACE"
+else
+ echo "Creating '$WORKSPACE' terraform workspace"
+ terraform workspace new "$WORKSPACE"
+fi
+
+terraform apply -var-file="$WORKSPACE.tfvars"
+
+if [ "$USE_S3_BACKEND" == "false" ]; then
+ echo "Setting up your s3 terraform backend"
+ if [ -f .env ]; then
+ export $(cat .env | xargs)
+ fi
+ set_backend "s3"
+ terraform init \
+ -var-file="$WORKSPACE.tfvars" \
+ -migrate-state \
+ -backend-config "encrypt=true" \
+ -backend-config "key=setup_tfstate" \
+ -backend-config "bucket=$BUCKET" \
+ -backend-config "dynamodb_table=$DYNAMODB_TABLE" \
+ -backend-config "region=$REGION" \
+ || (echo "terraform init failed, exiting..." && exit 1)
+fi
diff --git a/terraform/implementation/setup/terraform.tfvars.example b/terraform/implementation/setup/terraform.tfvars.example
new file mode 100644
index 000000000..18272d9bb
--- /dev/null
+++ b/terraform/implementation/setup/terraform.tfvars.example
@@ -0,0 +1,16 @@
+/*
+* Terraform variables file, copy this file to .tfvars ( dev.tfvars, prod.tfvars ) and fill in the values you require.
+* Remove the comments and replace the values with the desired values for your environment.
+* This tfvars file is used to configure the environment for the Terraform implementation.
+* required:
+* - owner: The owner of the environment
+* - project: The name of the project
+* - region: The region to launch the AWS resources in
+* optional:
+* - oidc_github_repo: The GitHub repository to use for OIDC authentication
+*/
+
+owner = "John"
+project = "JurassicPark"
+region = "us-east-1"
+oidc_github_repo = "CDCgov/dibbs-aws"
\ No newline at end of file
diff --git a/terraform/modules/oidc/README.md b/terraform/modules/oidc/README.md
new file mode 100644
index 000000000..4ad8da104
--- /dev/null
+++ b/terraform/modules/oidc/README.md
@@ -0,0 +1,62 @@
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.9.0 |
+| [aws](#requirement\_aws) | ~> 5.56.1 |
+| [random](#requirement\_random) | ~> 3.6.3 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | ~> 5.56.1 |
+| [random](#provider\_random) | ~> 3.6.3 |
+
+## Modules
+
+No modules.
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_iam_policy.request_tags_create_actions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.resource_tags_delete_actions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.resource_tags_update_actions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.scoped_one](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.scoped_two](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.storage](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.wildcard](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_role.github](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [random_string.oidc](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
+| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
+| [aws_iam_policy_document.github_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.request_tags_create_actions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.resource_tags_delete_actions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.resource_tags_update_actions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.scoped_one](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.scoped_two](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.storage](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.wildcard](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [dynamodb\_table\_arn](#input\_dynamodb\_table\_arn) | The ARN of the DynamoDB table for state | `string` | `""` | no |
+| [oidc\_github\_repo](#input\_oidc\_github\_repo) | The GitHub repository for OIDC | `string` | `""` | no |
+| [owner](#input\_owner) | The owner of the project | `string` | `"skylight"` | no |
+| [project](#input\_project) | The name of the project | `string` | `"dibbs"` | no |
+| [region](#input\_region) | The AWS region where resources are created | `string` | `""` | no |
+| [state\_bucket\_arn](#input\_state\_bucket\_arn) | The ARN of the S3 bucket for state | `string` | `""` | no |
+| [vpc\_id](#input\_vpc\_id) | ID of the VPC | `string` | `""` | no |
+| [workspace](#input\_workspace) | terraform workspace that OIDC will have permissions to | `string` | `""` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [role](#output\_role) | n/a |
+
\ No newline at end of file
diff --git a/terraform/modules/oidc/_data.tf b/terraform/modules/oidc/_data.tf
new file mode 100644
index 000000000..87bd1ca0b
--- /dev/null
+++ b/terraform/modules/oidc/_data.tf
@@ -0,0 +1,388 @@
+
+data "aws_caller_identity" "current" {}
+
+# # create a role that can be assumed to pull and push docker images from
+data "aws_iam_policy_document" "github_assume_role" {
+ statement {
+ principals {
+ type = "Federated"
+ identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/token.actions.githubusercontent.com"]
+ }
+ actions = [
+ "sts:AssumeRoleWithWebIdentity"
+ ]
+ condition {
+ test = "StringEquals"
+ variable = "token.actions.githubusercontent.com:aud"
+ values = ["sts.amazonaws.com", ]
+ }
+ condition {
+ test = "StringLike"
+ variable = "token.actions.githubusercontent.com:sub"
+ values = [
+ "repo:${var.oidc_github_repo}:*",
+ ]
+ }
+ }
+}
+
+# tfstate and storage policy
+# trivy:ignore:AVD-AWS-0057
+data "aws_iam_policy_document" "storage" {
+ statement {
+ actions = [
+ "dynamodb:DeleteItem",
+ "dynamodb:GetItem",
+ "dynamodb:PutItem",
+ "s3:*",
+ ]
+ resources = [
+ "arn:aws:s3:::*",
+ var.state_bucket_arn,
+ "${var.state_bucket_arn}/*",
+ var.dynamodb_table_arn,
+ ]
+ }
+}
+
+# Wildcard policy
+# trivy:ignore:AVD-AWS-0057
+data "aws_iam_policy_document" "wildcard" {
+ statement {
+ actions = [
+ "acm:ListCertificates",
+ "acm:DescribeCertificate",
+ "acm:GetCertificate",
+ "acm:ListTagsForCertificate",
+ "application-autoscaling:DescribeScalableTargets",
+ "application-autoscaling:DescribeScalingPolicies",
+ "application-autoscaling:ListTagsForResource",
+ "ec2:DescribeAddresses",
+ "ec2:DescribeVpcEndpoints",
+ "ec2:DescribePrefixLists",
+ "ec2:DescribeAddressesAttribute",
+ "ec2:DescribeFlowLogs",
+ "ec2:DescribeInternetGateways",
+ "ec2:DescribeNatGateways",
+ "ec2:DescribeNetworkAcls",
+ "ec2:DescribeNetworkInterfaces",
+ "ec2:DescribeRouteTables",
+ "ec2:DescribeSecurityGroupRules",
+ "ec2:DescribeSecurityGroups",
+ "ec2:DescribeSubnets",
+ "ec2:DescribeVpcs",
+ "ecr:GetAuthorizationToken",
+ "ecs:DeregisterTaskDefinition",
+ "ecs:DescribeTaskDefinition",
+ "elasticloadbalancing:DescribeListeners",
+ "elasticloadbalancing:DescribeLoadBalancerAttributes",
+ "elasticloadbalancing:DescribeLoadBalancers",
+ "elasticloadbalancing:DescribeRules",
+ "elasticloadbalancing:DescribeTags",
+ "elasticloadbalancing:DescribeTargetGroupAttributes",
+ "elasticloadbalancing:DescribeTargetGroups",
+ "iam:ListPolicies",
+ "route53:CreateHostedZone",
+ "secretsmanager:GetSecretValue",
+ ]
+ resources = [
+ "*"
+ ]
+ }
+}
+
+# Scoped policies
+# trivy:ignore:AVD-AWS-0057
+data "aws_iam_policy_document" "scoped_one" {
+ statement {
+ actions = [
+ "appmesh:DescribeMesh",
+ "appmesh:DescribeVirtualNode",
+ "appmesh:ListTagsForResource",
+ "ec2:DescribeVpcAttribute",
+ "ecr:DescribeRepositories",
+ "ecr:ListTagsForResource",
+ "ecs:DescribeClusters",
+ "ecs:DescribeServices",
+ "iam:GetPolicy",
+ "iam:GetPolicyVersion",
+ "iam:GetRole",
+ "iam:ListAttachedRolePolicies",
+ "iam:ListInstanceProfilesForRole",
+ "iam:ListPolicyVersions",
+ "iam:ListRolePolicies",
+ "logs:DescribeLogGroups",
+ "logs:ListAttachedRolePolicies",
+ "logs:ListTagsLogGroup",
+ "logs:ListTagsForResource",
+ "servicediscovery:GetNamespace",
+ "servicediscovery:GetOperation",
+ "servicediscovery:ListTagsForResource",
+ ]
+ resources = [
+ "arn:aws:appmesh:${var.region}:${data.aws_caller_identity.current.account_id}:mesh/${local.project_owner_workspace}",
+ "arn:aws:appmesh:${var.region}:${data.aws_caller_identity.current.account_id}:mesh/${local.project_owner_workspace}/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:*/*",
+ "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:*",
+ "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:*/*",
+ "arn:aws:ecr:${var.region}:${data.aws_caller_identity.current.account_id}:*",
+ "arn:aws:ecr:${var.region}:${data.aws_caller_identity.current.account_id}:repository/*",
+ "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:*",
+ "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:*/*",
+ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.project_owner_workspace}*",
+ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/${local.project_owner_workspace}*",
+ "arn:aws:iam::aws:policy/service-role/*",
+ "arn:aws:logs:${var.region}:${data.aws_caller_identity.current.account_id}:*",
+ "arn:aws:servicediscovery:${var.region}:${data.aws_caller_identity.current.account_id}:*",
+ "arn:aws:servicediscovery:${var.region}:${data.aws_caller_identity.current.account_id}:*/*",
+ ]
+ }
+}
+
+# Scoped policies
+data "aws_iam_policy_document" "scoped_two" {
+ statement {
+ actions = [
+ "application-autoscaling:DeleteScalingPolicy",
+ "application-autoscaling:DeregisterScalableTarget",
+ "application-autoscaling:PutScalingPolicy",
+ "application-autoscaling:RegisterScalableTarget",
+ "application-autoscaling:TagResource",
+ "ec2:createVpcEndpoint",
+ "ec2:CreateFlowLogs",
+ "ec2:CreateNatGateway",
+ "ec2:CreateNetworkAclEntry",
+ "ec2:CreateRoute",
+ "ec2:CreateRouteTable",
+ "ec2:CreateSecurityGroup",
+ "ec2:CreateSubnet",
+ "ec2:DeleteNetworkAclEntry",
+ "iam:PassRole",
+ ]
+ resources = [
+ "arn:aws:application-autoscaling:${var.region}:${data.aws_caller_identity.current.account_id}:scalable-target/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:vpc/${local.vpc_id}",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:vpc-flow-log/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:subnet/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:route-table/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:security-group/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:network-acl/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:elastic-ip/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:natgateway/*",
+ "arn:aws:ecr:${var.region}:${data.aws_caller_identity.current.account_id}:repository/*",
+ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.project_owner_workspace}*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:vpc-endpoint/*",
+ ]
+ }
+}
+
+# Request tags/Scoped policy
+data "aws_iam_policy_document" "request_tags_create_actions" {
+ statement {
+ actions = [
+ "appmesh:CreateMesh",
+ "ec2:createVpcEndpoint",
+ "appmesh:CreateVirtualNode",
+ "appmesh:DeleteMesh",
+ "appmesh:DeleteVirtualNode",
+ "appmesh:TagResource",
+ "ec2:AllocateAddress",
+ "ec2:CreateInternetGateway",
+ "ec2:CreateRoute",
+ "ec2:CreateTags",
+ "ec2:CreateVpc",
+ "ecs:CreateCluster",
+ "ecs:CreateService",
+ "ecr:CreateRepository",
+ "elasticloadbalancing:CreateListener",
+ "elasticloadbalancing:CreateLoadBalancer",
+ "elasticloadbalancing:CreateRule",
+ "elasticloadbalancing:CreateTargetGroup",
+ "iam:CreatePolicy",
+ "iam:CreateRole",
+ "logs:CreateLogDelivery",
+ "logs:CreateLogGroup",
+ "logs:TagResource",
+ "servicediscovery:CreatePrivateDnsNamespace",
+ ]
+ resources = [
+ "arn:aws:appmesh:${var.region}:${data.aws_caller_identity.current.account_id}:mesh/${local.project_owner_workspace}",
+ "arn:aws:appmesh:${var.region}:${data.aws_caller_identity.current.account_id}:mesh/${local.project_owner_workspace}/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:vpc/${local.vpc_id}",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:vpc-endpoint/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:vpc-flow-log/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:subnet/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:route-table/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:security-group/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:network-acl/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:internet-gateway/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:natgateway/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:elastic-ip/*",
+ "arn:aws:ecr:${var.region}:${data.aws_caller_identity.current.account_id}:repository/*",
+ "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:service/${local.project_owner_workspace}/*",
+ "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:cluster/${local.project_owner_workspace}",
+ "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:listener/app/${local.project_owner_workspace}/*",
+ "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:loadbalancer/app/${local.project_owner_workspace}/*",
+ "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:targetgroup/${local.project_owner_workspace}*/*",
+ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.project_owner_workspace}*",
+ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/${local.project_owner_workspace}*",
+ "arn:aws:logs:${var.region}:${data.aws_caller_identity.current.account_id}:log-group:/${local.project_owner_workspace}:log-stream:",
+ "arn:aws:servicediscovery:${var.region}:${data.aws_caller_identity.current.account_id}:*/*",
+ ]
+ condition {
+ test = "StringEquals"
+ variable = "aws:RequestTag/workspace"
+ values = [
+ var.project,
+ var.owner,
+ var.workspace
+ ]
+ }
+ }
+}
+
+# Resource tags/Scoped policy
+data "aws_iam_policy_document" "resource_tags_update_actions" {
+ statement {
+ actions = [
+ "appmesh:TagResource",
+ "ec2:AttachInternetGateway",
+ "ec2:AuthorizeSecurityGroupEgress",
+ "ec2:AuthorizeSecurityGroupIngress",
+ "ec2:ReplaceRouteTableAssociation",
+ "ec2:RevokeSecurityGroupEgress",
+ "ec2:RevokeSecurityGroupIngress",
+ "ec2:AssociateRouteTable",
+ "ec2:ModifyVpcAttribute",
+ "ec2:ModifyVpcEndpoint",
+ "ec2:CreateTags",
+ "elasticloadbalancing:AddTags",
+ "elasticloadbalancing:ModifyLoadBalancerAttributes",
+ "elasticloadbalancing:ModifyTargetGroupAttributes",
+ "elasticloadbalancing:RemoveTags",
+ "elasticloadbalancing:ModifyRule",
+ "ecs:RegisterTaskDefinition",
+ "ecs:UpdateService",
+ "ecs:TagResource",
+ "ecs:UntagResource",
+ "ecs:ListTagsForResource",
+ "ecr:InitiateLayerUpload",
+ "ecr:UploadLayerPart",
+ "ecr:CompleteLayerUpload",
+ "ecr:PutImage",
+ "ecr:BatchGetImage",
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:TagResource",
+ "ecr:UntagResource",
+ "iam:AttachRolePolicy",
+ "iam:TagRole",
+ "iam:TagPolicy",
+ "iam:UntagPolicy",
+ "logs:PutRetentionPolicy",
+ "logs:UntagResource",
+ "servicediscovery:TagResource",
+ ]
+ resources = [
+ "arn:aws:appmesh:${var.region}:${data.aws_caller_identity.current.account_id}:mesh/${local.project_owner_workspace}",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:vpc/${local.vpc_id}",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:vpc-endpoint/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:security-group/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:subnet/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:route-table/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:internet-gateway/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:vpc-flow-log/*",
+ "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:cluster/${local.project_owner_workspace}",
+ "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:service/${local.project_owner_workspace}/*",
+ "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:task-definition/*",
+ "arn:aws:ecr:${var.region}:${data.aws_caller_identity.current.account_id}:repository/*",
+ "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:listener/app/${local.project_owner_workspace}/*",
+ "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:listener-rule/app/${local.project_owner_workspace}/*",
+ "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:loadbalancer/app/${local.project_owner_workspace}/*",
+ "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:targetgroup/${local.project_owner_workspace}*/*",
+ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.project_owner_workspace}*",
+ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/${local.project_owner_workspace}*",
+ "arn:aws:logs:${var.region}:${data.aws_caller_identity.current.account_id}:log-group:/${local.project_owner_workspace}:log-stream:",
+ "arn:aws:servicediscovery:${var.region}:${data.aws_caller_identity.current.account_id}:*/*",
+ ]
+ condition {
+ test = "StringEquals"
+ variable = "aws:ResourceTag/workspace"
+ values = [
+ var.project,
+ var.owner,
+ var.workspace
+ ]
+ }
+ }
+}
+
+# Resource tags/Scoped policy
+data "aws_iam_policy_document" "resource_tags_delete_actions" {
+ statement {
+ actions = [
+ "ec2:DisassociateAddress",
+ "appmesh:DeleteMesh",
+ "appmesh:DeleteVirtualNode",
+ "ec2:DeleteFlowLogs",
+ "ec2:DeleteNatGateway",
+ "ec2:DeleteSecurityGroup",
+ "ec2:DeleteSubnet",
+ "ecs:DeleteCluster",
+ "ecs:DeleteService",
+ "ec2:DeleteVpc",
+ "ec2:DeleteVpcEndpoints",
+ "ec2:DeleteTags",
+ "ec2:DisassociateRouteTable",
+ "ec2:DeleteRouteTable",
+ "ec2:DeleteRoute",
+ "ec2:ReleaseAddress",
+ "ec2:DetachInternetGateway",
+ "ec2:DeleteInternetGateway",
+ "ecr:DeleteRepository",
+ "elasticloadbalancing:DeleteLoadBalancer",
+ "elasticloadbalancing:DeleteTargetGroup",
+ "elasticloadbalancing:DeleteRule",
+ "elasticloadbalancing:DeleteListener",
+ "iam:DetachRolePolicy",
+ "iam:DeleteRole",
+ "iam:DeletePolicy",
+ "logs:DeleteLogGroup",
+ "servicediscovery:DeleteNamespace",
+ ]
+ resources = [
+ "arn:aws:appmesh:${var.region}:${data.aws_caller_identity.current.account_id}:mesh/${local.project_owner_workspace}",
+ "arn:aws:appmesh:${var.region}:${data.aws_caller_identity.current.account_id}:mesh/${local.project_owner_workspace}/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:vpc/${local.vpc_id}",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:route-table/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:subnet/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:natgateway/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:security-group/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:vpc-flow-log/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:vpc-endpoint/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:internet-gateway/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:elastic-ip/*",
+ "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:network-interface/*",
+ "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:service/${local.project_owner_workspace}/*",
+ "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:cluster/${local.project_owner_workspace}",
+ "arn:aws:ecr:${var.region}:${data.aws_caller_identity.current.account_id}:repository/*",
+ "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:listener/app/${local.project_owner_workspace}/*",
+ "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:listener-rule/app/${local.project_owner_workspace}/*",
+ "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:loadbalancer/app/${local.project_owner_workspace}/*",
+ "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:targetgroup/${local.project_owner_workspace}*/*",
+ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.project_owner_workspace}*",
+ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/${local.project_owner_workspace}*",
+ "arn:aws:logs:${var.region}:${data.aws_caller_identity.current.account_id}:log-group:/${local.project_owner_workspace}:log-stream:",
+ "arn:aws:servicediscovery:${var.region}:${data.aws_caller_identity.current.account_id}:namespace/*",
+ ]
+ condition {
+ test = "StringEquals"
+ variable = "aws:ResourceTag/workspace"
+ values = [
+ var.project,
+ var.owner,
+ var.workspace
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/terraform/modules/oidc/_local.tf b/terraform/modules/oidc/_local.tf
new file mode 100644
index 000000000..97b7f5598
--- /dev/null
+++ b/terraform/modules/oidc/_local.tf
@@ -0,0 +1,6 @@
+locals {
+ github_role_name = "${var.project}-github-role-${var.owner}-${random_string.oidc.result}"
+ project_owner_workspace = "${var.project}-${var.owner}-${var.workspace}"
+ wildcard = "*"
+ vpc_id = var.vpc_id == "" ? local.wildcard : var.vpc_id
+}
\ No newline at end of file
diff --git a/terraform/modules/oidc/_output.tf b/terraform/modules/oidc/_output.tf
new file mode 100644
index 000000000..f2a8d8e7e
--- /dev/null
+++ b/terraform/modules/oidc/_output.tf
@@ -0,0 +1,3 @@
+output "role" {
+ value = aws_iam_role.github
+}
\ No newline at end of file
diff --git a/terraform/modules/oidc/_variable.tf b/terraform/modules/oidc/_variable.tf
new file mode 100644
index 000000000..4f8a8a518
--- /dev/null
+++ b/terraform/modules/oidc/_variable.tf
@@ -0,0 +1,59 @@
+variable "oidc_github_repo" {
+ description = "The GitHub repository for OIDC"
+ type = string
+ default = ""
+ validation {
+ condition = length(var.oidc_github_repo) == 0 || can(regex("^[a-zA-Z0-9-]+/[a-zA-Z0-9-]+$", var.oidc_github_repo))
+ error_message = "oidc_github_repo must be set with 'org/repo' format or blank"
+ }
+}
+
+variable "owner" {
+ description = "The owner of the project"
+ type = string
+ default = "skylight"
+ validation {
+ condition = can(regex("^[[:alnum:]]{1,8}$", var.owner))
+ error_message = "owner must be 8 characters or less, all lowercase with no special characters or spaces"
+ }
+}
+
+variable "project" {
+ description = "The name of the project"
+ type = string
+ default = "dibbs"
+}
+
+variable "region" {
+ type = string
+ description = "The AWS region where resources are created"
+ default = ""
+ validation {
+ condition = can(regex("^(us|eu|ap|sa|ca|cn|af|me|eu)-[[:alnum:]]{2,10}-[0-9]$", var.region))
+ error_message = "region must be a valid AWS region"
+ }
+}
+
+variable "workspace" {
+ default = ""
+ type = string
+ description = "terraform workspace that OIDC will have permissions to"
+}
+
+variable "vpc_id" {
+ type = string
+ description = "ID of the VPC"
+ default = ""
+}
+
+variable "state_bucket_arn" {
+ type = string
+ description = "The ARN of the S3 bucket for state"
+ default = ""
+}
+
+variable "dynamodb_table_arn" {
+ type = string
+ description = "The ARN of the DynamoDB table for state"
+ default = ""
+}
\ No newline at end of file
diff --git a/terraform/modules/oidc/main.tf b/terraform/modules/oidc/main.tf
new file mode 100644
index 000000000..6b0fa1f0b
--- /dev/null
+++ b/terraform/modules/oidc/main.tf
@@ -0,0 +1,54 @@
+resource "random_string" "oidc" {
+ length = 8
+ special = false
+ upper = false
+}
+
+resource "aws_iam_policy" "wildcard" {
+ name = "${var.project}-wildcard-policy-${var.owner}-${random_string.oidc.result}"
+ policy = data.aws_iam_policy_document.wildcard.json
+}
+
+resource "aws_iam_policy" "scoped_one" {
+ name = "${var.project}-scoped-one-policy-${var.owner}-${random_string.oidc.result}"
+ policy = data.aws_iam_policy_document.scoped_one.json
+}
+
+resource "aws_iam_policy" "scoped_two" {
+ name = "${var.project}-scoped-two-policy-${var.owner}-${random_string.oidc.result}"
+ policy = data.aws_iam_policy_document.scoped_two.json
+}
+
+resource "aws_iam_policy" "request_tags_create_actions" {
+ name = "${var.project}-request-tags-policy-${var.owner}-${random_string.oidc.result}"
+ policy = data.aws_iam_policy_document.request_tags_create_actions.json
+}
+
+resource "aws_iam_policy" "resource_tags_update_actions" {
+ name = "${var.project}-resource-tags-update-policy-${var.owner}-${random_string.oidc.result}"
+ policy = data.aws_iam_policy_document.resource_tags_update_actions.json
+}
+
+resource "aws_iam_policy" "resource_tags_delete_actions" {
+ name = "${var.project}-resource-tags-delete-policy-${var.owner}-${random_string.oidc.result}"
+ policy = data.aws_iam_policy_document.resource_tags_delete_actions.json
+}
+
+resource "aws_iam_policy" "storage" {
+ name = "${var.project}-storage-policy-${var.owner}-${random_string.oidc.result}"
+ policy = data.aws_iam_policy_document.storage.json
+}
+
+resource "aws_iam_role" "github" {
+ name = local.github_role_name
+ managed_policy_arns = [
+ aws_iam_policy.wildcard.arn,
+ aws_iam_policy.scoped_one.arn,
+ aws_iam_policy.scoped_two.arn,
+ aws_iam_policy.request_tags_create_actions.arn,
+ aws_iam_policy.resource_tags_update_actions.arn,
+ aws_iam_policy.resource_tags_delete_actions.arn,
+ aws_iam_policy.storage.arn,
+ ]
+ assume_role_policy = data.aws_iam_policy_document.github_assume_role.json
+}
\ No newline at end of file
diff --git a/terraform/modules/oidc/provider.tf b/terraform/modules/oidc/provider.tf
new file mode 100644
index 000000000..ab1a18bb7
--- /dev/null
+++ b/terraform/modules/oidc/provider.tf
@@ -0,0 +1,13 @@
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 5.56.1"
+ }
+ random = {
+ source = "hashicorp/random"
+ version = "~> 3.6.3"
+ }
+ }
+ required_version = "~> 1.9.0"
+}
diff --git a/terraform/modules/tfstate/README.md b/terraform/modules/tfstate/README.md
new file mode 100644
index 000000000..5a72bb502
--- /dev/null
+++ b/terraform/modules/tfstate/README.md
@@ -0,0 +1,43 @@
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.9.0 |
+| [aws](#requirement\_aws) | ~> 5.56.1 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | ~> 5.56.1 |
+
+## Modules
+
+No modules.
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_dynamodb_table.tfstate_lock](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
+| [aws_s3_bucket.tfstate](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
+| [aws_s3_bucket_public_access_block.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
+| [aws_s3_bucket_server_side_encryption_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
+| [aws_s3_bucket_versioning.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [identifier](#input\_identifier) | n/a | `string` | `""` | no |
+| [owner](#input\_owner) | The owner of the project | `string` | `"skylight"` | no |
+| [project](#input\_project) | The name of the project | `string` | `"dibbs"` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [dynamodb\_table](#output\_dynamodb\_table) | n/a |
+| [state\_bucket](#output\_state\_bucket) | n/a |
+
\ No newline at end of file
diff --git a/terraform/modules/tfstate/_output.tf b/terraform/modules/tfstate/_output.tf
new file mode 100644
index 000000000..c86c10a95
--- /dev/null
+++ b/terraform/modules/tfstate/_output.tf
@@ -0,0 +1,7 @@
+output "state_bucket" {
+ value = aws_s3_bucket.tfstate
+}
+
+output "dynamodb_table" {
+ value = aws_dynamodb_table.tfstate_lock
+}
\ No newline at end of file
diff --git a/terraform/modules/tfstate/_variable.tf b/terraform/modules/tfstate/_variable.tf
new file mode 100644
index 000000000..5f891b562
--- /dev/null
+++ b/terraform/modules/tfstate/_variable.tf
@@ -0,0 +1,24 @@
+variable "owner" {
+ description = "The owner of the project"
+ type = string
+ default = "skylight"
+ validation {
+ condition = can(regex("^[[:alnum:]]{1,8}$", var.owner))
+ error_message = "owner must be 8 characters/numbers or less, all lowercase with no special characters or spaces"
+ }
+}
+
+variable "project" {
+ description = "The name of the project"
+ type = string
+ default = "dibbs"
+}
+
+variable "identifier" {
+ type = string
+ default = ""
+ validation {
+ condition = can(regex("^[[:alnum:]]{1,8}$", var.identifier))
+ error_message = "identifier must be 8 characters or less, all lowercase with no special characters or spaces"
+ }
+}
\ No newline at end of file
diff --git a/terraform/modules/tfstate/main.tf b/terraform/modules/tfstate/main.tf
new file mode 100644
index 000000000..de4e477a7
--- /dev/null
+++ b/terraform/modules/tfstate/main.tf
@@ -0,0 +1,45 @@
+resource "aws_s3_bucket" "tfstate" {
+ bucket = "${var.project}-tfstate-${var.owner}-${var.identifier}"
+
+ force_destroy = true
+}
+
+resource "aws_s3_bucket_public_access_block" "default" {
+ bucket = aws_s3_bucket.tfstate.id
+
+ block_public_acls = true
+ block_public_policy = true
+ ignore_public_acls = true
+ restrict_public_buckets = true
+}
+
+# https://avd.aquasec.com/misconfig/aws/s3/avd-aws-0132/
+# trivy:ignore:AVD-AWS-0132
+resource "aws_s3_bucket_server_side_encryption_configuration" "default" {
+ bucket = aws_s3_bucket.tfstate.bucket
+
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = "aws:kms"
+ }
+ }
+}
+
+resource "aws_s3_bucket_versioning" "default" {
+ bucket = aws_s3_bucket.tfstate.id
+ versioning_configuration {
+ status = "Enabled"
+ }
+}
+
+# Create a DynamoDB table for locking the state file
+resource "aws_dynamodb_table" "tfstate_lock" {
+ name = "${var.project}-tfstate-lock-${var.owner}-${var.identifier}"
+ hash_key = "LockID"
+ billing_mode = "PAY_PER_REQUEST"
+
+ attribute {
+ name = "LockID"
+ type = "S"
+ }
+}
diff --git a/terraform/modules/tfstate/provider.tf b/terraform/modules/tfstate/provider.tf
new file mode 100644
index 000000000..c7c2964f9
--- /dev/null
+++ b/terraform/modules/tfstate/provider.tf
@@ -0,0 +1,9 @@
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 5.56.1"
+ }
+ }
+ required_version = "~> 1.9.0"
+}
diff --git a/terraform/utilities/tfdocs.sh b/terraform/utilities/tfdocs.sh
new file mode 100755
index 000000000..bec9282ab
--- /dev/null
+++ b/terraform/utilities/tfdocs.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+terraform-docs markdown table --output-file README.md --output-mode inject ../modules/oidc
+terraform-docs markdown table --output-file README.md --output-mode inject ../modules/tfstate
+terraform-docs markdown table --output-file README.md --output-mode inject ../implementation/ecs
+terraform-docs markdown table --output-file README.md --output-mode inject ../implementation/setup
diff --git a/terraform/utilities/tffmt.sh b/terraform/utilities/tffmt.sh
new file mode 100755
index 000000000..00488f333
--- /dev/null
+++ b/terraform/utilities/tffmt.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+terraform fmt -recursive ../
\ No newline at end of file
diff --git a/terraform/utilities/tflint.sh b/terraform/utilities/tflint.sh
new file mode 100755
index 000000000..a822ca058
--- /dev/null
+++ b/terraform/utilities/tflint.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+tflint -f compact --chdir ../modules/oidc
+tflint -f compact --chdir ../modules/tfstate
+tflint -f compact --chdir ../implementation/setup
+tflint -f compact --chdir ../implementation/ecs