Secrets Management in CI/CD
Why Secrets Management?
Never hardcode sensitive information like API keys, passwords, or tokens in code. Use secure secrets management instead.
GitHub Actions Secrets
name: Deploy
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to AWS
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: ./deploy.shEnvironment-Specific Secrets
jobs:
deploy-staging:
environment: staging
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.STAGING_API_KEY }}
DB_PASSWORD: ${{ secrets.STAGING_DB_PASSWORD }}
run: ./deploy.sh staging
deploy-production:
environment: production
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.PROD_API_KEY }}
DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
run: ./deploy.sh productionKubernetes Secrets
# Create secret
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
database-url: cG9zdGdyZXNxbDovL3Bvc3RncmVzOnBhc3N3b3JkQGxvY2FsaG9zdDo1NDMyL215YXBw
api-key: YWJjMTIzNDU2Nzg5
jwt-secret: c3VwZXJzZWNyZXRrZXk=
---
# Use in deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
containers:
- name: myapp
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
- name: API_KEY
valueFrom:
secretKeyRef:
name: app-secrets
key: api-keyHashiCorp Vault
// Node.js with Vault
const vault = require('node-vault')({
endpoint: 'http://vault:8200',
token: process.env.VAULT_TOKEN
});
async function getSecrets() {
const dbSecret = await vault.read('secret/database');
const apiSecret = await vault.read('secret/api');
return {
databaseUrl: dbSecret.data.url,
apiKey: apiSecret.data.key
};
}
// Use secrets
const secrets = await getSecrets();
const db = await connectDatabase(secrets.databaseUrl);Azure Key Vault
// .NET with Azure Key Vault
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
var client = new SecretClient(
new Uri("https://myvault.vault.azure.net/"),
new DefaultAzureCredential()
);
var databaseUrl = await client.GetSecretAsync("DatabaseUrl");
var apiKey = await client.GetSecretAsync("ApiKey");
// Use secrets
var connectionString = databaseUrl.Value.Value;AWS Secrets Manager
// Node.js with AWS Secrets Manager
const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager();
async function getSecret(secretName) {
const data = await secretsManager.getSecretValue({
SecretId: secretName
}).promise();
return JSON.parse(data.SecretString);
}
// Use secrets
const dbCreds = await getSecret('prod/database');
const apiKeys = await getSecret('prod/api-keys');Docker Secrets
# docker-compose.yml
version: '3.8'
services:
app:
image: myapp:latest
secrets:
- db_password
- api_key
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
- API_KEY_FILE=/run/secrets/api_key
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt// Read Docker secret
const fs = require('fs');
function readSecret(secretName) {
const secretPath = `/run/secrets/${secretName}`;
return fs.readFileSync(secretPath, 'utf8').trim();
}
const dbPassword = readSecret('db_password');
const apiKey = readSecret('api_key');Environment Variables
# .env (NOT committed to git)
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
API_KEY=abc123xyz789
JWT_SECRET=supersecretkey// Load with dotenv
require('dotenv').config();
const config = {
databaseUrl: process.env.DATABASE_URL,
apiKey: process.env.API_KEY,
jwtSecret: process.env.JWT_SECRET
};Encryption
// Encrypt secrets
const crypto = require('crypto');
function encrypt(text, key) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return iv.toString('hex') + ':' + encrypted.toString('hex');
}
function decrypt(text, key) {
const parts = text.split(':');
const iv = Buffer.from(parts[0], 'hex');
const encrypted = Buffer.from(parts[1], 'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
let decrypted = decipher.update(encrypted);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}Best Practices
- Never commit secrets: Use .gitignore
- Rotate regularly: Change secrets periodically
- Least privilege: Minimal access rights
- Encrypt at rest: Encrypted storage
- Audit access: Track who accesses secrets
- Use secret managers: Vault, AWS Secrets Manager
- Environment-specific: Different secrets per environment
.gitignore
# Secrets and credentials
.env
.env.local
.env.*.local
*.key
*.pem
secrets/
credentials/
# Configuration files with secrets
config/production.json
appsettings.Production.jsonInterview Tips
- Explain importance: Never hardcode secrets
- Show tools: GitHub Secrets, Vault, AWS Secrets Manager
- Demonstrate Kubernetes: Secret resources
- Discuss encryption: Protect sensitive data
- Mention best practices: Rotation, least privilege
- Show .gitignore: Prevent commits
Summary
Secrets management protects sensitive information in CI/CD pipelines. Use GitHub Actions secrets, Kubernetes secrets, or dedicated tools like Vault and AWS Secrets Manager. Never commit secrets to version control. Encrypt secrets at rest. Rotate regularly. Implement least privilege access. Essential for secure DevOps practices.
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.