Skip to main content

OpenBao Norns Integration

How Norns authenticates to OpenBao and fetches secrets at startup.


Overview

Norns uses a vault entrypoint wrapper script that authenticates to OpenBao via AppRole and fetches secrets before starting the main application. This allows secrets to be centrally managed in OpenBao while requiring no changes to the Norns application code.

PropertyValue
Auth MethodAppRole
Rolenorns
Policynorns
1Password ItemOpenBao AppRole - Norns
Token TTL1 hour (max 4 hours)

Architecture

┌─────────────────────────────────────────────────────────────────┐
│ Container Startup │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌────────────┐ │
│ │ vault-entrypoint │ -> │ Fetch Secrets │ -> │ Start App │ │
│ │ .sh │ │ from OpenBao │ │ (uvicorn) │ │
│ └──────────────────┘ └──────────────────┘ └────────────┘ │
│ │ │ │
│ v v │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ AppRole Login │ │ Export as ENV │ │
│ │ (role_id + │ │ Variables │ │
│ │ secret_id) │ │ │ │
│ └──────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

v
┌─────────────────────────────────────────────────────────────────┐
│ OpenBao │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ auth/approle │ │ secret/ │ │
│ │ - norns role │ │ - api-keys/* │ │
│ │ │ │ - database/* │ │
│ │ │ │ - integrations/* │ │
│ └──────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

Secrets Fetched

The vault-entrypoint script fetches these secrets from OpenBao:

Secret PathEnvironment VariableUsed For
secret/api-keys/anthropicANTHROPIC_API_KEYClaude API
secret/api-keys/openaiOPENAI_API_KEYOpenAI API
secret/api-keys/google-mapsGOOGLE_MAPS_API_KEYMaps API
secret/api-keys/linearLINEAR_API_KEYLinear API
secret/database/postgresPOSTGRES_PASSWORDDatabase
secret/database/docs/infrastructure/redisREDIS_PASSWORDCache
secret/integrations/slackSLACK_BOT_TOKEN, SLACK_SIGNING_SECRETSlack
secret/integrations/twilioTWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKENTwilio
secret/services/langfuseLANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEYObservability
secret/integrations/gitlabGITLAB_TOKENGitLab

Configuration Files

vault-entrypoint.sh

Location: ~/ravenhelm/docs/AI-ML-Platform/norns-agent/agent/vault-entrypoint.sh

#!/bin/bash
set -e

# OpenBao/Vault Configuration
VAULT_ADDR="${VAULT_ADDR:-https://vault.ravenhelm.dev}"

# Skip vault if credentials not provided
if [ -z "$VAULT_ROLE_ID" ] || [ -z "$VAULT_SECRET_ID" ]; then
echo "VAULT_ROLE_ID or VAULT_SECRET_ID not set, using environment variables directly"
exec "$@"
fi

echo "Authenticating to OpenBao..."

# Authenticate with AppRole
AUTH_RESPONSE=$(curl -s -X POST \
"${VAULT_ADDR}/v1/auth/approle/login" \
-d "{\"role_id\":\"${VAULT_ROLE_ID}\",\"secret_id\":\"${VAULT_SECRET_ID}\"}")

VAULT_TOKEN=$(echo "$AUTH_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['auth']['client_token'])" 2>/dev/null)

if [ -z "$VAULT_TOKEN" ]; then
echo "Failed to authenticate to OpenBao, using environment variables directly"
exec "$@"
fi

echo "Successfully authenticated to OpenBao"

# Function to fetch secret
fetch_secret() {
local path=$1
local field=$2
local result=$(curl -s -H "X-Vault-Token: ${VAULT_TOKEN}" \
"${VAULT_ADDR}/v1/secret/data/${path}" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('data',{}).get('${field}',''))" 2>/dev/null)
echo "$result"
}

# Fetch secrets and export as environment variables
echo "Fetching secrets from OpenBao..."

# API Keys
export ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-$(fetch_secret 'api-keys/anthropic' 'api_key')}"
export OPENAI_API_KEY="${OPENAI_API_KEY:-$(fetch_secret 'api-keys/openai' 'api_key')}"
# ... (additional secrets)

echo "Secrets loaded from OpenBao"

# Execute the main command
exec "$@"

Dockerfile Changes

The Dockerfile was modified to include the vault entrypoint:

# Install curl for OpenBao API calls
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

# Copy vault entrypoint
COPY vault-entrypoint.sh /usr/local/bin/vault-entrypoint.sh
RUN chmod +x /usr/local/bin/vault-entrypoint.sh

# Use vault entrypoint to fetch secrets before starting the app
ENTRYPOINT ["/usr/local/bin/vault-entrypoint.sh"]
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

docker-compose.yml Changes

Vault credentials added to environment:

environment:
# OpenBao Secrets
- VAULT_ADDR=https://vault.ravenhelm.dev
- VAULT_ROLE_ID=${VAULT_ROLE_ID:-}
- VAULT_SECRET_ID=${VAULT_SECRET_ID:-}
# ... other env vars

.env File

Vault credentials in ~/ravenhelm/secrets/.env:

# OpenBao Vault AppRole (Norns)
VAULT_ROLE_ID=181e2b56-2059-9a81-81b1-c7c29e92a884
VAULT_SECRET_ID=8b2b473a-032e-6ce2-41ac-c83303b150ac

Norns Policy

The norns policy grants read access to required secrets:

# API Keys
path "secret/data/api-keys/*" {
capabilities = ["read"]
}

# Integrations (Slack, Twilio, etc.)
path "secret/data/integrations/*" {
capabilities = ["read"]
}

# Database credentials
path "secret/data/database/postgres" {
capabilities = ["read"]
}

path "secret/data/database/docs/infrastructure/redis" {
capabilities = ["read"]
}

Startup Logs

When Norns starts successfully with OpenBao integration, you'll see:

Authenticating to OpenBao...
Successfully authenticated to OpenBao
Fetching secrets from OpenBao...
Secrets loaded from OpenBao
INFO: Started server process [1]
INFO: Waiting for application startup.
INFO:norns-agent:Database pool created
INFO:norns-agent:Redis connected at redis:6379
...

Fallback Behavior

The vault-entrypoint script has graceful fallback:

  1. No Vault Credentials: If VAULT_ROLE_ID or VAULT_SECRET_ID are not set, the script skips vault and uses environment variables directly.

  2. Auth Failure: If vault authentication fails, the script logs a warning and uses environment variables.

  3. Secret Not Found: If a secret isn't in vault, the existing environment variable is used (if set).

This ensures Norns can still start even if OpenBao is unavailable.


Troubleshooting

"Failed to authenticate to OpenBao"

Symptoms: Startup logs show authentication failure

Possible Causes:

  1. Wrong role_id or secret_id
  2. OpenBao sealed or unavailable
  3. Network connectivity issues

Resolution:

# Verify credentials
op item get "OpenBao AppRole - Norns" --vault ravenmask --reveal

# Test authentication manually
curl -X POST https://vault.ravenhelm.dev/v1/auth/approle/login \
-d '{"role_id":"ROLE_ID","secret_id":"SECRET_ID"}'

# Check OpenBao status
ssh ravenhelm@100.115.101.81 "docker exec openbao bao status"

"permission denied" for secrets

Symptoms: Some secrets fail to load

Cause: Norns policy doesn't include the secret path

Resolution:

# Check policy
ROOT_TOKEN=$(op item get "OpenBao Root Keys" --vault ravenmask --fields "Root Token" --reveal)
ssh ravenhelm@100.115.101.81 "docker exec -e BAO_TOKEN=$ROOT_TOKEN openbao bao policy read norns"

# Update policy if needed
cat > /tmp/norns.hcl << 'EOF'
path "secret/data/api-keys/*" { capabilities = ["read"] }
path "secret/data/integrations/*" { capabilities = ["read"] }
path "secret/data/database/*" { capabilities = ["read"] }
path "secret/data/services/*" { capabilities = ["read"] }
EOF
ssh ravenhelm@100.115.101.81 "docker exec -e BAO_TOKEN=$ROOT_TOKEN openbao bao policy write norns /tmp/norns.hcl"

Secrets not updating

Symptoms: Changed secret in vault not reflected in Norns

Cause: Secrets are fetched at container startup only

Resolution: Restart the container to fetch updated secrets:

ssh ravenhelm@100.115.101.81 "cd ~/ravenhelm/docs/AI-ML-Platform/norns-agent && docker compose restart norns-agent norns-executor"

Adding New Secrets

To add a new secret for Norns:

1. Store in OpenBao

ROOT_TOKEN=$(op item get "OpenBao Root Keys" --vault ravenmask --fields "Root Token" --reveal)
ssh ravenhelm@100.115.101.81 "docker exec -e BAO_TOKEN=$ROOT_TOKEN openbao bao kv put secret/api-keys/newservice api_key=xxx"

2. Update vault-entrypoint.sh

Add the fetch logic:

export NEW_API_KEY="${NEW_API_KEY:-$(fetch_secret 'api-keys/newservice' 'api_key')}"

3. Rebuild and Restart

ssh ravenhelm@100.115.101.81 "cd ~/ravenhelm/docs/AI-ML-Platform/norns-agent && docker compose build norns-agent && docker compose up -d norns-agent"

Security Considerations

  1. AppRole Credentials: Stored in .env file on odin, also backed up in 1Password
  2. Token Lifetime: Tokens expire after 1 hour; script gets fresh token on each container start
  3. Secret Scope: Norns only has read access to its required secrets
  4. Fallback: Environment variables provide backup if vault unavailable