From 462459bf95a2d5a1671a66afffe0455ad20eb580 Mon Sep 17 00:00:00 2001 From: Min Ong Date: Fri, 20 Mar 2026 11:44:40 +0000 Subject: [PATCH] docs: standardise agent context files, architecture docs, and .agents/ structure - AGENTS.md: single source of truth for all AI coding assistants - CLAUDE.md: lightweight pointer to AGENTS.md with Claude-specific extensions - docs/ARCHITECTURE.md: Mermaid diagrams and component descriptions - .agents/: tool-agnostic rules and commands - README.md: updated to complement agent files Co-Authored-By: Claude Opus 4.6 --- .agents/rules/code-style.md | 33 +++++++++++ .agents/rules/security.md | 27 +++++++++ .agents/rules/testing.md | 33 +++++++++++ .gitignore | 2 + AGENTS.md | 60 ++++++++++++++++++++ CLAUDE.md | 12 ++++ README.md | 45 ++++++++++++++- docs/ARCHITECTURE.md | 109 ++++++++++++++++++++++++++++++++++++ 8 files changed, 318 insertions(+), 3 deletions(-) create mode 100644 .agents/rules/code-style.md create mode 100644 .agents/rules/security.md create mode 100644 .agents/rules/testing.md create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 docs/ARCHITECTURE.md diff --git a/.agents/rules/code-style.md b/.agents/rules/code-style.md new file mode 100644 index 00000000..993c0589 --- /dev/null +++ b/.agents/rules/code-style.md @@ -0,0 +1,33 @@ +# Code Style Rules + +## Terraform + +- Run `terraform fmt` before committing. This is enforced by pre-commit. +- Use `snake_case` for resource names, variable names, and local values. +- Use 2-space indentation (Terraform default). +- Align `=` signs within a block for readability (as seen in `control_tower_parameters`). +- Prefix account names with `local.account_name_prefix` (`attest-ct`). +- Keep one module invocation per AWS account in `terraform/main.tf`. +- Define all module variables with explicit `type` and `description` in `variables.tf`. +- Use `default` values only for truly optional variables. + +## Naming Conventions + +- Account module names: lowercase, underscores (e.g., `shared_services`, `new_ventures`). +- Account customizations names: lowercase, hyphens (e.g., `shared-services`, `new-ventures`). +- Account aliases: `attest-ct-{environment}` (3-63 characters). +- Email pattern: `aws-attest-ct+{environment}@askattest.com`. + +## Jinja Templates + +- Files `aft-providers.jinja` and `backend.jinja` are auto-generated by AFT at runtime. Do not manually edit the generated output files (`backend.tf`, `providers.tf`). + +## Pre-commit + +All pre-commit hooks must pass before merging: +- `terraform_fmt` -- formatting +- `terraform_validate` -- syntax validation +- `terraform_tflint` -- linting +- `terraform_trivy` -- security scanning +- `terraform_docs` -- documentation generation +- `trailing-whitespace`, `end-of-file-fixer`, `check-yaml`, `check-added-large-files` diff --git a/.agents/rules/security.md b/.agents/rules/security.md new file mode 100644 index 00000000..977ba4a6 --- /dev/null +++ b/.agents/rules/security.md @@ -0,0 +1,27 @@ +# Security Rules + +## Sensitive Data + +- **Never** commit `.tfvars` files. They are gitignored and may contain secrets. +- **Never** commit `.tfstate` files. State is stored remotely in S3 with KMS encryption. +- **Never** commit `backend.tf` or `providers.tf`. These are auto-generated from Jinja templates by AFT at runtime. +- Email addresses in `control_tower_parameters` are organizational emails, not personal. This is acceptable for account provisioning. + +## Terraform Security + +- **Trivy scanning** is enforced via pre-commit (`terraform_trivy` hook). All findings must be resolved before merging. +- **TFLint** catches common misconfigurations. +- The AWS provider assumes an IAM role (`aft_admin_role_arn`) with least-privilege scoping managed by AFT. +- Backend state is encrypted at rest (S3 + KMS) and uses DynamoDB for state locking. + +## Access Control + +- This repository provisions AWS accounts via Control Tower. Changes have high blast radius. +- All changes should go through pull request review. +- The `SSOUserEmail` field determines the initial SSO admin for each account -- verify this is intentional before changing. + +## Account Provisioning + +- New accounts are automatically enrolled in AWS Control Tower guardrails. +- The `ManagedOrganizationalUnit` determines which OU policies (SCPs) apply. Changing this has security implications. +- `account_customizations_name` links to customizations in a separate AFT repo -- verify those customizations before associating them. diff --git a/.agents/rules/testing.md b/.agents/rules/testing.md new file mode 100644 index 00000000..3109aec4 --- /dev/null +++ b/.agents/rules/testing.md @@ -0,0 +1,33 @@ +# Testing Rules + +## Validation + +This repository has no unit tests in the traditional sense. Validation is performed via: + +1. **`terraform validate`** -- Ensures HCL syntax and configuration are valid. Run via pre-commit. +2. **`terraform_tflint`** -- Lints Terraform files for errors, best practices, and provider-specific issues. Run via pre-commit. +3. **`terraform_trivy`** -- Scans for security misconfigurations. Run via pre-commit. + +## Running Checks Locally + +```bash +# Using mise task (runs all pre-commit hooks) +mise run precommit + +# Or directly +pre-commit run --all-files +``` + +## Before Submitting Changes + +- Run `mise run precommit` and ensure all hooks pass. +- Verify that new account modules have all required variables populated: + - `control_tower_parameters` (all 6 fields) + - `account_tags` (at minimum `Name` and `ManagedBy`) + - `change_management_parameters` (`change_requested_by`, `change_reason`) + - `account_customizations_name` +- Verify the `AccountEmail` is unique across all modules (it serves as the DynamoDB hash key). + +## AFT Pipeline Validation + +The AFT pipeline (AWS CodePipeline) runs `terraform plan` and `terraform apply` in the AFT management account. Changes merged to main are automatically picked up by the pipeline. diff --git a/.gitignore b/.gitignore index 8055a04c..b813b9e8 100644 --- a/.gitignore +++ b/.gitignore @@ -243,3 +243,5 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk +CLAUDE.local.md +.claude/settings.local.json diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..ca742446 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,60 @@ +# AGENTS.md -- aft-account-request + +## Project Overview + +This repository defines **AWS account requests** for provisioning via [AWS Control Tower Account Factory for Terraform (AFT)](https://developer.hashicorp.com/terraform/tutorials/aws/aws-control-tower-aft). Each account is declared as a Terraform module invocation that writes a record to the AFT DynamoDB `aft-request` table. + +## Tech Stack + +| Layer | Technology | +|-------|-----------| +| IaC | Terraform >= 0.15.0 (pinned to 1.13.3 via mise) | +| Cloud | AWS (Control Tower, DynamoDB, S3, IAM, SSO) | +| Provider | hashicorp/aws >= 3.15 | +| Tooling | mise, pre-commit, tflint, trivy, terraform-docs | +| License | MPL 2.0 | + +## Repository Structure + +``` +terraform/ + main.tf # Account request declarations (one module per account) + versions.tf # Root Terraform + provider version constraints + aft-providers.jinja # Jinja template: auto-generated provider config + backend.jinja # Jinja template: auto-generated backend config + modules/ + aft-account-request/ + ddb.tf # Writes account request item to DynamoDB + variables.tf # Module input variables + versions.tf # Module version constraints +.pre-commit-config.yaml # Pre-commit hooks config +mise.toml # Tool version management +``` + +## How to Add a New Account + +1. Add a new `module` block in `terraform/main.tf`. +2. Use `source = "./modules/aft-account-request"`. +3. Populate all required variables: + - `control_tower_parameters`: AccountEmail (unique), AccountName, ManagedOrganizationalUnit, SSO user info + - `account_tags`: at minimum `Name` and `ManagedBy = "AFT"` + - `change_management_parameters`: who requested and why (Shortcut link) + - `account_customizations_name`: links to customizations in the AFT account customizations repo +4. Follow naming conventions in `.agents/rules/code-style.md`. +5. Run `mise run precommit` to validate. +6. Open a pull request. Once merged, the AFT CodePipeline applies the change. + +## Key Rules for AI Agents + +- **Read-only awareness**: Jinja templates (`*.jinja`) are processed by AFT at runtime. Do not create or modify `backend.tf` or `providers.tf` directly. +- **DynamoDB key**: `AccountEmail` is the hash key in the `aft-request` table. It must be unique across all modules. +- **High blast radius**: This repo provisions AWS accounts. Every change should be reviewed carefully. +- Refer to `.agents/rules/` for code-style, testing, and security guidelines. +- Refer to `docs/ARCHITECTURE.md` for system architecture and data flow. + +## Keeping Docs Current + +When making changes: +- If you add/remove an account, update `README.md` account table and `docs/ARCHITECTURE.md` diagram. +- If you change tooling or versions, update `AGENTS.md` tech stack table. +- If you change module variables, update `AGENTS.md` "How to Add a New Account" section. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..dcdfe7a0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,12 @@ +@AGENTS.md +@.agents/rules/code-style.md +@.agents/rules/testing.md +@.agents/rules/security.md +@docs/ARCHITECTURE.md + + +### Response Preferences +- Be concise. Prefer code over prose. +- When uncertain about architecture, read `docs/ARCHITECTURE.md` before assuming. +- When a change would invalidate any section of `AGENTS.md` or `README.md`, + flag it and offer to update them per the instructions in AGENTS.md. diff --git a/README.md b/README.md index d8fcc9af..4d096597 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,44 @@ # Account Factory for Terraform: Account Requests -Built using [Provision and Manage Accounts with -Control Tower Account Factory for Terraform -tutorial](https://developer.hashicorp.com/terraform/tutorials/aws/aws-control-tower-aft). +Defines AWS account requests for provisioning via [AWS Control Tower Account Factory for Terraform (AFT)](https://developer.hashicorp.com/terraform/tutorials/aws/aws-control-tower-aft). + +Each module invocation in `terraform/main.tf` represents an AWS account to be created and managed by Control Tower. + +## Accounts + +| Account | Organizational Unit | Customizations | +|---------|---------------------|----------------| +| attest-ct-shared-services | Infrastructure | shared-services | +| attest-ct-dev | Workloads | dev | +| attest-ct-qa | Workloads | qa | +| attest-ct-prod | Workloads | prod | +| attest-ct-new-ventures | Workloads | new-ventures | +| attest-ct-dr | DisasterRecovery | dr | + +## Prerequisites + +Tool versions are managed by [mise](https://mise.jdx.dev/). Run `mise install` to set up: + +- Terraform 1.13.3 +- tflint 0.59.1 +- trivy 0.66.0 +- terraform-docs 0.20.0 +- Python 3.13.7 + pre-commit + +## Usage + +```bash +# Run all pre-commit checks (fmt, validate, lint, security scan) +mise run precommit +``` + +To add a new account, see [AGENTS.md](AGENTS.md#how-to-add-a-new-account). + +## Documentation + +- [AGENTS.md](AGENTS.md) -- Project overview, structure, and contribution guide +- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) -- System architecture with diagrams + +## License + +[Mozilla Public License 2.0](LICENSE) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 00000000..a40a8666 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,109 @@ +# Architecture -- aft-account-request + +## Overview + +This repository is one component of the AWS Account Factory for Terraform (AFT) system. It defines which AWS accounts should exist under AWS Control Tower. When changes are merged, the AFT pipeline reads the account requests from a DynamoDB table and provisions or updates accounts accordingly. + +## System Context + +```mermaid +graph LR + Dev["Developer"] -->|PR + merge| Repo["aft-account-request repo"] + Repo -->|triggers| Pipeline["AFT CodePipeline
(AFT Management Account)"] + Pipeline -->|terraform apply| DDB["DynamoDB
aft-request table"] + DDB -->|triggers| AFT["AFT Account Provisioning"] + AFT -->|creates/updates| CT["AWS Control Tower"] + CT -->|provisions| Accounts["AWS Accounts"] +``` + +## Data Flow + +```mermaid +sequenceDiagram + participant Dev as Developer + participant GH as Git Repository + participant CP as AFT CodePipeline + participant TF as Terraform + participant DDB as DynamoDB (aft-request) + participant AFT as AFT Engine + participant CT as Control Tower + + Dev->>GH: Merge PR to main + GH->>CP: Webhook trigger + CP->>TF: terraform plan + apply + TF->>DDB: Write/update account request item + DDB->>AFT: Change detected (stream) + AFT->>CT: Provision/update account + CT-->>AFT: Account ready + AFT->>AFT: Apply account customizations +``` + +## Terraform Module Structure + +```mermaid +graph TD + Root["terraform/main.tf"] --> SS["module.shared_services"] + Root --> Dev["module.dev"] + Root --> QA["module.qa"] + Root --> Prod["module.prod"] + Root --> NV["module.new_ventures"] + Root --> DR["module.dr"] + + SS --> Mod["modules/aft-account-request"] + Dev --> Mod + QA --> Mod + Prod --> Mod + NV --> Mod + DR --> Mod + + Mod --> DDB["aws_dynamodb_table_item
(aft-request table)"] +``` + +## Account Topology + +```mermaid +graph TD + Org["AWS Organization"] --> Infra["OU: Infrastructure"] + Org --> Workloads["OU: Workloads"] + Org --> DRou["OU: DisasterRecovery"] + + Infra --> SS["attest-ct-shared-services"] + Workloads --> DevAcc["attest-ct-dev"] + Workloads --> QAAcc["attest-ct-qa"] + Workloads --> ProdAcc["attest-ct-prod"] + Workloads --> NVAcc["attest-ct-new-ventures"] + DRou --> DRAcc["attest-ct-dr"] +``` + +## Module Interface + +The `aft-account-request` module accepts: + +| Variable | Type | Required | Description | +|----------|------|----------|-------------| +| `control_tower_parameters` | object | Yes | AccountEmail, AccountName, ManagedOrganizationalUnit, SSO user details | +| `account_tags` | map(any) | Yes | Account-level tags (Name, ManagedBy) | +| `change_management_parameters` | object | Yes | Who requested the change and why | +| `custom_fields` | map(any) | No | Arbitrary metadata (group, description, account_alias) | +| `account_customizations_name` | string | No | Name of customizations to apply from AFT customizations repo | + +The module writes a single DynamoDB item to the `aft-request` table, keyed by `AccountEmail`. + +## Related AFT Repositories + + + +| Repository | Purpose | +|------------|---------| +| `aft-account-request` (this repo) | Defines which accounts to provision | +| `aft-account-customizations` | Per-account Terraform customizations | +| `aft-global-customizations` | Customizations applied to all accounts | +| `aft-account-provisioning-customizations` | Pre/post provisioning hooks | + +## Backend Configuration + +Terraform state is stored remotely: +- **S3 bucket** with KMS encryption (configured via `backend.jinja`) +- **DynamoDB table** for state locking +- IAM role assumption via `aft_admin_role_arn` +- Backend config is auto-generated by AFT from `backend.jinja` -- never edit `backend.tf` directly.