Secrets and Variables

AuraBoot configuration is layered. Each layer overrides the one below it. Knowing the layer order is critical: a hotfix injected at the wrong layer either takes no effect or leaks into a git-tracked file.

Three-layer model

LayerSourcePrecedenceTypical use
1. Program arguments--spring.data.redis.host=...HighestOne-off override, production launchers
2. Environment variablesDATABASE_PASSWORD, SPRING_DATA_REDIS_HOSTMiddleContainer deployments, CI
3. External vaultHashicorp Vault / cloud KMS via spring.config.importLowest of the three (still above application.yml)Long-lived secrets, rotation

The packaged application.yml only contains non-sensitive defaults; it should never carry a real password or API key.

Sensitive fields

The following must never be committed and must come from layer 2 or 3:

FieldNotes
spring.datasource.passwordPostgreSQL primary credentials
spring.data.redis.host / passwordRedis is required by the enterprise build at startup
security.jwt.secretSigns platform JWTs; rotation invalidates sessions
aura.storage.minio.access-key / secret-keyObject storage
aura.mq.kafka.bootstrap-servers credentialsPlain bootstrap host is fine to log; SASL secret is not
Connector credentials (cr_csp_connector_pid resolver)Third-party API keys, OAuth tokens
Webhook signing secretsSee Webhooks
LLM provider keysOpenAI / Claude / local model gateway tokens

When to use

  • Bootstrapping a fresh environment: prefer environment variables for portability.
  • Production with frequent rotation: bind to an external vault and never hard-code.
  • Local development: use a gitignored .env file plus a launcher that translates it into program arguments.

How it works

Spring Boot resolves configuration in declared precedence order. AuraBoot extends this with a connector credential resolver that looks up bearer / basic / api-key payloads by cr_csp_connector_pid and injects them into outbound requests without persisting plaintext in DSL.

# application.yml (committed, contains no secrets)
spring:
  config:
    import:
      - "optional:vault://secret/auraboot/${SPRING_PROFILES_ACTIVE}"
  datasource:
    url: "${DATABASE_URL:jdbc:postgresql://localhost:5432/aura_boot?charSet=UTF8}"
    username: "${DATABASE_USERNAME:auraboot}"
    password: "${DATABASE_PASSWORD:}"   # required in production, no default
  data:
    redis:
      host: "${REDIS_HOST:localhost}"   # injected via SPRING_DATA_REDIS_HOST when Redis is enabled
      port: "${REDIS_PORT:6379}"

security:
  jwt:
    secret: "${JWT_SECRET}"   # required

For a one-off override (for example during incident response), pass it as a program argument so it does not leak into the process environment of every child shell:

java -jar platform.jar \
  --spring.profiles.active=prod \
  --spring.data.redis.host=10.0.3.7 \
  --spring.datasource.hikari.maximum-pool-size=50

Rotation

# 1. Generate a new secret; demote the current one to "previous" so old tokens still verify
NEW_SECRET="$(openssl rand -base64 64)"
# JWT_PREVIOUS_SECRET=$JWT_SECRET, JWT_PREVIOUS_KID=$JWT_KID
# JWT_SECRET=$NEW_SECRET,        JWT_KID=key-$(date +%Y%m%d)

# 2. Roll-restart each node so the new config takes effect
#    (the platform does not enable Spring Cloud /actuator/refresh)
#    New tokens are signed with the new secret; old tokens still verify via previous-secret

# 3. Verify the new JWT works: call a protected endpoint with the new token
curl -sS "$AURA_API_URL/api/auth/me" \
  -H "Authorization: Bearer $NEW_TOKEN"

# 4. After one token-expiry window (default 86400s), remove the JWT_PREVIOUS_* env vars

The platform supports dual keys via security.jwt.previous-secret / previous-kid: during a rolling restart old tokens still verify, so you do not have to invalidate all active sessions at once. Retire the old key after one expiry window. Schedule it during a maintenance window, or stagger nodes behind a session-affinity-aware load balancer.

Verify

  • git grep against the repository turns up no committed secrets (pg_password, jwt.secret:, raw bearer tokens).
  • /actuator/configprops (admin only) shows masked values for all fields under the sensitive-fields list.
  • Server logs at INFO level do not echo secrets; structured-log filters mask password, secret, token, apiKey.
  • A rotated secret takes effect within one refresh cycle on every node; old credentials are rejected.
  • CI pipelines source secrets from the CI vault integration, never from plaintext repository variables.

Related