Skip to content
Back to blog Secretless Broker: Zero-Secret Applications

Secretless Broker: Zero-Secret Applications

SecurityK8s

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

  1. No secrets in app memory - can’t be dumped
  2. No secrets in logs - app never sees them
  3. No secrets in env vars - not visible in /proc
  4. Automatic rotation - Vault handles it
  5. Audit trail - all access logged in Vault
  6. Least privilege - each app gets specific creds

References

======================================== Secretless Broker + Vault + Kubernetes

Your app has zero secrets. Literally.

Found this helpful?

Comments