cloud-foundation-principles/skills/secrets-and-configuration-management/SKILL.md
This skill should be used when the user is storing credentials, managing API keys, setting up secret rotation, designing secret naming conventions, creating database users, managing environment-specific configuration, or deciding how applications should access secrets at runtime. Covers the one-secret-per-service pattern, account-based environment isolation, KMS encryption, role-based database users, and the infrastructure wiring exception for parameter stores.
npx skillsauth add oborchers/fractional-cto secrets-and-configuration-managementInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The most common infrastructure mistake with credentials is over-engineering the separation. Splitting database passwords into a secrets manager, endpoints into a parameter store, and feature flags into environment variables creates three access patterns, three IAM policies, and a Terraform apply every time you change a connection string.
Put everything in one secret per service. One JSON blob containing every environment variable the service needs -- credentials, endpoints, flags, all of it. Store it in a secrets manager with customer-managed encryption. The application reads and parses the secret at startup. Done.
One secret per service. Each service has exactly one secret: /{service}/env. It contains a JSON object with every env var the service needs -- database passwords next to database hosts, API keys next to feature flags. No separation between "secrets" and "configuration."
Account = environment. The secret path is /{service}/env in every account. Dev account, prod account, staging account -- same path. The AWS account provides the isolation (see multi-account-from-day-one skill). Application code never needs to know which environment it runs in. This mirrors the internal DNS pattern (see network-architecture skill) where db.internal resolves to the right database per VPC -- same hostname everywhere, different values per environment.
Change without Terraform. Update the secret value in the console or CLI, force a container redeploy. No plan/apply cycle for config changes. Terraform creates the secret resource and sets the initial value; the team manages the value thereafter.
Customer-managed encryption. Every secret is encrypted with a KMS key you control, not the provider default. This enables key rotation, cross-account access policies, and decryption audit trails.
Rotation is a spectrum. Database credentials can auto-rotate if you invest in the rotation Lambda. Third-party API keys (Stripe, SendGrid) rarely rotate in practice. Don't let perfect rotation block shipping. Start with KMS encryption and proper access scoping; add rotation when it matters.
{
"DB_HOST": "mydb.cluster-xxx.eu-west-1.rds.amazonaws.com",
"DB_PORT": "5432",
"DB_PASSWORD": "auto-rotated-or-manual",
"REDIS_URL": "redis://cache.internal:6379",
"SENDGRID_API_KEY": "SG.xxx",
"FEATURE_V2_API": "true"
}
One JSON object. One secret ARN. One IAM policy statement. The application reads /{service}/env at startup, parses the JSON, and populates its environment.
# Good: one secret, app reads and parses at startup
resource "aws_secretsmanager_secret" "env" {
name = "/${var.service_name}/env"
kms_key_id = aws_kms_key.secrets.arn
}
resource "aws_ecs_task_definition" "myapp" {
# ...
container_definitions = jsonencode([{
name = var.service_name
environment = [{
name = "SECRET_NAME"
value = "/${var.service_name}/env"
}]
}])
}
# Bad: plaintext password in environment variable via Terraform
resource "aws_ecs_task_definition" "myapp" {
container_definitions = jsonencode([{
name = "myapp"
environment = [{
name = "DB_PASSWORD"
value = "hunter2" # Plaintext in state file, logs, and console
}]
}])
}
# Bad: separate stores for secrets and configuration
/prod/myapp/db-password --> Secrets Manager
/prod/myapp/db-host --> SSM Parameter Store
/prod/myapp/redis-url --> SSM Parameter Store
/prod/myapp/sendgrid-api-key --> Secrets Manager
# Two stores, two access patterns, two IAM policies,
# and a Terraform apply to change a connection string.
Dev account (123456789012):
/myapp/env (all env vars for myapp)
/myapp/db-app_rw (database role credential)
/myapp/db-analytics_ro (database role credential)
Prod account (987654321098):
/myapp/env (all env vars for myapp)
/myapp/db-app_rw (database role credential)
/myapp/db-analytics_ro (database role credential)
Same paths. Same code. Different accounts. Application code reads /{service}/env -- it never needs to know whether it runs in dev or prod.
Database role credentials are the exception to the one-blob rule. They are stored as individual secrets at /{service}/db-{role} because they are shared across consumers (app service, analytics service, RDS proxy) and auto-rotation targets individual secrets, not keys inside a blob.
The one-secret-per-service pattern covers everything an application needs at runtime. But cross-service infrastructure dependencies -- where one Terraform module's output feeds another module's input -- need a different mechanism.
Use SSM Parameter Store (or Terraform remote state) for infrastructure wiring:
These values change when infrastructure changes. They must flow through Terraform or a parameter store so dependent modules pick up the new value automatically. They are not application config -- they are infrastructure bindings.
# Infrastructure wiring: consumed by Terraform modules, not application code
resource "aws_ssm_parameter" "db_endpoint" {
name = "/${var.service_name}/infra/db-endpoint"
type = "String"
value = aws_db_instance.main.address
tags = local.tags
}
Database users follow the same principle. Never create person-specific database accounts (john_doe, jane_smith). Create role-based users that describe purpose and access level.
Use abbreviated access suffixes for brevity (see naming-and-labeling-as-code skill for the canonical naming conventions):
api_rw -- API service (read + write)
api_ro -- API service (read only, e.g., replica queries)
dashboard_ro -- Analytics/BI tools (read only, all tables)
migration_admin -- Schema migration runner (DDL permissions, time-boxed)
generic_ro -- All team members (read only, for debugging)
Individual developer access:
Developer --> SSO --> Cloud console --> Database proxy --> generic_ro role
(No personal credentials. Access revoked by disabling SSO account.)
Application access:
Container --> Reads /{service}/env --> Gets DB_PASSWORD --> Connects directly
(Password from secrets manager. No human knows the password.)
Analytics access:
BI tool --> Reads /{service}/db-analytics_ro --> Connects via proxy
(Scoped to SELECT on reporting tables only.)
Emergency admin access:
SRE --> SSO --> Temporary admin session --> migration_admin role
(Time-boxed to 1 hour. Fully audited. Requires approval.)
| Concept | AWS | GCP | Azure |
|---------|-----|-----|-------|
| Secrets storage | Secrets Manager | Secret Manager | Key Vault |
| Encryption keys | KMS (customer-managed CMK) | Cloud KMS | Key Vault (keys) |
| Auto-rotation | Rotation Lambdas | Rotation via Cloud Functions | Key Vault rotation policies |
| Infrastructure config (cross-service wiring) | SSM Parameter Store | Secret Manager labels or Firestore | App Configuration |
| Database proxy | RDS Proxy / IAM DB auth | Cloud SQL Auth Proxy | Microsoft Entra ID DB auth |
| Credential-free CLI | aws sso login | gcloud auth login | az login |
Working implementations in examples/:
examples/secrets-and-config-separation.md -- Complete single-secret-per-service setup with KMS encryption, one JSON blob, ECS task definition, and scoped IAM policiesexamples/database-role-management.md -- Role-based database user creation with purpose-named roles, individual secrets per role, and IAM-based developer access through a database proxyWhen designing or reviewing secrets and configuration management:
/{service}/env containing a JSON blob with all env vars/{service}/db-{role} (exception to one-blob rule){purpose}_{access}), not person-specific.env filestools
This skill should be used when the user invokes any /plan-* command from the planning-tools plugin (/plan-context, /plan-master, /plan-open-questions, /plan-verify, /plan-tick, /plan-progress, /plan-delete), asks how Claude Code's plan files work, asks where plans are stored, asks to author or audit a multi-phase master planning document, asks how to walk through a plan's Open Questions interactively, asks how to write progress entries, or mentions ~/.claude/plans/ or .claude/planning-tools.local.md. Provides the index of planning-tools commands, the master-plan workflow lifecycle, the v0.3.0+ list-shape mandate (phases and questions as headings + bulleted scope items, never tables), the v0.3.2+ plain-bullet shape (no `- [ ]` checkboxes — heading emoji is the sole tick signal), the progress-entry methodology, and the mechanics of Claude Code's plan-mode file storage.
testing
This skill should be used by the plan-verifier agent and the /plan-verify command to audit a drafted master plan against a fixed checklist. Covers universal-core completeness, the v0.3.0+ no-tables-for-phases-or-questions rule, trigger-based section-coverage gaps, phase actionability (heading + per-phase TL;DR + bulleted scope + exit criteria), the v0.3.1+ per-phase TL;DR requirement, the v0.3.2+ plain-bullet scope shape (legacy `- [ ]`/`- [x]` accepted silently), the v0.3.3+ context-block shape (plan-level `**TL;DR:**` + bulleted metadata, legacy `>` blockquote accepted silently), integer phase numbering enforcement, dependency traceability, citation resolution, callout/evidence convention compliance, Open Questions placement, and the one-PR-per-master-plan rule. Single-owner of the audit checklist.
tools
This skill should be used when authoring, reviewing, or modifying a multi-phase master planning document via the planning-tools plugin (especially the /plan-master and /plan-verify commands). Codifies the universal core sections, trigger-based optional sections, integer-only phase numbering, Open Questions placement, one-PR-per-plan rule, status conventions, evidence attribution, callouts, cross-reference formats, the v0.3.0 list-shape mandate (phases and questions are heading + bulleted list, never markdown tables), the v0.3.1 per-phase TL;DR requirement (1–3 sentence what/why summary under each phase heading for glance-ability), the v0.3.2 plain-bullet scope shape (`- <action>` items, no `- [ ]` checkboxes — the phase status emoji is the sole tick signal), and the v0.3.3 context-block shape (a plan-level `**TL;DR:**` + a bulleted metadata list instead of a `>` blockquote; legacy blockquote blocks accepted silently). Project-agnostic — no ticket-prefix or plan-type taxonomy.
testing
This skill should be used when the user is adjusting spacing, padding, margins, content density, section gaps, vertical rhythm, or separation between elements. Also applies when reviewing whether a design feels cramped or too sparse, choosing between borders and whitespace for separation, or defining a spacing system. Covers the 4px/8px spacing system, macro vs micro whitespace, content density spectrum, separation techniques (whitespace > background shifts > borders), and vertical rhythm.