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
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is for you! Please, updated to the versions agreed by your team.

terraform 1.7.0
terraform 1.11.1
pre-commit 3.6.0
vale 3.6.0
poetry 2.1.1
Expand Down
53 changes: 53 additions & 0 deletions infrastructure/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
###################
## Utilities ##
###################
guard-%:
@ if [ "${${*}}" = "" ]; then \
echo "Variable $* not set"; \
exit 1; \
fi

###################
#### Terraform ####
###################

# Initializes the Terraform configuration for the specified stack and environment.
terraform-init: guard-env guard-stack
rm -rf ./stacks/$(stack)/.terraform
terraform -chdir=./stacks/$(stack) init -var-file=stacks/_shared/tfvars/$(env).tfvars -backend-config=backends/$(env).$(stack).tfbackend -upgrade
terraform -chdir=./stacks/$(stack) get -update

# Selects or creates a Terraform workspace for the specified stack and environment.
terraform-workspace: guard-env guard-stack guard-workspace
terraform -chdir=./stacks/$(stack) workspace select $(workspace) || \
terraform -chdir=./stacks/$(stack) workspace new $(workspace)

terraform -chdir=./stacks/$(stack) workspace show

# Lists all Terraform workspaces for the specified stack and environment.
terraform-workspace-list: guard-env guard-stack terraform-init
terraform -chdir=./stacks/$(stack) workspace list

# Deletes a specified Terraform workspace for the stack, switching to the default workspace first.
terraform-workspace-delete: guard-env guard-stack
terraform -chdir=./stacks/$(stack) workspace select default
terraform -chdir=./stacks/$(stack) workspace delete $(workspace)

# Runs a specified Terraform command (e.g., plan, apply) for the stack and environment.
terraform: guard-env guard-stack guard-tf-command terraform-init terraform-workspace
terraform -chdir=./stacks/$(stack) $(tf-command) -var-file=../_shared/tfvars/$(env).tfvars $(args) --parallelism=30
rm -f ./terraform_outputs_$(stack).json || true
terraform -chdir=./stacks/$(stack) output -json > ./build/terraform_outputs_$(stack).json

###################
#### Bootstrap ####
###################

# Initializes the Terraform configuration for the bootstrap stack.
bootstrap-terraform-init: guard-env
terraform -chdir=./stacks/bootstrap init -var-file=stacks/_shared/tfvars/$(env).tfvars -upgrade
terraform -chdir=./stacks/bootstrap get -update

# Runs a specified Terraform command (e.g., plan, apply) for the bootstrap stack.
bootstrap-terraform: guard-env guard-tf-command bootstrap-terraform-init
terraform -chdir=./stacks/bootstrap $(tf-command) -var-file=../_shared/tfvars/$(env).tfvars $(args)
18 changes: 18 additions & 0 deletions infrastructure/modules/_shared/default_variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# tflint-ignore: terraform_unused_declarations
variable "project_name" {
default = "eligibility-signposting-api"
type = string
}

# tflint-ignore: terraform_unused_declarations
variable "environment" {
description = "The purpose of the account dev/test/ref/prod or the workspace"
type = string
}

# tflint-ignore: terraform_unused_declarations
variable "tags" {
description = "A map of tags to assign to resources."
type = map(string)
default = {}
}
12 changes: 12 additions & 0 deletions infrastructure/modules/bootstrap/tfstate/kms.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
resource "aws_kms_key" "terraform_state_bucket_cmk" {
description = "Terraform State Bucket Master Key"
deletion_window_in_days = 14
is_enabled = true
enable_key_rotation = true
tags = var.tags
}

resource "aws_kms_alias" "terraform_state_bucket_cmk" {
name = "alias/${var.project_name}-tfstate_bucket_cmk"
target_key_id = aws_kms_key.terraform_state_bucket_cmk.key_id
}
10 changes: 10 additions & 0 deletions infrastructure/modules/bootstrap/tfstate/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.11.1"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.6, != 5.71.0"
}
}
}
174 changes: 174 additions & 0 deletions infrastructure/modules/bootstrap/tfstate/s3.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Main state bucket
resource "aws_s3_bucket" "tfstate_bucket" {
bucket = "${var.project_name}-${var.environment}-tfstate"
tags = {
Stack = "Bootstrap"
}
}

# Enable versioning for disaster recovery
resource "aws_s3_bucket_versioning" "tfstate_bucket_versioning_config" {
bucket = aws_s3_bucket.tfstate_bucket.id
versioning_configuration {
status = "Enabled"
}
}
# Block public access to the bucket
resource "aws_s3_bucket_public_access_block" "tfstate" {
bucket = aws_s3_bucket.tfstate_bucket.id

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

# Encrypt the bucket with a KMS key
resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate_bucket_server_side_encryption_config" {
bucket = aws_s3_bucket.tfstate_bucket.id

rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.terraform_state_bucket_cmk.arn
sse_algorithm = "aws:kms"
}
bucket_key_enabled = true
}
}

resource "aws_s3_bucket_policy" "tfstate_bucket" {
bucket = aws_s3_bucket.tfstate_bucket.id
policy = data.aws_iam_policy_document.tfstate_s3_bucket_policy.json
}

data "aws_iam_policy_document" "tfstate_s3_bucket_policy" {
statement {
sid = "AllowSslRequestsOnly"
actions = [
"s3:*",
]
effect = "Deny"
resources = [
aws_s3_bucket.tfstate_bucket.arn,
"${aws_s3_bucket.tfstate_bucket.arn}/*",
]
principals {
type = "*"
identifiers = ["*"]
}
condition {
test = "Bool"
values = [
"false",
]

variable = "aws:SecureTransport"
}
}
}

resource "aws_s3_bucket_lifecycle_configuration" "tfstate_bucket" {
bucket = aws_s3_bucket.tfstate_bucket.id

rule {
id = "TfStateBucketExpirationTransferToIa"
status = "Enabled"
filter {
prefix = ""
}

expiration {
days = 90
}

noncurrent_version_transition {
noncurrent_days = 30
storage_class = "STANDARD_IA"
}

abort_incomplete_multipart_upload {
days_after_initiation = 7
}
}
}

# Logging

resource "aws_s3_bucket" "tfstate_s3_access_logs" {
bucket = "${var.project_name}-${var.environment}-tfstate-access-logs"
}

resource "aws_s3_bucket_logging" "s3_logging_config" {
bucket = aws_s3_bucket.tfstate_bucket.id
target_bucket = aws_s3_bucket.tfstate_s3_access_logs.bucket
target_prefix = "bucket_logs/"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate_s3_access_logs_server_side_encryption_config" {
bucket = aws_s3_bucket.tfstate_s3_access_logs.id

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

resource "aws_s3_bucket_lifecycle_configuration" "tfstate_s3_access_logs_object_expiry_lifecycle_rule_config" {
bucket = aws_s3_bucket.tfstate_s3_access_logs.id

rule {
id = "StateBucketLogsExpiration"
status = "Enabled"
filter {
prefix = ""
}
expiration {
days = var.log_retention_in_days
}

noncurrent_version_expiration {
noncurrent_days = var.log_retention_in_days
}
}
}

resource "aws_s3_bucket_public_access_block" "s3logs" {
bucket = aws_s3_bucket.tfstate_s3_access_logs.id

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

resource "aws_s3_bucket_policy" "tfstate_s3_access_logs_bucket_policy" {
bucket = aws_s3_bucket.tfstate_s3_access_logs.id
policy = data.aws_iam_policy_document.tfstate_s3_access_logs_bucket_policy.json
}

data "aws_iam_policy_document" "tfstate_s3_access_logs_bucket_policy" {
statement {
sid = "AllowSSLRequestsOnly"
actions = [
"s3:*",
]
effect = "Deny"
resources = [
aws_s3_bucket.tfstate_s3_access_logs.arn,
"${aws_s3_bucket.tfstate_s3_access_logs.arn}/*",
]
principals {
type = "*"
identifiers = ["*"]
}
condition {
test = "Bool"
values = [
"false",
]

variable = "aws:SecureTransport"
}
}
}
5 changes: 5 additions & 0 deletions infrastructure/modules/bootstrap/tfstate/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# tflint-ignore: terraform_unused_declarations
variable "log_retention_in_days" {
default = 5
type = number
}
11 changes: 11 additions & 0 deletions infrastructure/stacks/_shared/default_variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# tflint-ignore: terraform_unused_declarations
variable "project_name" {
default = "eligibility-signposting-api"
type = string
}

variable "environment" {
default = "dev"
description = "Environment"
type = string
}
18 changes: 18 additions & 0 deletions infrastructure/stacks/_shared/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
locals {
# tflint-ignore: terraform_unused_declarations
environment = var.environment
# tflint-ignore: terraform_unused_declarations
workspace = lower(terraform.workspace)
# tflint-ignore: terraform_unused_declarations
runtime = "python3.13.1"

# tflint-ignore: terraform_unused_declarations
tags = {
TagVersion = "1"
Programme = "Vaccinations"
Project = "EligibilitySignpostingAPI"
Environment = var.environment
ServiceCategory = var.environment == "prod" ? "Bronze" : "N/A"
Tool = "Terraform"
}
}
Loading
Loading