The Enclave That Could Not Connect
Build a SECRET-level mission enclave simulation with zero-trust and OSCAL.
Introduction
30 Second Summary
A classified network does not earn its connection by existing. It earns it by proving to an authorizing official that every boundary is closed by construction and every control is evidenced.
In this project, you will simulate a SECRET-level mission enclave in commercial AWS, building its infrastructure, control set, and connection-approval package from categorization through authorization briefing. The enclave cannot connect until you prove it deserves to.
What You'll Build
You will run the full categorize-tailor-build-package-finding-brief sequence against a live Terraform enclave, then present the authorization briefing to a mock authorizing official who can sign or return it with findings.
By the end of this project, you'll have:
- A 4-scanner policy gate that validates your enclave baseline at 0 violations, proving no public egress path exists and SPIFFE/SPIRE workload identity is enforced.
- A connection-approval-equivalent package modeled on the DoD classified-side path, with 8 of 9 control families evidenced in machine-readable OSCAL and provider inheritance documented.
- An injected cross-domain finding that stalls the approval, remediated by re-design and verified at 0 violations with the POA&M showing the finding opened and closed.
- Secret Mission: Deliver the authorization briefing as a presentation artifact with the three-boundary diagram and lessons learned for a mock authorizing official.
Are there any prerequisites?
This is a hard project. You need an AWS account with CLI configured, Terraform installed, Python 3.12 with pip, Git, working knowledge of VPC networking, and RMF familiarity at the practitioner level.
Before We Start
Before any Terraform resource fires, you need to commit to the shape of what you are building and why the connection approval path governs whether it exists. The enclave, the connection, and the injected finding are three parts of a single question. Getting that question sharp now prevents a build that drifts from its authorization boundary later.
Categorize, Decide, and Draw the Topology
An enclave that starts building before it categorizes receives the wrong control baseline. The wrong baseline leaves gaps that only surface during the connection approval assessment, which is the worst moment to discover you sized the control set wrong.
The five decision records and the topology diagram must precede the first Terraform resource because the design drives the build, not the reverse. This step establishes the categorization, commits the decisions that shape the architecture, and draws the boundaries the rest of the project enforces.
In this step, get ready to:
- Initialize the compliance-trestle workspace and import the NIST SP 800-53 Rev 5 Release 5.2.0 OSCAL catalog with the CNSSI 1253 Rev 5 profile.
- Author five decision records in ADR format, each naming a real alternative, the tradeoff accepted, and the reversal trigger.
- Draw and commit the topology diagram with three labeled boundaries.
Initialize the compliance-trestle workspace and import the catalog
The compliance-trestle toolchain manages OSCAL artifacts in a git-friendly directory structure. You need a workspace before you can import the catalog that holds all 1000+ controls from NIST SP 800-53 Rev 5.
- Create the trestle workspace directory and move into it by running these commands:
mkdir trestle-workspace
cd trestle-workspace
- Initialize the workspace by running:
trestle init
What does trestle init do?
This creates a .trestle/ config directory and the standard OSCAL model directories: catalogs/, profiles/, component-definitions/, system-security-plans/, and others. The workspace is an opinionated structure similar to git that enforces OSCAL schemas on every artifact you create.
- Confirm the workspace initialized by running:
ls -la
You should see the .trestle/ directory, catalogs/, profiles/, and the other model directories listed.
- Import the NIST SP 800-53 Rev 5 Release 5.2.0 OSCAL catalog by running:
trestle import -f https://raw.githubusercontent.com/usnistgov/oscal-content/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_catalog.json -o nist-800-53-r5
What does this import do?
The -f flag points to the official NIST OSCAL catalog hosted on GitHub. The -o nist-800-53-r5 flag names the model inside your workspace. After import, the catalog lives at catalogs/nist-800-53-r5/catalog.json.
This catalog contains every control from SP 800-53 Rev 5 Release 5.2.0 including the three new controls (SA-15(13), SA-24, SI-02(07)) added in this release. It is the foundation the profile will select from.
- Validate the imported catalog by running:
trestle validate -t catalog -n nist-800-53-r5
You should see validation pass with 0 errors. This confirms the catalog imported cleanly and conforms to the OSCAL 1.2.1 schema.
Seeing a validation error?
If you see Not in a trestle workspace, make sure you are inside the trestle-workspace directory (the one containing .trestle/).
If you see a network error on import, check your internet connection and try the command again. The URL fetches directly from GitHub.
Help me troubleshoot my trestle import or validation error
Now you need the profile that selects and tailors controls for the simulated IL6 categorization. The CNSSI 1253 Rev 5 profile with the Classified Information Overlay applies ADD and MODIFY operations to produce the High-High-High baseline appropriate for SECRET-equivalent systems.
- Create the profile directory by running:
mkdir -p profiles/cnssi-1253-il6-overlay
- Create the CNSSI 1253 Rev 5 profile with the Classified Information Overlay by creating the file profiles/cnssi-1253-il6-overlay/profile.json with the following content:
{
"profile": {
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"metadata": {
"title": "CNSSI 1253 Rev 5 IL6 Overlay Profile (Simulated)",
"version": "1.0.0",
"oscal-version": "1.2.1",
"last-modified": "2026-08-05T00:00:00Z"
},
"imports": [
{
"href": "trestle://catalogs/nist-800-53-r5/catalog.json",
"include-controls": [
{
"with-ids": [
"ac-1", "ac-2", "ac-3", "ac-4",
"ia-1", "ia-2", "ia-3", "ia-4",
"sc-1", "sc-7", "sc-8", "sc-12", "sc-13",
"au-1", "au-2", "au-3", "au-6",
"cm-1", "cm-2", "cm-6", "cm-7",
"ca-1", "ca-2", "ca-7",
"ir-1", "ir-4", "ir-5", "ir-6",
"cp-1", "cp-2", "cp-4", "cp-10",
"ps-1", "ps-2", "ps-3"
]
}
]
}
],
"modify": {
"set-parameters": [
{
"param-id": "sc-13_prm_1",
"values": ["FIPS 140-3 validated cryptography"]
}
]
}
}
}
What does this profile represent?
This profile models the CNSSI 1253 Rev 5 Classified Information Overlay applied as-equivalent for simulated IL6. It selects representative controls from 9 families: AC, IA, SC, AU, CM, CA, IR, CP, and PS.
The imports block references the catalog you just imported using trestle:// protocol. The modify block applies ADD/MODIFY operations. Here it sets the SC-13 parameter to require FIPS 140-3 validated cryptography, aligning with the September 22 2026 historical-list date for FIPS 140-2.
- Validate the profile resolves against the catalog by running:
trestle validate -t profile -n cnssi-1253-il6-overlay
You should see 0 schema errors. The profile imports resolve against the catalog you imported earlier.
Profile validation failing?
If you see an unresolved import error, confirm the catalog is imported with the exact name nist-800-53-r5. The profile's href must match: trestle://catalogs/nist-800-53-r5/catalog.json.
Help me fix my OSCAL profile validation error
Author five decision records in ADR format
Every design decision in this enclave must be reversible and auditable. An Architecture Decision Record names the decision, a real alternative that was considered, the tradeoff accepted, and the trigger that would reverse the decision. These records become part of the authorization package.
- Create the decisions directory by running:
mkdir -p docs/decisions
- Create the first decision record by creating the file docs/decisions/ADR-001-commercial-cloud-simulation.md with:
# ADR-001: Simulate SECRET Pattern in Commercial Cloud
## Status
Accepted
## Decision
Simulate the SECRET-level enclave pattern in commercial AWS rather than waiting for classified-environment access.
## Alternative Considered
Wait for classified-environment access with matching toolchain before building.
## Tradeoff Accepted
No real classified data or network is used, but the design achieves full fidelity to the pattern. The simulation cannot exercise cleared-role requirements or connect to a real classified network.
## Reversal Trigger
Classified environment access granted with matching toolchain availability.
- Create the second decision record at docs/decisions/ADR-002-transit-gateway-route-isolation.md:
# ADR-002: Transit Gateway Route-Table Isolation
## Status
Accepted
## Decision
Use Transit Gateway route-table isolation to model the SIPRNet boundary within a single AWS account.
## Alternative Considered
Separate AWS account per security domain (account-boundary isolation).
## Tradeoff Accepted
Single-account complexity in route management vs. account-boundary blast radius reduction. Route-table isolation provides equivalent logical separation for this simulation while simplifying credential management.
## Reversal Trigger
Multi-account requirement imposed by authorizing official or real deployment constraints.
- Confirm both files exist by running:
ls docs/decisions/
You should see ADR-001-commercial-cloud-simulation.md and ADR-002-transit-gateway-route-isolation.md listed.
- Create the third decision record at docs/decisions/ADR-003-fips-140-3.md:
# ADR-003: FIPS 140-3 Validated Cryptography
## Status
Accepted
## Decision
Require FIPS 140-3 validated cryptography for all enclave data-in-transit and data-at-rest.
## Alternative Considered
FIPS 140-2 validated modules (still on the active list until September 22 2026).
## Tradeoff Accepted
Narrower module selection today vs. guaranteed compliance posture after the September 22 2026 historical-list date when all FIPS 140-2 certificates move to the historical list.
## Reversal Trigger
Authoritative guidance from NIST or CMVP reverting or extending the historical-list date.
- Create the fourth decision record at docs/decisions/ADR-004-spiffe-spire-workload-identity.md:
# ADR-004: SPIFFE/SPIRE Workload Identity
## Status
Accepted
## Decision
Use SPIFFE/SPIRE for workload identity attestation rather than relying on network-position trust.
## Alternative Considered
Network-position trust (security groups and VPC membership as the identity signal).
## Tradeoff Accepted
Attestation overhead (SPIRE server, agent deployment, certificate rotation) vs. zero-trust alignment where a compromised network position does not confer trust.
## Reversal Trigger
Authoritative zero-trust guidance deprecating SPIFFE as a workload identity mechanism in this context.
- Create the fifth decision record at docs/decisions/ADR-005-no-bare-abbreviations.md:
# ADR-005: No Bare Abbreviations for CAP Terms
## Status
Accepted
## Decision
The connection approval process and the cloud access point are never abbreviated bare throughout the project. Every reference spells out the full term.
## Alternative Considered
Use standard abbreviations (CAP) throughout for brevity.
## Tradeoff Accepted
Verbosity in documentation vs. elimination of the naming trap where CAP could mean either the connection approval process or the cloud access point.
## Reversal Trigger
Style guide from authorizing authority mandating the use of abbreviations with disambiguation handled elsewhere.
- Confirm all five records exist by running:
ls docs/decisions/
You should see all five files: ADR-001-commercial-cloud-simulation.md through ADR-005-no-bare-abbreviations.md.
Why do decision records matter for the authorization package?
An authorizing official reviewing the package needs to know not just what you built, but why you chose this path over the alternatives. Each ADR gives the reviewer a single place to challenge or accept a design choice without re-reading the entire codebase.
The reversal trigger is the most important field. It tells the next engineer exactly when this decision should be revisited instead of defended.
Draw and commit the topology diagram
The topology diagram is the most-referenced artifact in the connection-approval-equivalent package. It must show three distinct boundaries because those boundaries have different authorization authorities and different evidence requirements.
- Create the topology diagram file at docs/topology-diagram.md starting with the boundary diagram:
# Enclave Topology Diagram
## Three Authorization Boundaries
```
+=============================================================+
| MISSION AUTHORIZATION BOUNDARY |
| (What this system is answerable for) |
| |
| [Enclave VPC - no IGW, no NAT, private subnets] |
| - ECS workloads with SPIFFE/SPIRE identity |
| - Security groups: deny-all egress |
| - KMS: FIPS 140-3 validated policy |
| | |
| TGW Attachment (enclave-isolated RT, propagation DISABLED) |
| | |
| [AWS Transit Gateway] |
| - Enclave-isolated route table |
| - Shared-services route table |
| - BLACKHOLE ROUTE (models stalled connection) |
| | |
| NETWORK CONNECTION BOUNDARY |
| (What the connection approval governs) |
| [Cloud-Access-Point-Equivalent / Inspection VPC] |
| - AWS Network Firewall (deep packet inspection) |
| - Shared-services route table attachment |
+=============================================================+
+-------------------------------------------------------------+
| PROVIDER INHERITANCE BOUNDARY |
| (AWS-managed IaaS controls beneath the mission boundary) |
| - Physical security, hypervisor isolation |
| - AWS-managed encryption of EBS, S3 at rest |
| - Network fabric, power, cooling |
+-------------------------------------------------------------+
```
Why three boundaries instead of one?
The mission authorization boundary is what your system security plan covers. The provider inheritance boundary is what AWS already authorized at the IaaS layer. The network connection boundary is what the connection approval process governs.
Confusing these boundaries is the most common authorization-package mistake. A control claimed as "inherited from AWS" that actually requires enclave-side configuration will be returned by the assessor. Keeping three boundaries distinct on the diagram prevents that confusion from the start.
- Now add the Key Labels section to the same file by appending this content to docs/topology-diagram.md:
## Key Labels
- **Mission Authorization Boundary**: Encloses the enclave VPC, ECS
workloads, Transit Gateway, and inspection VPC. This is what the
system security plan covers.
- **Provider Inheritance Boundary**: AWS IaaS controls inherited by
the mission system. Documented via provider SSP references.
- **Network Connection Boundary**: The cloud-access-point-equivalent
inspection VPC with Network Firewall. This boundary is what the
connection approval process governs.
- **Blackhole Route**: Models the stalled connection state. Until the
connection approval is granted, no traffic crosses from enclave to
shared services.
- **Transit Gateway**: Two route tables enforce isolation. The
enclave-isolated table has propagation disabled and a blackhole
route for the shared-services CIDR.
What does each label mean for the authorization?
The blackhole route is the physical representation of "no connection approved yet." Until the connection approval process completes, this route ensures zero traffic flows between the enclave and shared services. The Transit Gateway enforces this at the routing layer, not at a firewall rule that could be misconfigured.
- Stage and commit all artifacts to your repository by running:
cd ..
git add trestle-workspace/ docs/
git commit -m "step-1: categorize, decide, draw topology"
- Confirm the commit by running:
git log --oneline -1
You should see a single commit with the message step-1: categorize, decide, draw topology.
Before you run the final validation, predict: do you expect trestle validate -a to pass with 0 errors for both models?
- Run the full workspace validation by moving back into the workspace and running:
cd trestle-workspace
trestle validate -a
Both the catalog and the profile should validate with 0 schema errors. The catalog import resolved cleanly and the profile references a catalog that exists in the workspace.
Validation reporting errors after commit?
Make sure you are running trestle validate -a from inside the trestle-workspace directory (where .trestle/ lives). Running it from the repository root will fail with a workspace-not-found error.
Help me get trestle validate -a passing with 0 errors
✔️ Awesome, I've got everything!
Great. Confirm your commit includes the trestle-workspace/ directory with catalog and profile, docs/decisions/ with 5 ADR files, and docs/topology-diagram.md. All files are saved and committed.
ⓧ I'd like to double check the full code
Scroll up and confirm you have each of these committed artifacts in order:
- trestle-workspace/catalogs/nist-800-53-r5/catalog.json (imported from NIST).
- trestle-workspace/profiles/cnssi-1253-il6-overlay/profile.json (the profile you created).
- docs/decisions/ADR-001-commercial-cloud-simulation.md through ADR-005-no-bare-abbreviations.md (5 files total).
- docs/topology-diagram.md with three labeled boundaries and key labels section.
Your categorization is locked, your decisions are auditable, and your topology shows exactly where each boundary begins and ends. Next, you will turn this diagram into infrastructure that the scanners can interrogate.
Build the Enclave Baseline
In Step 1, you categorized the system, committed decision records, and drew the topology. The enclave exists as a diagram and a profile.
A diagram is not infrastructure the scanners can interrogate. This step makes the topology real: a Terraform configuration that is closed by construction, so an accidental egress path is a plan-time failure visible in terraform plan, not a runtime discovery.
In this step, get ready to:
- Write the Terraform enclave VPC, Transit Gateway, and inspection VPC with Network Firewall.
- Configure hardened AMIs, deny-all security groups, FIPS 140-3 KMS key, and SPIFFE/SPIRE workload identity.
- Run all 4 policy-as-code scanners against the plan at 0 violations.
Write the enclave network infrastructure
The enclave VPC has no internet gateway, no NAT gateway, and private subnets only. The Transit Gateway models the SIPRNet boundary using route-table isolation: the enclave attachment sits on its own route table with propagation disabled.
- Create the terraform/ directory and the variables file by running:
mkdir -p terraform
cd terraform
- Create terraform/variables.tf with the following content:
variable "aws_region" {
description = "AWS region for enclave resources"
default = "us-gov-west-1"
}
variable "enclave_cidr" {
description = "CIDR block for the enclave VPC"
default = "10.0.0.0/16"
}
variable "shared_services_cidr" {
description = "CIDR block for shared-services VPC"
default = "10.1.0.0/16"
}
variable "spire_server_port" {
description = "gRPC-over-TLS port for SPIRE server"
default = 8081
}
variable "spire_workload_api_port" {
description = "TLS port for SPIRE Workload API"
default = 443
}
variable "kms_key_alias" {
description = "Alias for the enclave FIPS KMS key"
default = "alias/enclave-fips-key"
}
What do these variables control?
Each variable maps to a decision from Step 1. The two CIDR ranges define the isolated routing domains. The SPIRE ports (8081 for the server, 443 for the Workload API) are the only traffic the enclave security groups will ever allow out.
- Create terraform/main.tf starting with the provider and enclave VPC:
provider "aws" {
region = var.aws_region
}
# Enclave VPC: closed network, no IGW, no NAT
resource "aws_vpc" "enclave" {
cidr_block = var.enclave_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = { Name = "enclave-vpc" }
}
resource "aws_subnet" "enclave_private_a" {
vpc_id = aws_vpc.enclave.id
cidr_block = cidrsubnet(var.enclave_cidr, 8, 1)
availability_zone = "${var.aws_region}a"
map_public_ip_on_launch = false
tags = { Name = "enclave-private-a" }
}
resource "aws_subnet" "enclave_private_b" {
vpc_id = aws_vpc.enclave.id
cidr_block = cidrsubnet(var.enclave_cidr, 8, 2)
availability_zone = "${var.aws_region}b"
map_public_ip_on_launch = false
tags = { Name = "enclave-private-b" }
}
Why no internet gateway or NAT gateway?
The CC SRG requires IL6 infrastructure to be a closed, self-contained environment connected only to SIPRNet. By omitting both an internet gateway and a NAT gateway, the enclave VPC has zero paths to the public internet by construction. Any resource that tries to reach the internet will fail at the routing layer before a packet leaves the VPC.
- Initialize the Terraform working directory by running:
terraform init
You should see Terraform download the AWS provider and print "Terraform has been successfully initialized."
Seeing a provider error?
Confirm your AWS CLI is configured with valid credentials. Run aws sts get-caller-identity to verify.
If the provider download fails behind a proxy, check HTTPS_PROXY is set correctly. Help me debug my Terraform init failure.
- Add the Transit Gateway with route-table isolation to terraform/main.tf:
# Transit Gateway: isolated routing per ADR-002
resource "aws_ec2_transit_gateway" "main" {
default_route_table_association = "disable"
default_route_table_propagation = "disable"
tags = { Name = "enclave-tgw" }
}
resource "aws_ec2_transit_gateway_route_table" "enclave_isolated" {
transit_gateway_id = aws_ec2_transit_gateway.main.id
tags = { Name = "enclave-isolated-rt" }
}
resource "aws_ec2_transit_gateway_route_table" "shared_services" {
transit_gateway_id = aws_ec2_transit_gateway.main.id
tags = { Name = "shared-services-rt" }
}
# Enclave attachment: propagation disabled
resource "aws_ec2_transit_gateway_vpc_attachment" "enclave" {
subnet_ids = [aws_subnet.enclave_private_a.id, aws_subnet.enclave_private_b.id]
transit_gateway_id = aws_ec2_transit_gateway.main.id
vpc_id = aws_vpc.enclave.id
tags = { Name = "enclave-tgw-attachment" }
}
resource "aws_ec2_transit_gateway_route_table_association" "enclave" {
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.enclave.id
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.enclave_isolated.id
}
What does disabling propagation accomplish?
When propagation is disabled on a Transit Gateway route table, no routes from attached VPCs are automatically learned. The enclave-isolated route table starts with zero routes. The only routes it will ever have are the explicit ones you add. This means the enclave cannot discover or reach any other VPC unless you deliberately create a static route.
- Add blackhole routes and the inspection VPC (the cloud-access-point-equivalent) to terraform/main.tf:
# Blackhole routes: block cross-VPC and default traffic
resource "aws_ec2_transit_gateway_route" "blackhole_shared" {
destination_cidr_block = var.shared_services_cidr
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.enclave_isolated.id
blackhole = true
}
resource "aws_ec2_transit_gateway_route" "blackhole_default" {
destination_cidr_block = "0.0.0.0/0"
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.enclave_isolated.id
blackhole = true
}
# Inspection VPC: cloud-access-point-equivalent
resource "aws_vpc" "inspection" {
cidr_block = var.shared_services_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = { Name = "inspection-vpc" }
}
resource "aws_subnet" "inspection_a" {
vpc_id = aws_vpc.inspection.id
cidr_block = cidrsubnet(var.shared_services_cidr, 8, 1)
availability_zone = "${var.aws_region}a"
tags = { Name = "inspection-subnet-a" }
}
# Network Firewall in inspection VPC for deep packet inspection
resource "aws_networkfirewall_firewall" "inspection" {
name = "enclave-inspection-fw"
firewall_policy_arn = aws_networkfirewall_firewall_policy.inspection.arn
vpc_id = aws_vpc.inspection.id
subnet_mapping {
subnet_id = aws_subnet.inspection_a.id
}
tags = { Name = "inspection-fw" }
}
resource "aws_networkfirewall_firewall_policy" "inspection" {
name = "enclave-inspection-policy"
firewall_policy {
stateless_default_actions = ["aws:forward_to_sfe"]
stateless_fragment_default_actions = ["aws:forward_to_sfe"]
}
tags = { Name = "inspection-fw-policy" }
}
# Inspection VPC attachment on shared-services route table
resource "aws_ec2_transit_gateway_vpc_attachment" "inspection" {
subnet_ids = [aws_subnet.inspection_a.id]
transit_gateway_id = aws_ec2_transit_gateway.main.id
vpc_id = aws_vpc.inspection.id
tags = { Name = "inspection-tgw-attachment" }
}
resource "aws_ec2_transit_gateway_route_table_association" "inspection" {
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.inspection.id
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.shared_services.id
}
What does the blackhole route model?
The blackhole route for the shared-services CIDR explicitly drops any traffic from the enclave that would reach the inspection VPC or beyond. Combined with the 0.0.0.0/0 blackhole, the enclave cannot route anywhere. This models the stalled connection state: the connection approval has not been granted, so no traffic flows. In Step 5, you will inject a route that violates this isolation.
Before you run the plan, do you expect Terraform to show any routes to an internet gateway or NAT gateway from the enclave VPC?
- Verify the network infrastructure by running:
terraform plan -out=tfplan
The plan should show resources to create (VPCs, subnets, Transit Gateway, routes, Network Firewall) and zero references to aws_internet_gateway or aws_nat_gateway. The enclave has no path to the internet by construction.
Plan fails with missing variable?
Make sure you are running terraform plan from inside the terraform/ directory where variables.tf and main.tf live. Help me fix my Terraform plan error.
Configure the hardening and identity layer
The enclave has no internet. Now it needs hardened images, locked-down egress rules, FIPS 140-3 cryptography, and SPIFFE/SPIRE workload identity so that a compromised network position does not confer trust.
- Add hardened AMI data sources and security groups to terraform/main.tf:
# Hardened AMI: CIS-benchmarked base image
data "aws_ami" "cis_hardened" {
most_recent = true
owners = ["self"]
filter {
name = "name"
values = ["cis-hardened-al2-*"]
}
}
# Security group: deny-all egress, allowlist SPIRE only
resource "aws_security_group" "enclave_workload" {
name = "enclave-workload-sg"
vpc_id = aws_vpc.enclave.id
tags = { Name = "enclave-workload-sg" }
}
resource "aws_vpc_security_group_egress_rule" "deny_all" {
security_group_id = aws_security_group.enclave_workload.id
ip_protocol = "-1"
cidr_ipv4 = "0.0.0.0/0"
description = "Explicit deny-all baseline"
}
resource "aws_vpc_security_group_egress_rule" "allow_spire" {
security_group_id = aws_security_group.enclave_workload.id
ip_protocol = "tcp"
from_port = var.spire_server_port
to_port = var.spire_server_port
cidr_ipv4 = var.enclave_cidr
description = "SPIRE server gRPC-over-TLS"
}
Why deny-all egress with an allowlist?
A deny-all egress baseline means workloads cannot reach anything by default. The only exceptions are the SPIRE server port (8081) and the inspection VPC endpoint. If an attacker gains code execution inside a container, they cannot exfiltrate data because there is no egress route to reach.
- Add the KMS key with the FIPS 140-3 validated policy condition to terraform/main.tf:
# KMS key: FIPS 140-3 validated per ADR-003
resource "aws_kms_key" "enclave" {
description = "Enclave encryption key - FIPS 140-3"
enable_key_rotation = true
deletion_window_in_days = 30
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "RequireFIPS"
Effect = "Deny"
Principal = "*"
Action = "kms:*"
Resource = "*"
Condition = {
Bool = { "aws:SecureTransport" = "false" }
}
}]
})
tags = { Name = var.kms_key_alias }
}
resource "aws_kms_alias" "enclave" {
name = var.kms_key_alias
target_key_id = aws_kms_key.enclave.key_id
}
- Validate the syntax so far by running:
terraform validate
You should see "Success! The configuration is valid."
- Add the SPIRE server and agent ECS task definitions with the aws_iid node attestor to terraform/main.tf:
# ECS cluster for SPIRE workloads
resource "aws_ecs_cluster" "enclave" {
name = "enclave-spire-cluster"
tags = { Name = "enclave-ecs" }
}
# SPIRE server task definition with aws_iid attestor
resource "aws_ecs_task_definition" "spire_server" {
family = "spire-server"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = "512"
memory = "1024"
container_definitions = jsonencode([{
name = "spire-server"
image = "ghcr.io/spiffe/spire-server:1.15.2"
portMappings = [{
containerPort = var.spire_server_port
protocol = "tcp"
}]
environment = [
{ name = "SPIRE_ATTESTOR", value = "aws_iid" }
]
}])
tags = { Name = "spire-server-task" }
}
# SPIRE agent task with workload registration
resource "aws_ecs_task_definition" "spire_agent" {
family = "spire-agent"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = "256"
memory = "512"
container_definitions = jsonencode([{
name = "spire-agent"
image = "ghcr.io/spiffe/spire-agent:1.15.2"
portMappings = [{
containerPort = var.spire_workload_api_port
protocol = "tcp"
}]
environment = [
{ name = "SPIRE_SERVER_ADDRESS", value = "spire-server.enclave.local" },
{ name = "SPIRE_SERVER_PORT", value = tostring(var.spire_server_port) },
{ name = "SPIRE_TRUST_DOMAIN", value = "enclave.mil" },
{ name = "SPIRE_WORKLOAD_ID", value = "spiffe://enclave.mil/workload" }
]
}])
tags = { Name = "spire-agent-task" }
}
What does the aws_iid attestor do?
The aws_iid (AWS Instance Identity Document) attestor verifies that a node belongs to a specific AWS account before issuing it a SPIFFE identity. Network position alone is not enough to gain trust. A compromised container on the wrong subnet cannot impersonate a legitimate workload because it cannot produce a valid instance identity document for the enclave account.
- Create terraform/outputs.tf to expose key resource identifiers:
output "transit_gateway_id" {
value = aws_ec2_transit_gateway.main.id
}
output "enclave_vpc_id" {
value = aws_vpc.enclave.id
}
output "inspection_vpc_id" {
value = aws_vpc.inspection.id
}
output "kms_key_arn" {
value = aws_kms_key.enclave.arn
}
output "spire_server_task_arn" {
value = aws_ecs_task_definition.spire_server.arn
}
- Run the full plan to confirm the complete configuration is valid:
terraform plan -out=tfplan
Terraform should show all enclave resources, Transit Gateway, inspection VPC, Network Firewall, KMS key, ECS cluster, and SPIRE task definitions in the plan output. No errors.
ECS task definition error?
If Terraform reports a JSON encoding error in container_definitions, check that all jsonencode blocks have matching brackets and no trailing commas. Help me fix my ECS task definition.
Run the 4-scanner gate
The infrastructure is defined. Now you prove it meets policy. Four scanners interrogate the Terraform plan JSON: Conftest (OPA Rego), Checkov, KICS, and Trivy. The gate passes only at 0 violations across all four.
- Create the OPA Rego policy file at policies/enclave.rego:
cd ..
mkdir -p policies
package main
# Deny any internet gateway in enclave VPC
deny[msg] {
resource := input.planned_values.root_module.resources[_]
resource.type == "aws_internet_gateway"
msg := "Enclave VPC must not have an internet gateway"
}
# Deny any NAT gateway in enclave VPC
deny[msg] {
resource := input.planned_values.root_module.resources[_]
resource.type == "aws_nat_gateway"
msg := "Enclave VPC must not have a NAT gateway"
}
# Deny public subnets in enclave VPC
deny[msg] {
resource := input.planned_values.root_module.resources[_]
resource.type == "aws_subnet"
resource.values.map_public_ip_on_launch == true
msg := sprintf("Subnet %s must not map public IPs", [resource.address])
}
# Deny routes outside enclave CIDR in isolated route table
deny[msg] {
resource := input.planned_values.root_module.resources[_]
resource.type == "aws_ec2_transit_gateway_route"
resource.values.blackhole != true
msg := sprintf("Non-blackhole route %s in enclave route table", [resource.address])
}
# Require FIPS key policy on KMS keys
deny[msg] {
resource := input.planned_values.root_module.resources[_]
resource.type == "aws_kms_key"
not contains(resource.values.policy, "SecureTransport")
msg := "KMS key must enforce FIPS transport condition"
}
What do these policies enforce?
The five deny rules cover the enclave-specific invariants: no public subnet, no internet gateway, no NAT gateway, no non-blackhole routes from the enclave, and FIPS key policy present. Conftest evaluates these rules against the Terraform plan JSON. A single violation means the gate fails and the enclave is not closed by construction.
- Generate the plan JSON and run Conftest by executing:
cd terraform
terraform show -json ./tfplan > tfplan.json
conftest test -p ../policies/ tfplan.json
Before you check the output, do you expect any violations? The plan has no IGW, no NAT, no public subnets, only blackhole routes, and the KMS policy includes SecureTransport.
You should see 0 failures. Save the output for your records:
conftest test -p ../policies/ tfplan.json > ../scanner-results/step2-conftest.txt 2>&1
cat ../scanner-results/step2-conftest.txt
- Create the scanner-results directory and run the remaining three scanners:
mkdir -p ../scanner-results
checkov -f tfplan.json --framework terraform_plan -o cli > ../scanner-results/step2-checkov.txt 2>&1
cat ../scanner-results/step2-checkov.txt
kics scan -p . -t Terraform --no-progress > ../scanner-results/step2-kics.txt 2>&1
cat ../scanner-results/step2-kics.txt
trivy config ./tfplan.json > ../scanner-results/step2-trivy.txt 2>&1
cat ../scanner-results/step2-trivy.txt
All 4 scanners should report 0 violations. The plan shows 0 routes to any internet gateway or NAT gateway from the enclave VPC.
What does each scanner check?
- Conftest evaluates your custom OPA Rego policies (no IGW, no NAT, no public subnet, FIPS key, blackhole-only routes).
- Checkov runs built-in CKV_AWS checks against the Terraform plan for known AWS misconfigurations.
- KICS scans Terraform files for infrastructure misconfigurations and security weaknesses.
- Trivy scans the plan for known CVEs in referenced providers and modules.
Scanner reports a violation?
If Checkov flags a missing VPC flow log, add aws_flow_log resources for both VPCs or use a skip annotation for simulation purposes. If KICS reports a findings-level warning rather than a critical violation, verify the severity level. Only critical and high findings constitute gate failures for this project. Help me resolve a scanner violation in my enclave Terraform plan.
✔️ Awesome, I've got everything!
Confirm you have saved all files in terraform/ and policies/.
ⓧ I'd like to double check the full code
Scroll up and confirm each of these chunks is present in your terraform/main.tf, in order:
- Provider block and enclave VPC with two private subnets.
- Transit Gateway, two route tables, enclave attachment, and route table association.
- Blackhole routes (shared-services CIDR and 0.0.0.0/0).
- Inspection VPC, subnet, Network Firewall, firewall policy, inspection attachment and association.
- Hardened AMI data source and security group with deny-all egress and SPIRE allowlist.
- KMS key with FIPS policy and alias.
- ECS cluster, SPIRE server task definition, and SPIRE agent task definition.
Confirm terraform/variables.tf has 6 variables (region, enclave CIDR, shared-services CIDR, SPIRE server port, SPIRE workload API port, KMS key alias).
Confirm terraform/outputs.tf has 5 outputs (Transit Gateway ID, enclave VPC ID, inspection VPC ID, KMS key ARN, SPIRE server task ARN).
Confirm policies/enclave.rego has 5 deny rules covering IGW, NAT, public subnet, non-blackhole routes, and FIPS key policy.
The enclave is closed by construction and proved clean by 4 independent scanners. Next, you will author the OSCAL control set that turns this infrastructure into auditable evidence.
Assemble the Connection-Approval-Equivalent Package
The enclave exists in infrastructure and its controls are documented in OSCAL. But none of that matters yet because the enclave has no approval to connect.
The connection is the system. Without the approval package, the enclave is an island with no path to any other network. This step assembles the five artifacts an approver would actually read, shaped to match the DoD classified-side connection approval path.
In this step, get ready to:
- Draft the authorization decision document referencing the OSCAL SSP by UUID.
- Author the ports-protocols-and-services registration and signed consent-to-monitor.
- Document the registration and connection workflow modeled on the DISN classified-side path.
Create the package directory and draft the authorization decision document
The authorization decision document is the cover sheet of the package. It names what the system is, who authorizes it, and what boundary it claims.
- Create the package directory and the authorization decision document by running these commands:
mkdir package
Now retrieve your SSP UUID. You will need it in the authorization decision document.
- Find the metadata.uuid field in your SSP by running:
grep '"uuid"' trestle-workspace/system-security-plans/enclave-ssp.json | head -1
- Copy the UUID value. You will paste it into the document below.
- Create the authorization decision document by saving the following content to package/authorization-decision-document.md:
# Authorization Decision Document (Equivalent)
## System Identification
- **System Name:** Mission Enclave (Simulated IL6)
- **Categorization:** High-High-High with Classified Information Overlay applied as-equivalent
- **Impact Level:** IL6-equivalent per DoD CC SRG
- **Authorizing Official:** [Mock] Chief Information Security Officer
## Authorization Boundary
The mission authorization boundary encompasses the enclave VPC, Transit Gateway attachment on the enclave-isolated route table, SPIRE workload identity infrastructure, and KMS encryption services. The provider inheritance boundary (AWS IaaS controls) sits beneath. The network connection boundary is governed by this connection approval.
## Connection Scope
The enclave connects exclusively through the cloud-access-point-equivalent inspection VPC (AWS Network Firewall). No direct internet egress path exists by construction.
## SSP Reference
- **SSP Artifact:** trestle-workspace/system-security-plans/enclave-ssp.json
- **SSP UUID:** [[SSP_UUID="UUID from your enclave-ssp.json metadata.uuid field"]]
## Control Baseline
CNSSI 1253 Rev 5 with Classified Information Overlay, tailored over NIST SP 800-53 Rev 5 Release 5.2.0. Eight of nine families evidenced. PS recorded as simulation assumption (ADR-001).
What does this document do?
The authorization decision document is the equivalent of a cover memo for the package. It pins down four things a reviewer needs immediately.
- The categorization (High-High-High plus the Classified Information Overlay) establishes which control baseline applies.
- The authorization boundary section names the three distinct boundaries from your topology diagram so the reviewer knows exactly what this system is answerable for.
- The SSP UUID creates a machine-traceable link between this human-readable document and the OSCAL evidence chain you built in Step 3.
- Save package/authorization-decision-document.md.
- Confirm the file exists by running:
ls package/
You should see authorization-decision-document.md listed.
Don't see the file?
- Make sure you created the package/ directory first with mkdir package.
- Check that you saved the file inside package/ and not the project root.
Why isn't my authorization decision document appearing in the package directory?
Draft the ports-protocols-and-services registration and consent-to-monitor
The ports-protocols-and-services registration is the most constrained artifact in the package. It names the only traffic the enclave is allowed to carry. Everything else is denied by construction.
- Retrieve your SPIRE and Network Firewall component UUIDs. You will need them for the next two documents:
grep '"uuid"' trestle-workspace/component-definitions/spire.json | head -1
grep '"uuid"' trestle-workspace/component-definitions/network-firewall.json | head -1
- Copy both UUIDs. Now create the ports-protocols-and-services registration by saving the following content to package/ports-protocols-services.yaml:
# Ports, Protocols, and Services Registration
# Cross-references: enclave-ssp.json component IDs
system_name: "Mission Enclave (Simulated IL6)"
ssp_reference: "trestle-workspace/system-security-plans/enclave-ssp.json"
allowed_protocols:
- protocol: "TLS 1.3"
port: 443
direction: "inbound"
purpose: "SPIRE server communication"
component_id: "[[SPIRE_COMPONENT_ID=\"component UUID from spire.json\"]]"
justification: "SPIRE agents attest to the SPIRE server over mutual TLS"
- protocol: "gRPC-over-TLS"
port: 8081
direction: "inbound"
purpose: "Workload API"
component_id: "[[SPIRE_COMPONENT_ID=\"component UUID from spire.json\"]]"
justification: "Workloads retrieve SVIDs from the local SPIRE agent"
denied_by_construction:
mechanism:
- "Blackhole route in enclave-isolated Transit Gateway route table"
- "Deny-all egress security group on all enclave subnets"
statement: "All traffic not matching the two allowed protocols is denied."
firewall_component_id: "[[FIREWALL_COMPONENT_ID=\"component UUID from network-firewall.json\"]]"
Why only two protocols?
The enclave is closed by construction. The only traffic it can carry is SPIRE attestation (TLS 1.3 on port 443) and workload identity retrieval (gRPC-over-TLS on port 8081). The blackhole route and deny-all security group make this a plan-time guarantee, not a runtime hope.
A reviewer reading this YAML can confirm in seconds that the enclave carries exactly two flows. If a third appears in the Terraform plan, the scanner gate catches it before it ever deploys.
- Save package/ports-protocols-services.yaml.
- Confirm the two ports are recorded by running:
grep 'port:' package/ports-protocols-services.yaml
You should see two lines: one showing port: 443 and one showing port: 8081.
Seeing only one port or none?
- Check the YAML indentation. Each protocol entry must be indented under allowed_protocols: with consistent spacing.
- Make sure both entries use the port: key (lowercase, with a colon and space before the number).
Help me fix my ports-protocols-services YAML file so it shows both allowed ports.
- Now create the consent-to-monitor document by saving the following content to package/consent-to-monitor.md:
# Consent to Monitor (Equivalent)
## Acknowledgment
All traffic transiting the cloud-access-point-equivalent inspection VPC is subject to deep packet inspection by AWS Network Firewall. This includes TLS 1.3 traffic on port 443 and gRPC-over-TLS traffic on port 8081.
## Scope
The inspection VPC sits on the shared-services Transit Gateway route table. All enclave egress passes through the Network Firewall stateful rule engine before reaching any destination outside the enclave authorization boundary.
## SSP Cross-Reference
- **Network Firewall Component ID:** [[FIREWALL_COMPONENT_ID="component UUID from network-firewall.json"]]
- **SSP Artifact:** trestle-workspace/system-security-plans/enclave-ssp.json
## Signatories
- [Mock] System Owner, Mission Enclave
- [Mock] Information System Security Officer
Signed: [Simulation - no actual signature authority exercised]
What is a consent-to-monitor?
In a real classified connection, the consent-to-monitor is a signed acknowledgment that all traffic through the cloud access point is subject to inspection. It is not optional. Without it, the Connection Approval Office cannot issue the authorization to connect.
Here the inspection is performed by AWS Network Firewall in the inspection VPC. The document names the component ID so a reviewer can trace back to the OSCAL evidence that the firewall is actually configured.
- Save package/consent-to-monitor.md.
- Confirm both new files exist by running:
ls package/
You should now see three files: authorization-decision-document.md, consent-to-monitor.md, and ports-protocols-services.yaml.
Draft the registration workflow and complete the package
The registration and connection workflow is the process document. It tells a reviewer exactly what steps the Connection Approval Office would follow to evaluate this package, mapped to the 4-scanner gate you already built.
- Create the registration workflow document by saving the following content to package/registration-workflow.md:
# Registration and Connection Workflow (DISN Classified-Side Equivalent)
## Overview
This document models the steps a Connection Approval Office would follow to review this package, conduct the remote compliance assessment, and issue the authorization to connect (ATC) or interim authorization to connect (IATC).
## Workflow Steps
### 1. Package Submission
The system owner submits the connection-approval-equivalent package to the Connection Approval Office. Required artifacts: authorization decision document, ports-protocols-and-services registration, topology diagram, and consent-to-monitor.
### 2. Completeness Review
The Connection Approval Office verifies all artifacts are present and cross-reference the SSP by component ID. The cloud access point configuration is validated against the topology diagram.
### 3. Remote Compliance Assessment
The Connection Approval Office conducts a remote compliance assessment mapped to the 4-scanner gate (Conftest, Checkov, KICS, Trivy). All scanners must report 0 violations.
### 4. Authorization Decision
Upon successful assessment, the Connection Approval Office issues an authorization to connect (ATC) or interim authorization to connect (IATC). The connection approval process requires all findings resolved before ATC issuance.
## Naming Convention
Per ADR-005: the connection approval process and the cloud access point are named separately in every reference. No bare abbreviations are used.
Why map the scanner gate to the remote compliance assessment?
In the real DISN classified-side path, the Connection Approval Office conducts an online remote compliance assessment (vulnerability scan) for SIPRNet new connections. Your 4-scanner gate (Conftest, Checkov, KICS, Trivy at 0 violations) is the simulation equivalent. It proves the same thing: the infrastructure meets the stated baseline before the connection is authorized.
- Save package/registration-workflow.md.
- Retrieve your Transit Gateway component UUID for the final document:
grep '"uuid"' trestle-workspace/component-definitions/transit-gateway.json | head -1
- Create the topology diagram reference by saving the following content to package/topology-diagram-ref.md:
# Package Topology Diagram Reference
## Source Artifact
The canonical topology diagram is maintained at: docs/topology-diagram.md
## Three Boundaries Annotated
1. **Mission Authorization Boundary** - Enclave VPC and all components (SPIRE, KMS, hardened workloads)
2. **Provider Inheritance Boundary** - AWS IaaS controls inherited beneath the mission boundary
3. **Network Connection Boundary** - Transit Gateway attachment and cloud-access-point-equivalent inspection VPC
## Cloud-Access-Point-Equivalent
The inspection VPC with AWS Network Firewall serves as the cloud-access-point-equivalent. All traffic between the enclave and any external network transits this inspection point.
## SSP Cross-References
- **Transit Gateway Component ID:** [[TGW_COMPONENT_ID="component UUID from transit-gateway.json"]]
- **Network Firewall Component ID:** [[FIREWALL_COMPONENT_ID="component UUID from network-firewall.json"]]
- **SPIRE Component ID:** [[SPIRE_COMPONENT_ID="component UUID from spire.json"]]
Why a separate topology reference instead of copying the diagram?
The canonical topology lives at docs/topology-diagram.md and is maintained as the single source of truth. The package reference annotates it with the three boundaries and SSP component IDs so a reviewer can trace each boundary element back to its OSCAL evidence without duplicating the diagram.
- Save package/topology-diagram-ref.md.
Before you check the final package listing, predict: how many files should you see in the package/ directory?
- Verify the complete package by running:
ls package/
You should see exactly 5 files:
- authorization-decision-document.md
- consent-to-monitor.md
- ports-protocols-services.yaml
- registration-workflow.md
- topology-diagram-ref.md
- Confirm the PPS registration names both allowed ports by running:
grep 'port:' package/ports-protocols-services.yaml
You should see port: 443 and port: 8081. These are the only two flows the enclave is permitted to carry.
Seeing fewer than 5 files?
- Check that each file was saved inside the package/ directory, not the project root or another location.
- Verify file names match exactly. The topology reference must be topology-diagram-ref.md (not topology-diagram.md, which is the source artifact in docs/).
- Run find package/ -type f to see the full paths of everything in the directory.
Help me find which package artifacts are missing or misplaced.
Your connection-approval-equivalent package is complete. Five artifacts, each cross-referencing the OSCAL SSP, each traceable to a specific infrastructure component. Next up, you will inject a finding that stalls this entire approval and prove the boundary can survive a challenge.
Inject the Finding and Re-Design
Your enclave has a complete connection-approval-equivalent package, but the boundary has never been challenged. A boundary that has never met a reviewer is a diagram, not a design.
In this step, you will inject an unaccredited cross-domain path into the enclave routing domain. This route would carry data between the enclave (simulated SECRET) and the shared-services VPC (simulated UNCLASSIFIED) without a separate cross-domain authorization. The connection approval cannot proceed with this finding open. You will watch it get caught, remediate it, and prove the boundary holds.
In this step, get ready to:
- Inject a cross-domain finding into the Transit Gateway enclave-isolated route table and observe the scanner violation.
- Remediate the finding by removing the injected route and adding an explicit blackhole route for the shared-services CIDR.
- Re-run the full 4-scanner gate at 0 violations and update the POA&M and topology diagram.
Inject the cross-domain finding
The finding you are about to create models a real-world scenario: a route that carries data between two networks of different classification levels without a separate cross-domain authorization. In a real enclave, this would be discovered during a compliance assessment and would immediately stall the connection approval.
- Open terraform/main.tf in your editor.
- Find the enclave-isolated route table resource (aws_ec2_transit_gateway_route_table.enclave_isolated).
- Add the following static route resource below it. This propagates the shared-services VPC CIDR into the enclave routing domain, creating the unaccredited cross-domain path:
# INJECTED FINDING: Unauthorized cross-domain route
# This route propagates the shared-services VPC CIDR into the
# enclave routing domain, creating an unaccredited cross-domain path.
resource "aws_ec2_transit_gateway_route" "injected_cross_domain" {
destination_cidr_block = var.shared_services_vpc_cidr
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.shared_services.id
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.enclave_isolated.id
}
What does this route do?
This route tells the enclave-isolated route table how to reach the shared-services VPC. In a real classified environment, this would mean SECRET-level traffic could flow to an UNCLASSIFIED network without a cross-domain solution in between.
The Rego policy you wrote in Step 2 checks every route in the enclave route table and flags any destination outside the enclave CIDR. This injected route should trigger that exact check.
- Save terraform/main.tf.
- Generate the Terraform plan JSON by running these commands:
cd terraform
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
Before you run the Conftest policy gate, predict: do you think the scanner will pass or fail? Which policy rule will catch this route?
- Run the Conftest policy gate against the plan JSON:
conftest test --policy ../policies tfplan.json
You will see at least 1 violation. The Rego policy detects a route in the enclave route table whose destination is outside the enclave CIDR.
- Save the Conftest output to the scanner results directory:
conftest test --policy ../policies tfplan.json > ../scanner-results/step5-injection-conftest.txt 2>&1
Why inject a finding on purpose?
A real Connection Approval Office conducts a remote compliance assessment (vulnerability scan) before issuing an authorization to connect. By injecting this finding yourself, you prove two things: your policy gate catches unauthorized flows at plan time, and the remediation path works end-to-end before anyone else reviews the package.
Conftest shows 0 violations?
Confirm the injected route's transit_gateway_route_table_id references aws_ec2_transit_gateway_route_table.enclave_isolated.id. If it references a different route table, the policy will not flag it as an enclave violation.
Also verify your Rego policy in policies/enclave.rego checks for routes whose destination CIDR does not match the enclave CIDR.
Help me debug why Conftest is not catching the injected cross-domain route in my enclave policy.
Remediate the finding and update the POA&M
The finding is now visible and documented. The connection approval is stalled. Your remediation must do two things: remove the unauthorized path and add an explicit blackhole route so no residual flow path exists, even if propagation is accidentally re-enabled in the future.
- In terraform/main.tf, find the injected route resource you just added:
resource "aws_ec2_transit_gateway_route" "injected_cross_domain" {
destination_cidr_block = var.shared_services_vpc_cidr
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.shared_services.id
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.enclave_isolated.id
}
- Delete the entire injected_cross_domain resource block.
- Replace it with an explicit blackhole route for the shared-services CIDR. This ensures no traffic can reach the shared-services VPC from the enclave, even if route propagation is accidentally enabled:
# REMEDIATION: Explicit blackhole for shared-services CIDR
# Ensures no residual cross-domain flow path exists, even if
# propagation is accidentally re-enabled in the future.
resource "aws_ec2_transit_gateway_route" "blackhole_shared_services" {
destination_cidr_block = var.shared_services_vpc_cidr
blackhole = true
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.enclave_isolated.id
}
Why a blackhole instead of just deleting the route?
Deleting the route removes the immediate violation, but leaves the door unlocked. If someone accidentally enables propagation on the enclave route table in the future, the shared-services CIDR could re-appear as a learned route.
An explicit blackhole route means that even if the CIDR propagates, traffic to that destination is dropped at the Transit Gateway. This is defense in depth: the blackhole is the belt, disabled propagation is the suspenders.
- Also confirm that the aws_ec2_transit_gateway_route_table_association for the enclave VPC attachment has transit_gateway_default_route_table_propagation set to false. This should already be the case from Step 2, but verify it is still present.
- Save terraform/main.tf.
Now update the OSCAL POA&M to record the finding lifecycle.
- Open trestle-workspace/plan-of-action-and-milestones/enclave-poam.json.
- Add a finding entry that records the full lifecycle. Use this structure:
{
"poam-id": "POAM-001",
"title": "Unaccredited cross-domain path via injected static route",
"description": "Static route in enclave-isolated route table propagated shared-services VPC CIDR into enclave routing domain without cross-domain authorization.",
"date-opened": "2026-08-05T10:00:00Z",
"remediation": "Injected route removed. Explicit blackhole route added for shared-services CIDR. Propagation disabled and verified.",
"date-closed": "2026-08-05T10:15:00Z",
"status": "closed"
}
What does this POA&M entry prove?
The timestamps create an auditable trail: the finding was opened, a specific remediation was applied (blackhole route plus propagation check), and the finding was closed. When a reviewer reads the package, they see that the system was tested against its own controls, a gap was found, and the gap was closed with evidence.
This is the difference between a system that claims compliance and one that has been through a finding cycle.
- Save enclave-poam.json.
Not sure what timestamps to use?
Use the current date and time for both timestamps. The key requirement is that date-closed is after date-opened and both are in ISO 8601 format. In a real assessment these would be the actual times of discovery and closure.
Help me structure the POA&M finding entry with correct timestamps and fields.
Re-run the full verification and update the topology
The remediation is in place and the POA&M is documented. Now you need to prove the boundary holds under the full scanner battery and update the topology diagram to reflect the blackhole route.
- Regenerate the Terraform plan JSON from the terraform/ directory:
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
Before you run the scanners, predict: should the plan show 0 routes to the shared-services VPC from the enclave, or will the blackhole route appear in the diff?
- Run all 4 scanners against the remediated plan and save each output:
conftest test --policy ../policies tfplan.json > ../scanner-results/step5-remediated-conftest.txt 2>&1
checkov -f tfplan.json > ../scanner-results/step5-remediated-checkov.txt 2>&1
kics scan -p tfplan.json > ../scanner-results/step5-remediated-kics.txt 2>&1
trivy config tfplan.json > ../scanner-results/step5-remediated-trivy.txt 2>&1
All 4 scanners report 0 violations. The blackhole route appears in the plan as a new resource, but the Rego policy recognizes blackhole routes as valid isolation controls, not as unauthorized cross-VPC paths.
Still seeing a violation after remediation?
Confirm you completely removed the injected_cross_domain resource. If any reference to it remains (even commented out), regenerate the plan. Also check that the new resource uses blackhole = true and does not include a transit_gateway_attachment_id (blackhole routes drop traffic, they do not forward it).
Help me debug why my remediated Terraform plan still fails the Conftest policy gate.
- Validate the updated POA&M with trestle:
cd ../trestle-workspace
trestle validate -a
The output shows the POA&M validates clean with the finding closed and 0 open items.
- Open docs/topology-diagram.md and update it to show the explicit blackhole route for the shared-services CIDR in the enclave-isolated route table. Label it as the remediation for the injected cross-domain finding.
- Update package/topology-diagram-ref.md to reference the revised topology diagram showing the blackhole route remediation.
- Save both files.
- Commit all changes:
cd ..
git add .
git commit -m "Step 5: Finding injected, remediated, and verified at 0 violations"
What did this step prove?
You proved the enclave's controls work under adversarial conditions. The scanner caught the unauthorized route at plan time (before any infrastructure changed), the remediation closed the gap with no residual flow, and the full evidence chain is machine-readable in OSCAL.
The connection-approval-equivalent package is now complete. A reviewer can trace the finding from injection through remediation to closure, with scanner evidence at every stage.
Your enclave has now survived its first compliance assessment. The finding was the design review. Next up, you can brief a mock authorizing official on the full lifecycle in the Secret Mission.
Secret mission
Brief the Mock Authorizing Official
The authorization is not a document. It is a boundary, a control set, and evidence an authorizing official can stand behind. Produce the signature briefing artifact that a mock authorizing official could read and either sign or return with findings.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running or delete them entirely. This project deploys AWS infrastructure that accrues hourly charges while running.
Cost warning
The Transit Gateway costs approximately $0.05 per hour while deployed. AWS Network Firewall and the KMS key also accrue charges (the KMS key is $1/month if not deleted). If you are finished with the project, run terraform destroy to stop all charges immediately.
Resources you used:
- AWS Transit Gateway with route tables (hourly charge while deployed).
- AWS Network Firewall in the inspection VPC (hourly charge while deployed).
- AWS KMS key with FIPS 140-3 validated policy ($1/month while active).
- VPC, subnets, security groups, and ECS task definitions.
- Local project directory on your Desktop (Terraform files, OSCAL workspace, documentation, scanner results).
- GitHub private repository.
Keep everything running
No action needed. Choose this if you are still actively iterating on the enclave design or connection-approval package.
- Your Transit Gateway and Network Firewall continue to accrue hourly charges.
- The KMS key accrues $1/month.
- Your local project files and GitHub repository remain untouched.
Pause - I'll come back to this later
Terraform-managed infrastructure does not have a true pause state. Resources are either deployed or destroyed.
- If you want to stop charges but keep your code, run terraform destroy to remove the AWS resources. Your Terraform files remain on disk so you can re-deploy later with terraform apply.
- Your local project directory and GitHub repository stay intact for when you return.
Delete - I don't want to use this again
Remove all project resources and start fresh. This is the recommended path after the build is complete.
- Destroy all AWS infrastructure by running this command from the terraform/ directory:
cd terraform
terraform destroy
- When prompted, type yes to confirm destruction.
- Verify in the AWS console that the Transit Gateway, Network Firewall, and KMS key no longer appear. Open the VPC console and confirm the enclave VPC is deleted.
Remove local project files by running:
cd ~/Desktop
rm -rf enclave-project
On Windows, use:
cd ~\Desktop
Remove-Item -Recurse -Force enclave-project
- Delete the GitHub repository by opening the repository page, clicking Settings, scrolling to the Danger Zone section, and clicking Delete this repository. Confirm by typing the repository name when prompted.
That's a Wrap!
That's a Wrap!
Nice work! You just built a SECRET-level mission enclave simulation in commercial AWS, proved it closed by construction, and assembled the documentation package an authorizing official would actually read.
You've learned how to:
- Build a Terraform enclave baseline validated by 4 policy-as-code scanners at 0 violations, with 0 public egress paths and SPIFFE/SPIRE workload identity enforcing zero-trust at the workload layer.
- Assemble a connection-approval-equivalent package modeled on the DoD classified-side path, with 8 of 9 control families evidenced in OSCAL and inheritance documented from the provider authorization boundary.
- Inject a cross-domain finding that stalled the connection approval, re-design the boundary with a blackhole route, and clear the finding with no residual flow path. The transferable pattern: categorize, tailor, build, package, find, re-design, brief.
- Secret Mission: Brief a mock authorizing official with the three-boundary diagram (mission authorization boundary, provider inheritance boundary, network connection boundary) and deliver the lessons-learned presentation artifact.
Ready to quiz yourself?