Secret inventory and lifecycle¶
This document provides the authoritative inventory of all secrets used in the linux-update-cds system, their consumers, storage locations, and lifecycle management.
Overview¶
All production secrets are stored in Azure Key Vault with RBAC-based access control. Runtime workloads consume secrets via Key Vault references (Azure Container Apps) or secret fetching at container startup (VM-hosted deployments). No production secrets are committed to source control.
Secret storage architecture¶
- Production storage: Azure Key Vault (one per environment: low-side, high-side)
- Development storage: Local
.envfiles (gitignored, never committed) - Access control: Azure RBAC
- Operators: Key Vault Secrets Officer (read + write)
- Runtime workloads: Key Vault Secrets User (read-only)
- Workload identity: Azure Container Apps managed identity or VM system-assigned managed identity
Complete secret inventory¶
Core Pulp runtime secrets¶
| Secret Name | Storage Location | Runtime Binding | Consumer | Purpose | Generation Method | Rotation Owner | Default Rotation Interval |
|---|---|---|---|---|---|---|---|
pulp-secret-key |
Azure Key Vault | PULP_SECRET_KEY |
Pulp API, workers | Django application secret for session signing, CSRF tokens | 50-byte random string (base64) | Operator | Annual |
pulp-admin-password |
Azure Key Vault | PULP_ADMIN_PASSWORD |
Pulp API, automation scripts | Admin account password for Pulp API authentication | 32-byte random password (base64) | Operator | Quarterly |
pulp-db-password |
Azure Key Vault | POSTGRES_PASSWORD |
Pulp API, workers, content | PostgreSQL pulp user password |
32-byte random password (base64) | Operator | Quarterly |
pulp-db-symmetric-key |
Azure Key Vault | (file-backed via secret volume) | Pulp API, workers | Database field encryption using Fernet | 32-byte Fernet key (base64) | Operator | Annual |
Azure infrastructure secrets¶
| Secret Name | Storage Location | Runtime Binding | Consumer | Purpose | Generation Method | Rotation Owner | Default Rotation Interval |
|---|---|---|---|---|---|---|---|
pulp-storage-account-key |
Azure Key Vault | AZURE_STORAGE_KEY |
Pulp API, workers | Azure Blob Storage artifact backend access | Azure-managed primary key | Operator | Quarterly |
pulp-redis-url |
ACA inline secret (not Key Vault) | PULP_REDIS_URL |
Pulp API, workers, content | Redis task queue connection string with embedded TLS primary key (rediss://:KEY@HOST:PORT/0) |
Derived from redis.listKeys().primaryKey at Bicep deploy time (infra/low-side/containerapps.bicep) |
Operator | When Azure Cache for Redis primary key is regenerated |
External integration secrets (future)¶
| Secret Name | Storage Location | Runtime Binding | Consumer | Purpose | Generation Method | Rotation Owner | Default Rotation Interval |
|---|---|---|---|---|---|---|---|
rhsm-username |
Azure Key Vault | RHSM_USERNAME |
Pulp workers | Red Hat Subscription Manager username | Operator-provided RHSM credential | Operator | Per RHSM policy |
rhsm-password |
Azure Key Vault | RHSM_PASSWORD |
Pulp workers | Red Hat Subscription Manager password | Operator-provided RHSM credential | Operator | Per RHSM policy |
TLS/HTTPS secrets¶
| Secret Name | Storage Location | Runtime Binding | Consumer | Purpose | Generation Method | Rotation Owner | Default Rotation Interval |
|---|---|---|---|---|---|---|---|
pulp-tls-cert |
Azure Key Vault | TLS_CERT_PATH |
ACA ingress or reverse proxy | HTTPS certificate (PEM) | Certificate authority or ACME service | Operator/CA | 90 days before expiration |
pulp-tls-key |
Azure Key Vault | TLS_KEY_PATH |
ACA ingress or reverse proxy | HTTPS private key (PEM) | Generated with certificate request | Operator/CA | When certificate is renewed |
Secret lifecycle stages¶
1. Generation¶
Secrets are generated using one of these methods:
- Random high-entropy: Python
os.urandom()with base64 encoding (default for most secrets) - Azure-managed: Storage account keys regenerated via Azure CLI (
az storage account keys renew) - Operator-provided: RHSM credentials, TLS certificates from external sources
- Key Vault automatic: Secrets Officer can create directly in Key Vault using Azure Portal or CLI
Primary generation script: automation/bootstrap/prepare_container_apps.py
This script implements "generate-if-absent" logic: 1. Check if secret exists in Key Vault 2. If missing, generate using appropriate method 3. Store in Key Vault with current timestamp metadata 4. Return secret value for runtime use
2. Storage¶
All production secrets are stored in Azure Key Vault with these controls:
- Purge protection: Enabled (secrets cannot be permanently deleted for 90 days after soft-delete)
- RBAC authorization: Enabled (no legacy access policies)
- Network ACLs:
Allowfor Milestone 1 bootstrap; future milestone may restrict to private endpoint - Audit logging: Enabled via Azure Monitor (all secret read/write operations logged)
- Versioning: Automatic (Key Vault maintains previous secret versions for rollback)
Key Vault naming: pulp<namePrefix>kv (e.g., pulpm1lowkv for low-side, pulpm1highkv for high-side)
Bicep configuration: infra/_shared/keyvault.bicep, infra/high-side/keyvault.bicep
3. Distribution¶
Secrets are distributed to runtime workloads via two paths:
Azure Container Apps (production path):
- Secrets stored as ACA secret references in infra/low-side/containerapps.bicep
- Key Vault reference format: @Microsoft.KeyVault(SecretUri=https://<vault>.vault.azure.net/secrets/<name>)
- ACA managed identity fetches secrets at container startup
- Environment variables injected into container runtime
VM-hosted Compose (development and transition path):
- Operator runs prepare_container_apps.py to fetch secrets from Key Vault
- Secrets written to local .env file (gitignored)
- Docker Compose mounts .env as environment variables
- Alternative: SSH to VM and use Azure CLI with VM managed identity
4. Consumption¶
Runtime workloads consume secrets as environment variables:
- Pulp API:
PULP_SECRET_KEY,PULP_ADMIN_PASSWORD,POSTGRES_PASSWORD,AZURE_STORAGE_KEY - Pulp workers: Same as API, plus future
RHSM_USERNAME,RHSM_PASSWORD - Pulp content:
POSTGRES_PASSWORD,AZURE_STORAGE_KEY - Bootstrap/reconcile jobs:
PULP_ADMIN_PASSWORDfor API authentication
Settings configuration: runtime/compose/pulp/settings.py
Fail-closed behavior: Missing required secrets cause runtime startup failure (no unsafe fallbacks in production)
5. Rotation¶
Secrets are rotated according to schedule or trigger events (compromise, personnel change, expiration).
Rotation procedure: See ../runbooks/secrets-rotation.md
Rotation triggers: - Scheduled: Annual (secret key, symmetric key), Quarterly (passwords, storage key), 90-day (TLS certificates) - Event-driven: Personnel change (admin password, RHSM), suspected compromise (any secret), Azure key regeneration (storage key) - Automated (future): Key Vault expiration dates + Azure Functions or Logic Apps
Evidence collection: Rotation events logged to artifacts/rotation/<secret-name>-<YYYYMMDD>.log (gitignored)
6. Revocation and expiration¶
Revocation process: 1. Rotate secret immediately (generates new value) 2. Remove compromised value from Key Vault (soft-delete, purge after 90 days) 3. Audit access logs to identify unauthorized access 4. Document incident for security review
Expiration policy (future Milestone 3): - Set Key Vault secret expiration dates at creation time - Configure Azure Monitor alerts for secrets nearing expiration - Automate rotation workflow triggered by expiration dates
Secret leakage prevention¶
Committed source control guardrails¶
.gitignore protections:
runtime/compose/.env
runtime/compose/.env*.local
runtime/compose/pulp/certs/
infra/low-side/*.local.bicepparam
artifacts/
CI checks (planned for Epic 4: Supply-chain security):
- Detect committed .local.bicepparam files
- Scan for real-looking secrets (high-entropy base64 strings, Azure connection strings)
- Validate .env.example contains only placeholder values
Runtime leakage prevention¶
- Container logs: Pulp settings.py does not log secret values
- Error messages: Django and Pulp redact sensitive fields in stack traces
- Environment variable inspection: Operators with SSH/exec access can view env vars; limit access via RBAC
- Memory dumps: Not captured in normal operation; require explicit operator action
Development workflow guardrails¶
Local .env files:
- Must never be committed (enforced by .gitignore)
- Should be regenerated per-developer, not shared via Slack/email
- Contain placeholder values in .env.example template
Azure parameter files:
- .local.bicepparam files are gitignored
- .example.bicepparam files contain placeholders only
- Real postgresAdminPassword and other secrets provided at az deployment group create time
Evidence and audit¶
Secret access audit¶
Query Key Vault secret access logs:
az monitor activity-log list \
--resource-group <rg> \
--offset 7d \
--query "[?contains(operationName.value, 'KeyVault')]" \
-o table
Secret inventory validation¶
Validate Key Vault contains required secrets:
Expected secrets (Key Vault):
- pulp-secret-key
- pulp-admin-password
- pulp-db-password
- pulp-db-symmetric-key
- pulp-storage-account-key
Note:
pulp-redis-urlis an ACA inline secret constructed fromredis.listKeys().primaryKeyat Bicep deploy time. It is not stored in Key Vault and will not appear in the list above. Validate it viaaz containerapp show ... --query properties.configuration.secrets.
Runtime consumption validation¶
Check ACA secret references:
az containerapp show \
--resource-group <rg> \
--name <app-name> \
--query properties.configuration.secrets \
-o table
Check VM-hosted container environment:
ssh azureuser@<vm-host>
docker exec <container-name> sh -c '
if [ -n "${PULP_SECRET_KEY:-}" ] && [ "${PULP_SECRET_KEY}" != "unsafe-local-dev-key" ]; then
echo "PULP_SECRET_KEY present and not the development fallback"
else
echo "PULP_SECRET_KEY missing or set to the development fallback" >&2
exit 1
fi
'
Development vs. production mode¶
Development mode (PULP_DEV_MODE=true):
- Allows unsafe-local-dev-key fallback for PULP_SECRET_KEY
- Accepts placeholder passwords and localhost-only URLs
- MUST NOT be enabled in Azure production deployments
Production mode (default, PULP_DEV_MODE unset or false):
- Requires PULP_SECRET_KEY from Key Vault; fails closed if missing
- All passwords and keys must be high-entropy random values
- Fail-closed behavior prevents accidental unsafe deployments
Settings enforcement: runtime/compose/pulp/settings.py lines 9-19
Compliance mappings¶
| Control ID | Framework | Requirement | Implementation |
|---|---|---|---|
| IA-5 | NIST SP 800-53 | Authenticator management | Key Vault RBAC, rotation procedures |
| SC-28 | NIST SP 800-53 | Protection of information at rest | Key Vault encryption, symmetric key for DB fields |
| AC-6(1) | NIST SP 800-53 | Least privilege | Read-only runtime identities, Secrets Officer for operators |
| AU-2 | NIST SP 800-53 | Audit events | Key Vault access logs via Azure Monitor |
| IA-5(1) | DoD IL5+ | Password-based authentication | High-entropy generated passwords, quarterly rotation |
Milestone roadmap¶
Milestone 1 (current): - ✅ Key Vault storage with RBAC - ✅ Generate-if-absent secret creation - ✅ Fail-closed production settings - ✅ Rotation runbook documentation
Milestone 2 (in progress): - [ ] CI secret leakage detection - [ ] Secret inventory validation tests - [ ] High-side secret replication strategy - [ ] RHSM credential integration
Milestone 3 (future): - [ ] Automated rotation workflows - [ ] Key Vault expiration monitoring - [ ] Zero-downtime rotation for ACA - [ ] Secret usage telemetry and alerting
See also¶
- ../runbooks/secrets-rotation.md — Detailed rotation procedures for each secret
- ../runbooks/low-side-config.md — Runtime configuration reference
- ../runbooks/pulp-bootstrap.md — Initial deployment and secret generation
- ../runbooks/troubleshooting.md — R-16 (Key Vault permission issues)
automation/bootstrap/prepare_container_apps.py— Secret generation scriptinfra/_shared/keyvault.bicep— Key Vault infrastructure configuration