From cloud-foundation-principles
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 claudepluginhub oborchers/fractional-cto --plugin cloud-foundation-principlesThis skill uses the workspace's default tool permissions.
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.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
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 files