Skip to content

Secrets Management

This guide covers best practices for managing sensitive configuration values like API keys, database credentials, and authentication secrets.

Secrets that require protection:

SecretComponentPurpose
LICENSE_MONITOR_API_KEYBothAPI authentication
AUTH_OKTA_SECRETDashboardOAuth client secret
AUTH_SECRETDashboardJWT signing key
AUTH_OKTA_IDDashboardOAuth client ID
Database credentialsDashboardConvex connection

Secrets are loaded in this priority (highest to lowest):

  1. Secrets manager (HashiCorp Vault, AWS Secrets Manager)
  2. Environment variables
  3. Configuration files
  4. Default values (never use for secrets)
Terminal window
# Set secrets in shell profile
export LICENSE_MONITOR_API_KEY="your-api-key"
export AUTH_SECRET="your-jwt-secret"
export AUTH_OKTA_SECRET="your-okta-secret"
# Or use a secrets file
cat > /etc/license-monitor/secrets.env << 'EOF'
LICENSE_MONITOR_API_KEY=your-api-key
AUTH_SECRET=your-jwt-secret
AUTH_OKTA_SECRET=your-okta-secret
EOF
chmod 600 /etc/license-monitor/secrets.env
Terminal window
# Set machine-level environment variables
[Environment]::SetEnvironmentVariable(
"LICENSE_MONITOR_API_KEY",
"your-api-key",
[EnvironmentVariableTarget]::Machine
)
# Or use Windows Credential Manager
cmdkey /generic:LicenseMonitorApiKey /user:service /pass:your-api-key
/etc/systemd/system/license-monitor.service
[Service]
EnvironmentFile=/etc/license-monitor/secrets.env

Secure the secrets file:

Terminal window
sudo chown root:license-monitor /etc/license-monitor/secrets.env
sudo chmod 640 /etc/license-monitor/secrets.env
docker-compose.yml
version: '3.8'
services:
license-monitor:
image: license-monitor:latest
secrets:
- license_monitor_api_key
environment:
- LICENSE_MONITOR_API_KEY_FILE=/run/secrets/license_monitor_api_key
license-server-detail:
image: license-server-detail:latest
secrets:
- auth_secret
- okta_secret
environment:
- AUTH_SECRET_FILE=/run/secrets/auth_secret
- AUTH_OKTA_SECRET_FILE=/run/secrets/okta_secret
secrets:
license_monitor_api_key:
file: ./secrets/license_monitor_api_key.txt
auth_secret:
file: ./secrets/auth_secret.txt
okta_secret:
file: ./secrets/okta_secret.txt
Terminal window
# Create secrets
echo "your-api-key" | docker secret create license_monitor_api_key -
echo "your-jwt-secret" | docker secret create auth_secret -
# Use in service
docker service create \
--name license-monitor \
--secret license_monitor_api_key \
license-monitor:latest
Terminal window
# Enable KV secrets engine
vault secrets enable -path=license-monitor kv-v2
# Store secrets
vault kv put license-monitor/api-keys \
license_monitor_api_key="your-api-key" \
auth_secret="your-jwt-secret"
# Create policy
vault policy write license-monitor - << 'EOF'
path "license-monitor/data/api-keys" {
capabilities = ["read"]
}
EOF
# Create AppRole for application
vault auth enable approle
vault write auth/approle/role/license-monitor \
policies="license-monitor" \
secret_id_ttl=0 \
token_ttl=1h \
token_max_ttl=24h
Terminal window
# Get role ID and secret ID
ROLE_ID=$(vault read -field=role_id auth/approle/role/license-monitor/role-id)
SECRET_ID=$(vault write -field=secret_id -f auth/approle/role/license-monitor/secret-id)
# Login and get token
VAULT_TOKEN=$(vault write -field=token auth/approle/login \
role_id=$ROLE_ID \
secret_id=$SECRET_ID)
# Read secrets
export LICENSE_MONITOR_API_KEY=$(vault kv get -field=license_monitor_api_key license-monitor/api-keys)
vault-agent.hcl
auto_auth {
method "approle" {
config = {
role_id_file_path = "/etc/vault/role_id"
secret_id_file_path = "/etc/vault/secret_id"
}
}
sink "file" {
config = {
path = "/etc/vault/token"
}
}
}
template {
source = "/etc/license-monitor/secrets.env.tpl"
destination = "/etc/license-monitor/secrets.env"
perms = 0640
}
Terminal window
# Create secret
aws secretsmanager create-secret \
--name license-monitor/api-keys \
--secret-string '{
"LICENSE_MONITOR_API_KEY": "your-api-key",
"AUTH_SECRET": "your-jwt-secret",
"AUTH_OKTA_SECRET": "your-okta-secret"
}'
Terminal window
# Get secret value
aws secretsmanager get-secret-value \
--secret-id license-monitor/api-keys \
--query SecretString \
--output text | jq -r '.LICENSE_MONITOR_API_KEY'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": [
"arn:aws:secretsmanager:us-east-1:123456789:secret:license-monitor/*"
]
}
]
}
  1. Generate new secret
  2. Update secrets manager
  3. Deploy to applications (rolling update)
  4. Verify functionality
  5. Remove old secret
rotate-api-key.sh
#!/bin/bash
# Generate new key
NEW_KEY=$(openssl rand -hex 32)
# Update Vault
vault kv patch license-monitor/api-keys \
license_monitor_api_key="$NEW_KEY"
# Restart services to pick up new secret
systemctl restart license-monitor
systemctl restart license-server-detail
# Verify health
sleep 10
curl -f http://localhost:8080/api/health || exit 1
curl -f http://localhost:3000/api/health || exit 1
echo "Rotation complete"
SecretRotation FrequencyNotes
API Keys90 daysAutomated rotation
OAuth SecretsAnnuallyCoordinate with IdP
JWT Secrets90 daysPlan for session invalidation
SSL CertificatesBefore expiryUse cert-manager or similar
.github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to production
env:
LICENSE_MONITOR_API_KEY: ${{ secrets.LICENSE_MONITOR_API_KEY }}
AUTH_SECRET: ${{ secrets.AUTH_SECRET }}
AUTH_OKTA_SECRET: ${{ secrets.AUTH_OKTA_SECRET }}
run: |
# Deploy with secrets
./deploy.sh
.gitlab-ci.yml
deploy:
stage: deploy
script:
- ./deploy.sh
variables:
LICENSE_MONITOR_API_KEY: $LICENSE_MONITOR_API_KEY
AUTH_SECRET: $AUTH_SECRET
only:
- main
  • Use a secrets manager in production
  • Rotate secrets regularly
  • Use least-privilege access
  • Audit secret access
  • Encrypt secrets at rest
  • Use separate secrets per environment
  • Commit secrets to version control
  • Log secrets (even accidentally)
  • Share secrets via email or chat
  • Use default or example secrets
  • Store secrets in plain text
  • Reuse secrets across environments
Terminal window
# Monitor secret access (Vault example)
vault audit enable file file_path=/var/log/vault/audit.log
# Review access logs
grep "license-monitor" /var/log/vault/audit.log | jq '.request.path'