|
| 1 | +--- |
| 2 | +description: 'Guidelines for writing, reviewing, and maintaining cloudformation templates' |
| 3 | +applyTo: 'cloudformation/**' |
| 4 | +--- |
| 5 | + |
| 6 | +This file is mastered in https://github.com/NHSDigital/eps-copilot-instructions and is automatically synced to all EPS repositories. To suggest changes, please open an issue or pull request in the eps-copilot-instructions repository. |
| 7 | + |
| 8 | +## General |
| 9 | +- Prefer YAML (not JSON). Follow existing style in [cloudformation/account_resources.yml](cloudformation/account_resources.yml), [cloudformation/ci_resources.yml](cloudformation/ci_resources.yml), [cloudformation/artillery_resources.yml](cloudformation/artillery_resources.yml), [cloudformation/account_resources_bootstrap.yml](cloudformation/account_resources_bootstrap.yml), [cloudformation/management.yml](cloudformation/management.yml). |
| 10 | +- Always start with `AWSTemplateFormatVersion: "2010-09-09"`. |
| 11 | +- Keep descriptions concise (> operator used only for multi‑line). |
| 12 | +- Use logical region `eu-west-2` unless cross‑region behavior explicitly required. |
| 13 | +- Maintain tagging pattern: version, stack, repo, cfnDriftDetectionGroup (see deployment scripts in [.github/scripts/release_code.sh](.github/scripts/release_code.sh) and [.github/scripts/create_changeset_existing_tags.sh](.github/scripts/create_changeset_existing_tags.sh)). |
| 14 | + |
| 15 | +## Parameters |
| 16 | +- Reuse and align parameter naming with existing templates: `LogRetentionDays`, `Env`, `SplunkHECEndpoint`, `DeployDriftDetection`. |
| 17 | +- For numeric retention days replicate allowed values list from [SAMtemplates/lambda_resources.yaml](SAMtemplates/lambda_resources.yaml) or [cloudformation/account_resources.yml](cloudformation/account_resources.yml). |
| 18 | +- Use `CommaDelimitedList` for OIDC subject claim filters like in [cloudformation/ci_resources.yml](cloudformation/ci_resources.yml). |
| 19 | + |
| 20 | +## Conditions |
| 21 | +- Follow pattern `ShouldDeployDriftDetection` (see [SAMtemplates/lambda_resources.yaml](SAMtemplates/lambda_resources.yaml)); avoid ad‑hoc condition names. |
| 22 | +- If creating a never-used placeholder stack use pattern from [cloudformation/empty_stack.yml](cloudformation/empty_stack.yml). |
| 23 | + |
| 24 | +## IAM Policies |
| 25 | +- Split large CloudFormation execution permissions across multiple managed policies (A, B, C, D) to keep each rendered size < 6144 chars (see check logic in [scripts/check_policy_length.py](scripts/check_policy_length.py)). |
| 26 | +- Scope resources minimally; prefer specific ARNs (e.g. logs, KMS aliases) as in [cloudformation/account_resources.yml](cloudformation/account_resources.yml). |
| 27 | +- When granting CloudFormation execution access: separate IAM‑focused policy (`GrantCloudFormationExecutionAccessIAMPolicy`) from broad service policies. |
| 28 | +- Use exports for policy ARNs with naming `ci-resources:GrantCloudFormationExecutionAccessPolicyA` pattern. |
| 29 | + |
| 30 | +## KMS |
| 31 | +- Alias naming: `alias/CloudwatchLogsKmsKeyAlias`, `alias/SecretsKMSKeyAlias`, `alias/ArtifactsBucketKMSKeyAlias` (see [cloudformation/account_resources.yml](cloudformation/account_resources.yml), [cloudformation/account_resources_bootstrap.yml](cloudformation/account_resources_bootstrap.yml)). |
| 32 | +- Grant encrypt/decrypt explicitly for principals (e.g. API Gateway, Lambda) mirroring key policy blocks in [cloudformation/account_resources.yml](cloudformation/account_resources.yml). |
| 33 | + |
| 34 | +## Secrets / Parameters |
| 35 | +- SecretsManager resources must depend on alias if needed (`DependsOn: SecretsKMSKeyKMSKeyAlias`) like in [cloudformation/account_resources_bootstrap.yml](cloudformation/account_resources_bootstrap.yml). |
| 36 | +- Export secret IDs (not ARNs unless specifically required) using colon-separated naming with stack name (pattern in outputs section of account templates). |
| 37 | +- Default placeholder value `ChangeMe` for bootstrap secrets. |
| 38 | + |
| 39 | +## S3 Buckets |
| 40 | +- Apply `PublicAccessBlockConfiguration` and encryption blocks consistent with [cloudformation/account_resources.yml](cloudformation/account_resources.yml). |
| 41 | +- Suppress guard rules using `Metadata.guard.SuppressedRules` where legacy exceptions exist (e.g. replication / logging) matching existing patterns. |
| 42 | + |
| 43 | +## Lambda / SAM |
| 44 | +- Shared lambda resources belong in SAM template ([SAMtemplates/lambda_resources.yaml](SAMtemplates/lambda_resources.yaml)); CloudFormation templates should not duplicate build-specific metadata. |
| 45 | +- Suppress cfn-guard rules where justified via `Metadata.guard.SuppressedRules` (e.g. `LAMBDA_INSIDE_VPC`, `LAMBDA_CONCURRENCY_CHECK`) only if precedent exists. |
| 46 | + |
| 47 | +## Exports & Cross Stack |
| 48 | +- Output export naming pattern: `!Join [":", [!Ref "AWS::StackName", "ResourceLogicalName"]]`. |
| 49 | +- Reference exports via `!ImportValue stack-name:ExportName` (see Proxygen role usage in [SAMtemplates/lambda_resources.yaml](SAMtemplates/lambda_resources.yaml)). |
| 50 | +- Avoid changing existing export names (breaking downstream stacks and scripts). |
| 51 | + |
| 52 | +## OIDC / Roles |
| 53 | +- Federated trust for GitHub actions must use conditions: |
| 54 | + - `token.actions.githubusercontent.com:aud: sts.amazonaws.com` |
| 55 | + - `ForAnyValue:StringLike token.actions.githubusercontent.com:sub: <ClaimFilters>` |
| 56 | + (pattern in roles inside [cloudformation/ci_resources.yml](cloudformation/ci_resources.yml)). |
| 57 | +- When adding a new OIDC role add matching parameter `<RoleName>ClaimFilters` and outputs `<RoleName>` and `<RoleName>Name`. |
| 58 | + |
| 59 | +## Drift Detection |
| 60 | +- Tag stacks with `cfnDriftDetectionGroup` (deployment scripts handle this). Config rules should filter on `TagKey: cfnDriftDetectionGroup` and specific `TagValue` (patterns in [SAMtemplates/lambda_resources.yaml](SAMtemplates/lambda_resources.yaml)). |
| 61 | +- Avoid duplicating rule identifiers; follow `${AWS::StackName}-CloudFormationDriftDetector-<Group>`. |
| 62 | + |
| 63 | +## Route53 |
| 64 | +- Environment hosted zones template ([cloudformation/eps_environment_route53.yml](cloudformation/eps_environment_route53.yml)) uses parameter `environment`; management template updates NS records referencing environment zones. |
| 65 | + |
| 66 | +## Style / Lint / Guard |
| 67 | +- Keep resources grouped with `#region` / `#endregion` comments as in existing templates for readability. |
| 68 | +- Use `Metadata.cfn-lint.config.ignore_checks` only when upstream spec mismatch (example: W3037 in large policy templates). |
| 69 | +- Ensure new templates pass `make lint-cloudformation` and `make cfn-guard` (scripts: [scripts/run_cfn_guard.sh](scripts/run_cfn_guard.sh)). |
| 70 | + |
| 71 | +## Naming Conventions |
| 72 | +- Logical IDs: PascalCase (`ArtifactsBucketKMSKey`, `CloudFormationDeployRole`). |
| 73 | +- Managed policy logical IDs end with `Policy` or `ManagedPolicy`. |
| 74 | +- KMS Key alias logical IDs end with `Alias` (e.g. `CloudwatchLogsKmsKeyAlias`). |
| 75 | +- Secrets logical IDs end with `Secret`. |
| 76 | + |
| 77 | +## Security |
| 78 | +- Block public access for all buckets unless explicitly required. |
| 79 | +- Encrypt logs with KMS key; provide alias export (see `CloudwatchLogsKmsKeyAlias`). |
| 80 | +- Limit wildcard `Resource: "*"` where service requires (e.g. some IAM, CloudFormation actions). Prefer service/resource ARNs otherwise. |
| 81 | + |
| 82 | +## When Adding New Resource Types |
| 83 | +- Update execution policies in [cloudformation/ci_resources.yml](cloudformation/ci_resources.yml) minimally; do not expand existing broad statements unnecessarily. |
| 84 | +- Run policy length check (`make test` invokes [scripts/check_policy_length.py](scripts/check_policy_length.py)) after modifications. |
| 85 | + |
| 86 | +## Do Not |
| 87 | +- Do not hardcode account IDs; use `${AWS::AccountId}`. |
| 88 | +- Do not remove existing exports or rename keys. |
| 89 | +- Do not inline large policy statements in a single managed policy if size risk exists. |
| 90 | + |
| 91 | +## Examples |
| 92 | +- IAM Role with OIDC trust: replicate structure from `CloudFormationDeployRole` in [cloudformation/ci_resources.yml](cloudformation/ci_resources.yml). |
| 93 | +- KMS key + alias + usage policy: follow `ArtifactsBucketKMSKey` block in [cloudformation/account_resources.yml](cloudformation/account_resources.yml). |
| 94 | + |
| 95 | +## Testing |
| 96 | +- After changes: run `make lint-cloudformation` and `make cfn-guard`. |
| 97 | +- For SAM-related cross-stack exports ensure `sam build` (see [Makefile](Makefile)) passes. |
| 98 | + |
| 99 | +## Automation Awareness |
| 100 | +- Deployment scripts expect unchanged parameter names & export patterns (see [.github/scripts/execute_changeset.sh](.github/scripts/execute_changeset.sh), [.github/scripts/release_code.sh](.github/scripts/release_code.sh)). |
| 101 | +- Changes to tagging keys must be reflected in release / changeset scripts; avoid unless necessary. |
| 102 | + |
| 103 | +## Preferred Patterns Summary |
| 104 | +- Exports: colon join |
| 105 | +- Tags: version, stack, repo, cfnDriftDetectionGroup |
| 106 | +- Conditions: prefixed with `Should` |
| 107 | +- Claim filter parameters: `<RoleName>ClaimFilters` |
| 108 | +- Secrets: depend on KMS alias, default `ChangeMe` |
| 109 | + |
| 110 | +Use these rules to guide completions for any new or modified CloudFormation template in this repository. |
0 commit comments