--- checkId: check.security.secrets plugin: stellaops.doctor.security severity: fail tags: [security, secrets, configuration] --- # Secrets Configuration ## What It Checks Validates that secrets are properly managed and not exposed as plain text in configuration. The check scans the following configuration keys for potential plain-text secrets: | Key | What it protects | |---|---| | `Jwt:SigningKey` | JWT token signing | | `Jwt:Secret` | JWT secret (alternative key) | | `ApiKey` | API authentication key | | `ApiSecret` | API secret | | `S3:SecretKey` | Object storage credentials | | `Smtp:Password` | Email server credentials | | `Ldap:Password` | Directory service credentials | | `Redis:Password` | Cache/message broker credentials | | `Valkey:Password` | Cache/message broker credentials | A value is considered a plain-text secret if it: 1. Is at least 8 characters long. 2. Contains both uppercase and lowercase letters. 3. Contains digits or special characters. 4. Does NOT start with a secrets provider prefix: `vault:`, `azurekv:`, `aws:`, `gcp:`, `${`, or `@Microsoft.KeyVault`. The check also examines whether a secrets management provider is configured (`Secrets:Provider`, `KeyVault:Provider`, `Secrets:VaultUrl`, `KeyVault:Url`, `Vault:Address`). A missing secrets manager is only flagged if plain-text secrets are also found. Note: Connection strings are intentionally excluded from this check as they are DSNs (host/port/db) and are expected in configuration. ## Why It Matters Plain-text secrets in configuration files are a critical security risk. Configuration files are often committed to version control, stored in CI artifacts, or readable by anyone with filesystem access. Leaked secrets enable: - Token forgery (JWT signing keys). - Unauthorized API access (API keys). - Data access via backend services (database, SMTP, LDAP passwords). - Lateral movement within the infrastructure. ## Common Causes - Secrets stored directly in `appsettings.json` instead of using a secrets provider - Environment variables containing secrets not sourced from a secrets manager - Development secrets left in production configuration - No secrets management provider configured (HashiCorp Vault, Azure Key Vault, etc.) ## How to Fix ### Docker Compose Use Docker secrets or reference an external secrets manager: ```yaml services: platform: environment: Jwt__SigningKey: "vault:secret/data/stellaops/jwt#signing_key" Secrets__Provider: "vault" Secrets__VaultUrl: "http://vault:8200" secrets: - jwt_signing_key secrets: jwt_signing_key: file: ./secrets/jwt_signing_key.txt ``` Or use `dotnet user-secrets` for development: ```bash dotnet user-secrets set "Jwt:SigningKey" "" ``` ### Bare Metal / systemd Configure a secrets provider in `appsettings.json`: ```json { "Secrets": { "Provider": "vault", "VaultUrl": "https://vault.internal:8200", "UseSecretManager": true } } ``` Store secrets in the provider instead of config files: ```bash # HashiCorp Vault vault kv put secret/stellaops/jwt signing_key="" # dotnet user-secrets (development) dotnet user-secrets set "Jwt:SigningKey" "" ``` ### Kubernetes / Helm Store secrets as Kubernetes Secrets: ```bash kubectl create secret generic stellaops-secrets \ --from-literal=jwt-signing-key="" \ --from-literal=smtp-password="" ``` Reference in Helm values: ```yaml secrets: provider: "kubernetes" existingSecret: "stellaops-secrets" ``` Or use an external secrets operator (e.g., External Secrets Operator with Vault): ```yaml apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: stellaops-secrets spec: secretStoreRef: name: vault-backend kind: ClusterSecretStore target: name: stellaops-secrets data: - secretKey: jwt-signing-key remoteRef: key: secret/stellaops/jwt property: signing_key ``` ## Verification ``` stella doctor run --check check.security.secrets ``` ## Related Checks - `check.security.jwt.config` — JWT signing key security - `check.security.encryption` — encryption key management - `check.security.apikey` — API key security practices