Secretless Broker: Zero-Secret Applications
The best way to secure secrets is to never have them. Secretless Broker acts as a sidecar that handles authentication on behalf of your application - your code never sees credentials.
This guide covers deploying Secretless for databases, HTTP APIs, and SSH connections.
TL;DR
- Secretless = sidecar proxy that injects credentials
- App connects to localhost, Secretless handles auth
- Supports PostgreSQL, MySQL, HTTP APIs, SSH
- Integrates with Vault, Conjur, K8s secrets
- Your application code has zero secrets
The Problem with Secrets
Traditional approach:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ App │────▶│ Vault │────▶│ Database │
│ (has creds) │ │ │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
│
└── DB_PASSWORD in memory
└── Can be dumped, logged, leaked
Secretless approach:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ App │────▶│ Secretless │────▶│ Database │
│ (no creds) │ │ (sidecar) │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
│ │
└── localhost:5432 └── Fetches creds from Vault
└── No secrets!
Install Secretless
# Using Helm
helm repo add cyberark https://cyberark.github.io/helm-charts
helm upgrade --install secretless cyberark/secretless-broker \
--namespace secretless --create-namespace
Or deploy as sidecar directly in your pod.
Database Authentication
PostgreSQL Example
# secretless-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: secretless-config
data:
secretless.yml: |
version: "2"
services:
postgres:
protocol: pg
listenOn: tcp://0.0.0.0:5432
credentials:
host:
from: kubernetes-secret
get: postgres-creds#host
port:
from: kubernetes-secret
get: postgres-creds#port
username:
from: kubernetes-secret
get: postgres-creds#username
password:
from: kubernetes-secret
get: postgres-creds#password
sslmode:
from: literal
get: require
---
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
template:
spec:
serviceAccountName: api-server
containers:
# Your application - connects to localhost:5432
- name: app
image: api-server:latest
env:
- name: DATABASE_URL
value: "postgres://localhost:5432/mydb?sslmode=disable"
# No username/password needed!
# Secretless sidecar
- name: secretless
image: cyberark/secretless-broker:latest
args:
- -f
- /etc/secretless/secretless.yml
volumeMounts:
- name: config
mountPath: /etc/secretless
readOnly: true
ports:
- containerPort: 5432
volumes:
- name: config
configMap:
name: secretless-config
MySQL Example
apiVersion: v1
kind: ConfigMap
metadata:
name: secretless-mysql-config
data:
secretless.yml: |
version: "2"
services:
mysql:
protocol: mysql
listenOn: tcp://0.0.0.0:3306
credentials:
host:
from: vault
get: secret/data/mysql#host
port:
from: literal
get: "3306"
username:
from: vault
get: secret/data/mysql#username
password:
from: vault
get: secret/data/mysql#password
HTTP API Authentication
Secretless can inject auth headers into HTTP requests:
apiVersion: v1
kind: ConfigMap
metadata:
name: secretless-http-config
data:
secretless.yml: |
version: "2"
services:
stripe-api:
protocol: http
listenOn: tcp://0.0.0.0:8080
credentials:
authorizationHeader:
from: kubernetes-secret
get: stripe-creds#api-key
headers:
Stripe-Version: "2023-10-16"
config:
pattern: ^https://api\.stripe\.com
github-api:
protocol: http
listenOn: tcp://0.0.0.0:8081
credentials:
authorizationHeader:
from: vault
get: secret/data/github#token
headers:
Accept: application/vnd.github+json
config:
pattern: ^https://api\.github\.com
Your app connects to localhost:8080 for Stripe, localhost:8081 for GitHub. No API keys in your code.
Vault Integration
Configure Secretless to fetch secrets from Vault:
# secretless.yml with Vault
version: "2"
services:
postgres:
protocol: pg
listenOn: tcp://0.0.0.0:5432
credentials:
host:
from: vault
get: secret/data/postgres#host
username:
from: vault
get: database/creds/readonly#username
password:
from: vault
get: database/creds/readonly#password
# Vault configuration via environment
# VAULT_ADDR=https://vault.company.com
# Authenticate via Kubernetes auth method
# Deployment with Vault auth
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
template:
spec:
serviceAccountName: api-server
containers:
- name: app
image: api-server:latest
env:
- name: DATABASE_URL
value: "postgres://localhost:5432/mydb"
- name: secretless
image: cyberark/secretless-broker:latest
args: ["-f", "/etc/secretless/secretless.yml"]
env:
- name: VAULT_ADDR
value: "https://vault.company.com"
# Use Kubernetes auth
volumeMounts:
- name: config
mountPath: /etc/secretless
- name: vault-token
mountPath: /var/run/secrets/vault
volumes:
- name: config
configMap:
name: secretless-config
- name: vault-token
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600
audience: vault
SSH Connections
Proxy SSH connections with injected keys:
version: "2"
services:
ssh-bastion:
protocol: ssh
listenOn: tcp://0.0.0.0:2222
credentials:
address:
from: literal
get: bastion.company.com:22
user:
from: kubernetes-secret
get: ssh-creds#username
privateKey:
from: vault
get: secret/data/ssh#private-key
Your app/script connects to localhost:2222, Secretless handles the SSH authentication with the actual bastion.
AWS Authentication
For AWS services, use Secretless with IAM credentials:
version: "2"
services:
aws-s3:
protocol: http
listenOn: tcp://0.0.0.0:8080
credentials:
accessKeyId:
from: vault
get: aws/creds/s3-role#access_key
secretAccessKey:
from: vault
get: aws/creds/s3-role#secret_key
accessToken:
from: vault
get: aws/creds/s3-role#security_token
config:
pattern: ^https://.*\.s3\..*\.amazonaws\.com
authenticateURLsMatching:
- ^https://.*\.s3\..*\.amazonaws\.com
Complete Production Example
# Full deployment with multiple services
apiVersion: v1
kind: ConfigMap
metadata:
name: secretless-config
namespace: production
data:
secretless.yml: |
version: "2"
services:
# Primary database
postgres-primary:
protocol: pg
listenOn: tcp://0.0.0.0:5432
credentials:
host:
from: vault
get: secret/data/postgres-primary#host
port:
from: literal
get: "5432"
username:
from: vault
get: database/creds/app-readwrite#username
password:
from: vault
get: database/creds/app-readwrite#password
sslmode:
from: literal
get: require
# Read replica
postgres-replica:
protocol: pg
listenOn: tcp://0.0.0.0:5433
credentials:
host:
from: vault
get: secret/data/postgres-replica#host
port:
from: literal
get: "5432"
username:
from: vault
get: database/creds/app-readonly#username
password:
from: vault
get: database/creds/app-readonly#password
sslmode:
from: literal
get: require
# Redis
redis:
protocol: http
listenOn: tcp://0.0.0.0:6379
credentials:
password:
from: vault
get: secret/data/redis#password
# External payment API
payment-api:
protocol: http
listenOn: tcp://0.0.0.0:8080
credentials:
authorizationHeader:
from: vault
get: secret/data/payment#api-key
config:
pattern: ^https://api\.payment\.com
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: production
spec:
replicas: 3
template:
metadata:
labels:
app: api-server
spec:
serviceAccountName: api-server
containers:
- name: app
image: api-server:v1.2.3
env:
# All connections go to localhost - no secrets!
- name: DATABASE_PRIMARY_URL
value: "postgres://localhost:5432/app"
- name: DATABASE_REPLICA_URL
value: "postgres://localhost:5433/app"
- name: REDIS_URL
value: "redis://localhost:6379"
- name: PAYMENT_API_URL
value: "http://localhost:8080"
ports:
- containerPort: 3000
resources:
requests:
cpu: 100m
memory: 256Mi
- name: secretless
image: cyberark/secretless-broker:1.7
args: ["-f", "/etc/secretless/secretless.yml"]
env:
- name: VAULT_ADDR
value: "https://vault.company.com"
volumeMounts:
- name: config
mountPath: /etc/secretless
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
ports:
- containerPort: 5432
- containerPort: 5433
- containerPort: 6379
- containerPort: 8080
volumes:
- name: config
configMap:
name: secretless-config
Troubleshooting
Connection refused:
# Check secretless is running
kubectl logs deploy/api-server -c secretless
# Verify port binding
kubectl exec deploy/api-server -c secretless -- netstat -tlnp
Authentication failed:
# Check credentials are being fetched
kubectl logs deploy/api-server -c secretless | grep -i auth
# Verify Vault permissions
vault token capabilities secret/data/postgres
Debug mode:
- name: secretless
image: cyberark/secretless-broker:latest
args: ["-f", "/etc/secretless/secretless.yml", "-d"] # -d for debug
Security Benefits
- No secrets in app memory - can’t be dumped
- No secrets in logs - app never sees them
- No secrets in env vars - not visible in /proc
- Automatic rotation - Vault handles it
- Audit trail - all access logged in Vault
- Least privilege - each app gets specific creds
References
- Secretless Docs: https://docs.secretless.io
- CyberArk Conjur: https://www.conjur.org
- GitHub: https://github.com/cyberark/secretless-broker