Skip to main content

Terraform Resources (Organization Management)

Last Updated: 2026-03-06 Source: https://github.com/co-cddo/ndx-try-aws-terraform Captured SHA: 4df9750

Executive Summary

The ndx-try-aws-terraform repository provides minimal Terraform configuration for NDX organization-level resources that fall outside the scope of both the Landing Zone Accelerator and the Innovation Sandbox platform. It provisions an encrypted, versioned S3 bucket for Terraform remote state storage and creates an IAM role granting MFA-protected, IP-restricted billing read-only access to three designated GDS users. The repository uses AWS provider v6.22+ with Terraform v1.14+ and stores its own state in the S3 bucket it creates.

Resource Architecture


Resources Managed

1. Terraform State S3 Bucket

The S3 bucket ndx-try-tf-state provides remote state storage for Terraform configurations across the NDX organization, including this repository's own state (bootstrapped locally, then migrated).

Resource Configuration:

ResourceTypePurpose
aws_s3_bucket.ndx_try_tf_stateaws_s3_bucketBucket ndx-try-tf-state
aws_s3_bucket_versioning.ndx_try_tf_state_versioningaws_s3_bucket_versioningVersioning enabled for rollback
aws_s3_bucket_server_side_encryption_configuration.ndx_try_tf_state_encryptionaws_s3_bucket_server_side_encryption_configurationKMS encryption (aws:kms)
aws_kms_key.ndx_try_tf_state_kms_keyaws_kms_keyDedicated encryption key (10-day deletion window)
aws_s3_bucket_ownership_controls.private_storageaws_s3_bucket_ownership_controlsObjectWriter ownership
aws_s3_bucket_acl.ndx_try_tf_state_aclaws_s3_bucket_aclPrivate ACL

Security Features:

  • KMS encryption with a dedicated key (not AWS-managed)
  • Versioning enabled for state rollback capability
  • Private ACL prevents public access
  • ObjectWriter ownership controls ensure the bucket creator owns uploaded objects

2. Billing Access IAM Role

The billing-access IAM role grants read-only billing, cost, and organization visibility to three GDS (Government Digital Service) users. Access requires both MFA authentication and source IP restriction to GDS network ranges.

Assume Role Conditions:

ConditionTypeValues
MFABool: aws:MultiFactorAuthPresenttrue (required)
Source IPIpAddress: aws:SourceIp6 GDS CIDR ranges

Allowed Source IPs:

CIDRNetwork
217.196.229.77/32GovWifi
217.196.229.79/32Brattain
217.196.229.80/32GDS BYOD VPN
217.196.229.81/32GDS VPN
51.149.8.0/25GDS/CO VPN
51.149.8.128/29GDS BYOD VPN

Attached Policies (3):

PolicyTypePermissions
billing-readonlyCustomaws-portal:View*, ce:Get*/List*/Describe*, health:Describe*, s3:HeadBucket, s3:ListAllMyBuckets, cur:DescribeReportDefinition, organizations:ListTagsForResource
AWSOrganizationsReadOnlyAccessAWS ManagedFull read-only access to Organizations
AWSBillingReadOnlyAccessAWS ManagedFull read-only access to Billing console

Provider and Backend Configuration

Provider (terraform.tf)

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.22"
}
}
required_version = ">= 1.14"
}

The AWS provider targets region us-west-2 as the primary deployment region.

Backend

State is stored in the S3 bucket that this same Terraform configuration creates:

backend "s3" {
bucket = "ndx-try-tf-state"
key = "state/terraform.tfstate"
region = "us-west-2"
}

This creates a bootstrap dependency: the bucket must be created with local state first, then the backend is migrated to S3 via terraform init -migrate-state. The state stored in this bucket is also consumed by the ndx-try-aws-scp repository (stored at key scp-overrides/terraform.tfstate in the same bucket).


Relationship to Other IaC Repositories

IaC Responsibility Matrix

No overlap between repositories:

  • LZA manages organizational structure, accounts, security baselines, and compliance SCPs
  • Terraform SCP manages Innovation Sandbox cost defense layers
  • This repository manages organization-level utility resources (state storage, billing access)
  • ISB CDK manages the Innovation Sandbox application and satellite services

The S3 bucket in this repository serves as shared state infrastructure for both this repo (key: state/terraform.tfstate) and the SCP repo (key: scp-overrides/terraform.tfstate).


Deployment

Initial Bootstrap

# 1. Clone and init with local state
git clone https://github.com/co-cddo/ndx-try-aws-terraform.git
cd ndx-try-aws-terraform

# 2. Create S3 bucket (local state initially)
terraform init
terraform apply

# 3. Migrate state to S3
terraform init -migrate-state

Ongoing Updates

terraform init
terraform plan
terraform apply

Usage: Billing Access

# Assume billing role (requires MFA and GDS network)
aws sts assume-role \
--role-arn arn:aws:iam::MANAGEMENT_ACCOUNT:role/billing-access \
--role-session-name billing-session \
--serial-number arn:aws:iam::622626885786:mfa/USER \
--token-code 123456

# Query cost data
aws ce get-cost-and-usage \
--time-period Start=2026-02-01,End=2026-03-01 \
--granularity DAILY \
--metrics BlendedCost

Security Considerations

  • The KMS key for the state bucket uses a 10-day deletion window, providing a safeguard against accidental key deletion
  • The billing role requires both MFA and IP-based conditions, implementing defense-in-depth for privileged access
  • The billing role is scoped to read-only actions across billing, cost, and organization services with no write permissions
  • The S3 bucket does not have a DynamoDB lock table configured; consider adding state locking for concurrent access protection
  • GDS user account ID (622626885786) is hardcoded as a trusted principal


Source Files Referenced

File PathPurpose
repos/ndx-try-aws-terraform/main.tfS3 bucket, KMS key, IAM role, and policy definitions
repos/ndx-try-aws-terraform/terraform.tfProvider version constraints and S3 backend configuration
repos/ndx-try-aws-terraform/README.mdRepository context and scope description

Generated from source analysis. See 00-repo-inventory.md for full inventory.