# Phoenix Vault Integration Guide **Last Updated:** 2026-01-31 **Document Version:** 1.0 **Status:** Active Documentation --- **Date:** 2026-01-19 **Status:** ✅ Ready for Integration **Purpose:** Guide for integrating Phoenix services with Vault cluster --- ## Overview This guide provides step-by-step instructions for integrating Phoenix services (API, Portal, and other components) with the deployed Vault cluster. --- ## Prerequisites - Vault cluster deployed and operational - AppRole authentication configured - AppRole credentials available - Phoenix services ready for integration --- ## AppRole Credentials AppRole credentials are stored in: ``` .secure/vault-credentials/phoenix-approle-credentials-YYYYMMDD.txt ``` **Phoenix API:** - Role ID: `27f213e2-f15e-b6de-3cf4-db2f02029dd5` - Secret ID: (stored in credentials file) **Phoenix Portal:** - Role ID: `70278dee-a85e-9007-c769-46b71a8c1460` - Secret ID: (stored in credentials file) --- ## Integration Methods ### Method 1: Environment Variables (Recommended) Set environment variables in your Phoenix service configuration: ```bash export VAULT_ADDR=http://192.168.11.200:8200 export VAULT_ROLE_ID= export VAULT_SECRET_ID= ``` ### Method 2: Configuration Files Create a Vault configuration file (e.g., `vault-config.json`): ```json { "vault_addr": "http://10.160.0.40:8200", "role_id": "", "secret_id": "" } ``` **⚠️ Security:** Never commit configuration files with credentials to Git. Use environment variables or secure secret management. --- ## Phoenix API Integration ### Node.js/TypeScript Example ```typescript import Vault from 'node-vault'; // Initialize Vault client const vault = Vault({ endpoint: process.env.VAULT_ADDR || 'http://192.168.11.200:8200', token: await authenticateWithAppRole( process.env.VAULT_ROLE_ID!, process.env.VAULT_SECRET_ID! ) }); // Authenticate with AppRole async function authenticateWithAppRole(roleId: string, secretId: string): Promise { const response = await fetch(`${process.env.VAULT_ADDR}/v1/auth/approle/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ role_id: roleId, secret_id: secretId }) }); const data = await response.json(); return data.auth.client_token; } // Get database credentials async function getDatabaseCredentials() { const response = await vault.read('secret/data/phoenix/database/postgres'); return { username: response.data.data.username, password: response.data.data.password, host: response.data.data.host, port: response.data.data.port, database: response.data.data.database }; } // Get JWT secrets async function getJWTSecrets() { const response = await vault.read('secret/data/phoenix/api/jwt-secrets'); return { accessTokenSecret: response.data.data['access-token-secret'], refreshTokenSecret: response.data.data['refresh-token-secret'] }; } ``` ### Python Example ```python import hvac import os # Initialize Vault client client = hvac.Client(url=os.getenv('VAULT_ADDR', 'http://10.160.0.40:8200')) # Authenticate with AppRole role_id = os.getenv('VAULT_ROLE_ID') secret_id = os.getenv('VAULT_SECRET_ID') client.auth.approle.login(role_id=role_id, secret_id=secret_id) # Get database credentials db_secrets = client.secrets.kv.v2.read_secret_version(path='phoenix/database/postgres') db_config = db_secrets['data']['data'] # Get JWT secrets jwt_secrets = client.secrets.kv.v2.read_secret_version(path='phoenix/api/jwt-secrets') jwt_config = jwt_secrets['data']['data'] ``` --- ## Phoenix Portal Integration Similar to API integration, but use the `phoenix-portal` AppRole: ```typescript // Portal-specific Vault configuration const vault = Vault({ endpoint: process.env.VAULT_ADDR || 'http://192.168.11.200:8200', token: await authenticateWithAppRole( process.env.VAULT_PORTAL_ROLE_ID!, process.env.VAULT_PORTAL_SECRET_ID! ) }); // Get JWT secrets for portal const jwtSecrets = await vault.read('secret/data/phoenix/api/jwt-secrets'); ``` --- ## Secret Paths Reference ### Available Secret Paths | Path | Description | Access Policy | |------|-------------|---------------| | `secret/data/phoenix/api/jwt-secrets` | JWT signing secrets | phoenix-api, phoenix-portal | | `secret/data/phoenix/api/api-keys` | API keys | phoenix-api | | `secret/data/phoenix/database/postgres` | PostgreSQL credentials | phoenix-api | | `secret/data/phoenix/database/redis` | Redis credentials | phoenix-api | | `secret/data/phoenix/keycloak/admin-credentials` | Keycloak admin | phoenix-api | | `secret/data/phoenix/keycloak/oidc-secrets` | OIDC client secrets | phoenix-api | | `secret/data/phoenix/services/blockchain` | Blockchain RPC/keys | phoenix-api | | `secret/data/phoenix/services/integrations` | Integration tokens | phoenix-api | ### Reading Secrets ```bash # Using Vault CLI export VAULT_ADDR=http://192.168.11.200:8200 export VAULT_TOKEN=$(vault write -field=token auth/approle/login \ role_id=$VAULT_ROLE_ID secret_id=$VAULT_SECRET_ID) # Read secret vault kv get secret/phoenix/database/postgres ``` --- ## Token Management ### Token Lifecycle - **Token TTL:** 1 hour (default) - **Token Max TTL:** 4 hours - **Secret ID TTL:** 24 hours ### Token Renewal Tokens should be renewed before expiration: ```typescript // Renew token async function renewToken(token: string): Promise { await fetch(`${process.env.VAULT_ADDR}/v1/auth/token/renew-self`, { method: 'POST', headers: { 'X-Vault-Token': token } }); } ``` ### Automatic Token Refresh Implement automatic token refresh in your services: ```typescript class VaultClient { private token: string | null = null; private tokenExpiry: Date | null = null; async getToken(): Promise { if (!this.token || this.isTokenExpired()) { await this.authenticate(); } return this.token!; } private isTokenExpired(): boolean { if (!this.tokenExpiry) return true; return new Date() >= this.tokenExpiry; } private async authenticate(): Promise { const response = await fetch(`${process.env.VAULT_ADDR}/v1/auth/approle/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ role_id: process.env.VAULT_ROLE_ID, secret_id: process.env.VAULT_SECRET_ID }) }); const data = await response.json(); this.token = data.auth.client_token; this.tokenExpiry = new Date(Date.now() + data.auth.lease_duration * 1000); } } ``` --- ## Error Handling ### Common Errors **1. Authentication Failed** ``` Error: invalid role_id or secret_id ``` **Solution:** Verify AppRole credentials are correct. **2. Permission Denied** ``` Error: permission denied ``` **Solution:** Check that the AppRole has the correct policy attached. **3. Secret Not Found** ``` Error: no secret found at path ``` **Solution:** Verify the secret path exists and is accessible. ### Retry Logic Implement retry logic for transient failures: ```typescript async function getSecretWithRetry(path: string, maxRetries = 3): Promise { for (let i = 0; i < maxRetries; i++) { try { return await vault.read(path); } catch (error) { if (i === maxRetries - 1) throw error; await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } } ``` --- ## Security Best Practices 1. **Never Hardcode Credentials** - Use environment variables - Use secure secret injection - Rotate credentials regularly 2. **Use Least Privilege** - Each service should only access secrets it needs - Use separate AppRoles for different services - Review policies regularly 3. **Monitor Access** - Enable audit logging - Monitor token usage - Alert on suspicious activity 4. **Token Management** - Implement automatic token renewal - Handle token expiration gracefully - Use short token TTLs 5. **Network Security** - Use TLS in production - Restrict network access to Vault - Use firewall rules --- ## Testing Integration ### Test Script ```bash #!/bin/bash # Test Vault integration export VAULT_ADDR=http://192.168.11.200:8200 export VAULT_ROLE_ID= export VAULT_SECRET_ID= # Authenticate TOKEN=$(vault write -field=token auth/approle/login \ role_id=$VAULT_ROLE_ID secret_id=$VAULT_SECRET_ID) export VAULT_TOKEN=$TOKEN # Test secret access echo "Testing secret access..." vault kv get secret/phoenix/api/jwt-secrets vault kv get secret/phoenix/database/postgres echo "✅ Integration test passed" ``` --- ## Troubleshooting ### Cannot Connect to Vault 1. Check network connectivity: ```bash curl http://10.160.0.40:8200/v1/sys/health ``` 2. Verify Vault is unsealed: ```bash vault status ``` 3. Check firewall rules ### Authentication Fails 1. Verify credentials: ```bash vault read auth/approle/role/phoenix-api/role-id ``` 2. Check AppRole is enabled: ```bash vault auth list ``` 3. Verify policy is attached: ```bash vault read auth/approle/role/phoenix-api ``` ### Permission Denied 1. Check policy: ```bash vault policy read phoenix-api-policy ``` 2. Verify secret path: ```bash vault kv list secret/phoenix/ ``` 3. Check token capabilities: ```bash vault token capabilities secret/data/phoenix/api/jwt-secrets ``` --- ## Migration from Existing Secrets If migrating from existing `.env` files or other secret storage: 1. **Inventory Current Secrets** - List all secrets currently in use - Map to Vault paths 2. **Create Secrets in Vault** ```bash vault kv put secret/phoenix/database/postgres \ username=current_username \ password=current_password \ host=current_host \ port=5432 \ database=phoenix ``` 3. **Update Services** - Replace `.env` file reading with Vault client - Test thoroughly - Deploy gradually 4. **Remove Old Secrets** - Delete `.env` files after migration - Update `.gitignore` if needed --- ## Related Documentation - [Phoenix Vault Cluster Deployment](PHOENIX_VAULT_CLUSTER_DEPLOYMENT.md) - [Vault TLS Configuration](VAULT_TLS_CONFIGURATION.md) - [Master Secrets Inventory](MASTER_SECRETS_INVENTORY.md) - [HashiCorp Vault Documentation](https://developer.hashicorp.com/vault/docs) --- **Status:** ✅ Ready for Integration **Last Updated:** 2026-01-19