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
78 changes: 78 additions & 0 deletions infrastructure/stacks/networking/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Networking stack

The networking stack contains the networking resources that are securing the Eligibility Signposting API application resources.

The stack is documented on [this Confluence page](https://nhsd-confluence.digital.nhs.uk/spaces/Vacc/pages/1054575846/VPC+structure)

## Traffic Flow Explanation

### Public HTTPS Request Flow

* External client makes HTTPS request → Internet Gateway
* Request routes to Load Balancer or API Gateway in public subnet
* Request forwards to Lambda (or other application) in private subnet
* Lambda processes the request and returns response
* Response returns to client through the same path

### Outbound Internet Access

* Lambda functions in private subnets can make outbound internet calls via NAT Gateways
* This allows Lambda to call external APIs, download packages, etc.
* No direct inbound access to Lambda from the internet

### Internal-Only Traffic

* Lambda functions access AWS services via VPC Endpoints:
* Gateway Endpoints: S3, DynamoDB
* Interface Endpoints: KMS, CloudWatch, SSM, Secrets Manager, Lambda, STS, SQS
* All traffic between Lambda and AWS services stays within the AWS network
* No internet transit required for AWS service access

### Security Controls

#### Network ACLs

Public subnets: Allow HTTP(80), HTTPS(443), ephemeral ports
Private subnets: Allow VPC traffic and responses to outbound requests

#### Security Groups

Default security group: Deny all
VPC Endpoint security group: Allow HTTPS(443) from within VPC

#### Route Tables

Public subnets: Route to Internet Gateway for external access
Private subnets: Route to NAT Gateways for outbound-only access

## Deployment to AWS Development Environment

This stack should only ever be deployed once per account (e.g. the use of Terraform workspaces is explicitly not recommended beyond specifying `dev` as the environment).

Deployment to the Development environment is done through use of `make` commands

### Initialize Terraform and Plan

Run the following command to initialize Terraform and generate a plan. Replace `<env>` with the target environment:

```bash
make terraform env=dev stack=networking tf-command=init workspace=<env>
```

then

```bash
make terraform env=dev stack=networking tf-command=plan workspace=<env>
```

### 1.4 Apply Terraform Changes

Deploy the Terraform configuration using the following command:

```bash
make terraform env=dev stack=networking tf-command=apply workspace=<env>
```

## Release Deployment to AWS (Int, Ref and Prod)

Deployment to Int, Ref and Prod, as well as running the automated tests can be done via GitHub actions, when they are developed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
bucket = "eligibility-signposting-api-dev-tfstate"
key = "networking.tfstate"
region = "eu-west-2"
encrypt = true
1 change: 1 addition & 0 deletions infrastructure/stacks/networking/data.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data "aws_caller_identity" "current" {}
1 change: 1 addition & 0 deletions infrastructure/stacks/networking/default_variables.tf
7 changes: 7 additions & 0 deletions infrastructure/stacks/networking/internet_gateway.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
resource "aws_internet_gateway" "vpc_external_access" {
vpc_id = aws_vpc.main.id
tags = {
Name = "internet-gateway",
Stack = local.stack_name
}
}
34 changes: 34 additions & 0 deletions infrastructure/stacks/networking/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
locals {
any_ip_cidr = "0.0.0.0/0"
vpc_cidr_block = "10.0.0.0/16"
public_subnet_1_cidr = "10.0.3.0/24"
public_subnet_2_cidr = "10.0.4.0/24"
public_subnet_3_cidr = "10.0.5.0/24"
private_subnet_1_cidr = "10.0.6.0/24"
private_subnet_2_cidr = "10.0.7.0/24"
private_subnet_3_cidr = "10.0.8.0/24"
availability_zone_1 = "eu-west-2a"
availability_zone_2 = "eu-west-2b"
availability_zone_3 = "eu-west-2c"
default_port = 443

region = "eu-west-2"
stack_name = "Networking"

# VPC Interface Endpoints
vpc_interface_endpoints = {
kms = "com.amazonaws.${local.region}.kms"
cloudwatch-logs = "com.amazonaws.${local.region}.logs"
ssm = "com.amazonaws.${local.region}.ssm"
secrets-manager = "com.amazonaws.${local.region}.secretsmanager"
lambda = "com.amazonaws.${local.region}.lambda"
sts = "com.amazonaws.${local.region}.sts"
sqs = "com.amazonaws.${local.region}.sqs"
}

# VPC Gateway Endpoints
vpc_gateway_endpoints = {
dynamodb = "com.amazonaws.${local.region}.dynamodb"
s3 = "com.amazonaws.${local.region}.s3"
}
}
109 changes: 109 additions & 0 deletions infrastructure/stacks/networking/network_acls.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Network ACL for Private Subnets
resource "aws_network_acl" "private" {
vpc_id = aws_vpc.main.id
subnet_ids = [
aws_subnet.private_1.id,
aws_subnet.private_2.id,
aws_subnet.private_3.id
]

# Allow all outbound traffic from private subnets
egress {
rule_no = 100
action = "allow"
cidr_block = local.vpc_cidr_block
protocol = -1
from_port = 0
to_port = 0
}

# Allow inbound traffic from within the VPC
ingress {
rule_no = 100
action = "allow"
cidr_block = local.vpc_cidr_block
protocol = -1
from_port = 0
to_port = 0
}

# Allow responses to outbound requests (ephemeral ports)
ingress {
rule_no = 200
action = "allow"
cidr_block = "0.0.0.0/0"
protocol = "tcp"
from_port = 1024
to_port = 65535
}

tags = {
Name = "private-nacl",
Stack = local.stack_name
}
}

# Network ACL for Public Subnets
resource "aws_network_acl" "public" {
vpc_id = aws_vpc.main.id
subnet_ids = [
aws_subnet.public_1.id,
aws_subnet.public_2.id,
aws_subnet.public_3.id
]

# Allow all outbound traffic from public subnets
egress {
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
protocol = -1
from_port = 0
to_port = 0
}

# Allow inbound HTTP
ingress {
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
protocol = "tcp"
from_port = 80
to_port = 80
}

# Allow inbound HTTPS
ingress {
rule_no = 110
action = "allow"
cidr_block = "0.0.0.0/0"
protocol = "tcp"
from_port = 443
to_port = 443
}

# Allow responses to outbound requests (ephemeral ports)
ingress {
rule_no = 120
action = "allow"
cidr_block = "0.0.0.0/0"
protocol = "tcp"
from_port = 1024
to_port = 65535
}

# Allow inbound VPC traffic
ingress {
rule_no = 130
action = "allow"
cidr_block = local.vpc_cidr_block
protocol = -1
from_port = 0
to_port = 0
}

tags = {
Name = "public-nacl",
Stack = local.stack_name
}
}
3 changes: 3 additions & 0 deletions infrastructure/stacks/networking/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
provider "aws" {
region = "eu-west-2"
}
100 changes: 100 additions & 0 deletions infrastructure/stacks/networking/route_tables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Public Route Tables
resource "aws_route_table" "public_1" {
vpc_id = aws_vpc.main.id
tags = {
Name = "public-route-1",
Stack = local.stack_name
}
}

resource "aws_route_table" "public_2" {
vpc_id = aws_vpc.main.id
tags = {
Name = "public-route-2",
Stack = local.stack_name
}
}

resource "aws_route_table" "public_3" {
vpc_id = aws_vpc.main.id
tags = {
Name = "public-route-3",
Stack = local.stack_name
}
}

# Associate Public Route Tables with Public Subnets
resource "aws_route_table_association" "public_1" {
subnet_id = aws_subnet.public_1.id
route_table_id = aws_route_table.public_1.id
}

resource "aws_route_table_association" "public_2" {
subnet_id = aws_subnet.public_2.id
route_table_id = aws_route_table.public_2.id
}

resource "aws_route_table_association" "public_3" {
subnet_id = aws_subnet.public_3.id
route_table_id = aws_route_table.public_3.id
}

# Private Route Tables
resource "aws_route_table" "private_1" {
vpc_id = aws_vpc.main.id
tags = {
Name = "private-route-1",
Stack = local.stack_name
}
}

resource "aws_route_table" "private_2" {
vpc_id = aws_vpc.main.id
tags = {
Name = "private-route-2",
Stack = local.stack_name
}
}

resource "aws_route_table" "private_3" {
vpc_id = aws_vpc.main.id
tags = {
Name = "private-route-3",
Stack = local.stack_name
}
}

# Associate Private Route Tables with Private Subnets
resource "aws_route_table_association" "private_association_1" {
subnet_id = aws_subnet.private_1.id
route_table_id = aws_route_table.private_1.id
}

resource "aws_route_table_association" "private_association_2" {
subnet_id = aws_subnet.private_2.id
route_table_id = aws_route_table.private_2.id
}

resource "aws_route_table_association" "private_association_3" {
subnet_id = aws_subnet.private_3.id
route_table_id = aws_route_table.private_3.id
}

# Egress Internet Access
resource "aws_route" "public_internet_access" {
route_table_id = aws_route_table.public_1.id
destination_cidr_block = local.any_ip_cidr
gateway_id = aws_internet_gateway.vpc_external_access.id
}

resource "aws_route" "public_internet_access_2" {
route_table_id = aws_route_table.public_2.id
destination_cidr_block = local.any_ip_cidr
gateway_id = aws_internet_gateway.vpc_external_access.id
}

resource "aws_route" "public_internet_access_3" {
route_table_id = aws_route_table.public_3.id
destination_cidr_block = local.any_ip_cidr
gateway_id = aws_internet_gateway.vpc_external_access.id
}
1 change: 1 addition & 0 deletions infrastructure/stacks/networking/shared_locals.tf
44 changes: 44 additions & 0 deletions infrastructure/stacks/networking/ssm.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# resource "aws_ssm_parameter" "proxygen_private_key" {
# count = var.environment == "dev" ? 1 : 0
# name = "/proxygen/private_key"
# type = "SecureString"
# value = var.PROXYGEN_PRIVATE_KEY
# tier = "Advanced"
#
# tags = {
# Stack = local.stack_name
# }
# }
#
# resource "aws_ssm_parameter" "mtls_api_ca_cert" {
# name = "/${var.environment}/mtls/api_ca_cert"
# type = "SecureString"
# key_id = aws_kms_key.networking_ssm_key.id
# value = var.API_CA_CERT
# tier = "Advanced"
# tags = {
# Stack = local.stack_name
# }
# }
#
# resource "aws_ssm_parameter" "mtls_api_client_cert" {
# name = "/${var.environment}/mtls/api_client_cert"
# type = "SecureString"
# key_id = aws_kms_key.networking_ssm_key.id
# value = var.API_CLIENT_CERT
# tier = "Advanced"
# tags = {
# Stack = local.stack_name
# }
# }
#
# resource "aws_ssm_parameter" "mtls_api_private_key_cert" {
# name = "/${var.environment}/mtls/api_private_key_cert"
# type = "SecureString"
# key_id = aws_kms_key.networking_ssm_key.id
# value = var.API_PRIVATE_KEY_CERT
# tier = "Advanced"
# tags = {
# Stack = local.stack_name
# }
# }
Loading
Loading