Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ indent_size = 4

[{Makefile,*.mk,go.mod,go.sum,*.go,.gitmodules}]
indent_style = tab


[scripts/infra/README.md]
ignore = true
2 changes: 2 additions & 0 deletions .markdownlintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
scripts/infra/README.md

48 changes: 48 additions & 0 deletions scripts/infra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## scripts/infra README

### Purpose

The `scripts/infra` directory contains **one-time bootstrap Terraform configuration** for setting up the foundational AWS infrastructure required to manage the `genomic-order-management-service-api` application infrastructure using Terraform.

---

### What This Sets Up

This directory creates the infrastructure **backend** and **deployment prerequisites**:

- **Terraform State Backend (S3 + DynamoDB)**
- S3 bucket for storing Terraform state files (`s3-terraform.tf`)
- DynamoDB table for state locking (`dynamodb-terraform.tf`)
- Enables safe, concurrent Terraform operations

- **GitHub Actions OIDC Integration**
- AWS IAM role for GitHub Actions (`iam-oidc.tf`)
- Trust policy allowing GitHub Actions workflows to assume the role
- Granular permissions for Terraform deployments
- No long-lived credentials stored in the pipeline

- **OIDC Identity Provider**
- Existing AWS OIDC provider for GitHub Actions (referenced via data source)
- Used by the IAM role for secure authentication

---

### Usage

#### Initial Setup (One-Time)

```bash
# Navigate to scripts/infra
cd scripts/infra

# Initialize Terraform with local backend
terraform init

# Review the plan
terraform plan \
-var github_org=NHSDigital \
-var github_repo=genomic-order-management-service-api

# Apply the infrastructure
terraform apply

28 changes: 28 additions & 0 deletions scripts/infra/dynamodb-terraform.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
resource "aws_dynamodb_table" "terraform_state_lock" {
name = "${var.project}-tfstate-lock-${var.environment}"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"

attribute {
name = "LockID"
type = "S"
}

server_side_encryption {
enabled = true
}

point_in_time_recovery {
enabled = true
}

lifecycle {
prevent_destroy = true
}

tags = {
project = var.project
Name = "terraform-lock-${var.environment}"
Environment = var.environment
}
}
190 changes: 190 additions & 0 deletions scripts/infra/iam-oidc.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
data "aws_iam_openid_connect_provider" "github_actions" {
arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${var.oidc_provider}"
}

data "aws_iam_policy_document" "github_actions_assume_role" {
statement {
effect = "Allow"

principals {
type = "Federated"
identifiers = [data.aws_iam_openid_connect_provider.github_actions.arn]
}

actions = ["sts:AssumeRoleWithWebIdentity"]

condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = [
"repo:${var.github_org}/${var.github_repo}:ref:refs/heads/${var.github_branch}",
]
}

condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:aud"
values = ["sts.amazonaws.com"]
}
}
}

resource "aws_iam_role" "github_actions" {
name = var.role_name
assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role.json
}

data "aws_iam_policy_document" "deploy_permissions" {
statement {
sid = "S3BucketManagement"
effect = "Allow"

actions = [
"s3:CreateBucket",
"s3:DeleteBucket",
"s3:GetBucketVersioning",
"s3:PutBucketVersioning",
"s3:GetBucketEncryption",
"s3:PutBucketEncryption",
"s3:GetBucketPublicAccessBlock",
"s3:PutBucketPublicAccessBlock",
"s3:GetBucketPolicy",
"s3:PutBucketPolicy",
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
]

resources = [
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
]
}

statement {
sid = "DynamoDBTableManagement"
effect = "Allow"

actions = [
"dynamodb:CreateTable",
"dynamodb:DeleteTable",
"dynamodb:DescribeTable",
"dynamodb:UpdateTable",
"dynamodb:ListTables",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
]

resources = ["arn:aws:dynamodb:*:*:table/*"]
}

statement {
sid = "SecretsManagerManagement"
effect = "Allow"

actions = [
"secretsmanager:CreateSecret",
"secretsmanager:DeleteSecret",
"secretsmanager:DescribeSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:PutSecretValue",
"secretsmanager:UpdateSecret",
"secretsmanager:ListSecrets",
]

resources = ["arn:aws:secretsmanager:*:*:secret:*"]
}

statement {
sid = "KMSKeyManagement"
effect = "Allow"

actions = [
"kms:CreateKey",
"kms:DescribeKey",
"kms:ListKeys",
"kms:ListAliases",
"kms:CreateAlias",
"kms:DeleteAlias",
"kms:UpdateAlias",
"kms:GetKeyPolicy",
"kms:PutKeyPolicy",
"kms:Decrypt",
"kms:Encrypt",
"kms:GenerateDataKey",
"kms:ScheduleKeyDeletion",
]

resources = ["arn:aws:kms:*:*:key/*"]
}

statement {
sid = "IAMRoleManagement"
effect = "Allow"

actions = [
"iam:CreateRole",
"iam:DeleteRole",
"iam:GetRole",
"iam:ListRoles",
"iam:UpdateAssumeRolePolicy",
"iam:GetAssumeRolePolicy",
"iam:PassRole",
"iam:TagRole",
"iam:UntagRole",
]

resources = ["arn:aws:iam::*:role/*"]
}

statement {
sid = "IAMPolicyManagement"
effect = "Allow"

actions = [
"iam:CreatePolicy",
"iam:DeletePolicy",
"iam:GetPolicy",
"iam:ListPolicies",
"iam:CreatePolicyVersion",
"iam:DeletePolicyVersion",
"iam:ListPolicyVersions",
"iam:GetPolicyVersion",
"iam:AttachRolePolicy",
"iam:DetachRolePolicy",
"iam:ListAttachedRolePolicies",
"iam:PutRolePolicy",
"iam:GetRolePolicy",
"iam:DeleteRolePolicy",
"iam:ListRolePolicies",
]

resources = [
"arn:aws:iam::*:policy/*",
"arn:aws:iam::*:role/*",
]
}

statement {
sid = "CloudWatchLogs"
effect = "Allow"

actions = [
"logs:CreateLogGroup",
"logs:DeleteLogGroup",
"logs:DescribeLogGroups",
"logs:ListLogGroups",
]

resources = ["arn:aws:logs:*:*:log-group:*"]
}
}

resource "aws_iam_role_policy" "deploy_permissions" {
name = "deploy-permissions"
role = aws_iam_role.github_actions.id
policy = data.aws_iam_policy_document.deploy_permissions.json
}
25 changes: 25 additions & 0 deletions scripts/infra/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
terraform {
required_version = ">= 1.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}

provider "aws" {
region = var.aws_region

default_tags {
tags = {
Environment = var.environment
Project = "genomic-order-management-service-api"
ManagedBy = "Terraform"
}
}
}

data "aws_caller_identity" "current" {}

59 changes: 59 additions & 0 deletions scripts/infra/s3-terraform.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
locals {
terraform_state_bucket_name = "${var.project}-tfstate-${var.environment}"
}

resource "aws_s3_bucket" "terraform_state_store" {
bucket = local.terraform_state_bucket_name

lifecycle {
prevent_destroy = true
}

tags = {
project = var.project
Name = "${local.terraform_state_bucket_name}"
Environment = var.environment
}
}

resource "aws_s3_bucket_versioning" "terraform_state_store" {
bucket = aws_s3_bucket.terraform_state_store.id

versioning_configuration {
status = "Enabled"
}
}

resource "aws_s3_bucket_ownership_controls" "terraform_state_ownership" {
bucket = aws_s3_bucket.terraform_state_store.id
rule {
object_ownership = "BucketOwnerPreferred"
}
}


resource "aws_s3_bucket_acl" "terraform-state-acl" {
depends_on = [aws_s3_bucket_ownership_controls.terraform_state_ownership]

bucket = aws_s3_bucket.terraform_state_store.id
acl = "private"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state_store" {
bucket = aws_s3_bucket.terraform_state_store.id

rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}

resource "aws_s3_bucket_public_access_block" "terraform_state_store" {
bucket = aws_s3_bucket.terraform_state_store.id

block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
Loading
Loading