Dynamic SSL Pinning
for Kubernetes

Helm chart for deploying the ssl-pinning server — a service that continuously tracks TLS certificate public keys and serves them as a cryptographically signed JSON registry, enabling pin rotation without app updates.

Helm Chart Kubernetes 1.24+ helm-apps/lib BSD-3-Clause

Installation

1

Add Helm repository

helm repo add ssl-pinning https://ssl-pinning.github.io/charts
helm repo update
2

Generate RSA signing keys

The server signs the pin registry with a 4096-bit RSA private key. The public key is embedded in the Android APK for signature verification.

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out prv.pem
openssl rsa -pubout -in prv.pem -out pub.pem
Important: keep prv.pem secret — it never leaves the server. Only pub.pem is distributed to clients.
3

Create values.yaml

apps-secrets:
  ssl-pinning-tls:
    data:
      prv.pem: <base64 -i prv.pem | tr -d '\n'>

apps-configmaps:
  ssl-pinning-config:
    data:
      config.yaml: |
        keys:
          - fqdn: api.example.com
            domainName: "api.example.com"
        server:
          listen: 0.0.0.0:7500

apps-stateless:
  ssl-pinning:
    replicas: 1
    containers:
      app:
        image:
          name: ghcr.io/ssl-pinning/ssl-pinning
          staticTag: v1.1.0
        ports: |
          - name: http
            containerPort: 7500
        env:
          SSL_PINNING_SERVER_LISTEN: 0.0.0.0:7500
          SSL_PINNING_STORAGE_TYPE: redis
          SSL_PINNING_STORAGE_DSN: redis://valkey:6379/0
          SSL_PINNING_TLS_DIR: /opt/ssl-pinning/tls
          SSL_PINNING_TLS_DUMP_INTERVAL: 1s
        volumeMounts: |
          - name: tls
            mountPath: /opt/ssl-pinning/tls
            readOnly: true
          - name: config
            mountPath: /etc/ssl-pinning
            readOnly: true
    volumes: |
      - name: tls
        secret:
          secretName: ssl-pinning-tls
      - name: config
        configMap:
          name: ssl-pinning-config
    service:
      enabled: true

apps-ingresses:
  ssl-pinning:
    class: nginx
    rules: |
      - host: ssl-pinning.example.com
        http:
          paths:
            - path: /
              pathType: Prefix
              backend:
                service:
                  name: ssl-pinning
                  port:
                    number: 7500
4

Install

helm install ssl-pinning ssl-pinning/ssl-pinning \
  --namespace ssl-pinning \
  --create-namespace \
  -f values.yaml
5

Get public key for the Android / iOS SDK

Pass the base64-encoded public key as signingKeyBase64 in SslPinningConfig.

base64 -i pub.pem | tr -d '\n'

Keys configuration

Each entry under keys defines a domain to track. Multiple entries can share the same file to group them into one API response.

FieldDefaultDescription
fqdnRequired. Domain to dial on port 443 for certificate tracking.
domainName*.{fqdn}Hostname pattern for OkHttp CertificatePinner on the client side.
file{fqdn}.jsonGroups this entry into a named response file. Multiple FQDNs can share the same file.
keys:
  - fqdn: google.com
    domainName: "www.google.com"
  - fqdn: mail.google.com
    file: google.com.json      # grouped into the same endpoint
    domainName: "mail.google.com"

Both entries are served at GET /api/v1/google.com.json

Storage backends

TypeDSN formatNotes
memoryEphemeral. Lost on restart. Development only.
fsWrites signed JSON to dump_dir. Serves files directly.
redisredis://:pass@host:6379/0Shared state across multiple instances.
postgrespostgres://user:pass@host/dbPersistent. Migrations run automatically on startup.

Configuration reference

All settings can be set via config file, environment variable, or CLI flag. Priority: flags > env vars > config file > defaults. Env var format: SSL_PINNING_ prefix + upper snake case (e.g. log.levelSSL_PINNING_LOG_LEVEL).

Server

Env var / FlagDefaultDescription
SSL_PINNING_SERVER_LISTEN127.0.0.1:7500HTTP listen address and port
SSL_PINNING_SERVER_READ_TIMEOUT5sRequest read timeout
SSL_PINNING_SERVER_WRITE_TIMEOUT5sResponse write timeout

Storage

Env var / FlagDefaultDescription
SSL_PINNING_STORAGE_TYPE
--storage-type
memorymemory · fs · redis · postgres
SSL_PINNING_STORAGE_DSN
--storage-dsn
Connection string for redis or postgres
SSL_PINNING_STORAGE_DUMP_DIR
--storage-dump-dir
/tmpDirectory for fs/memory persistence dumps
SSL_PINNING_STORAGE_MAX_IDLE_CONNS
--storage-max-idle-conns
5Max idle connections in pool
SSL_PINNING_STORAGE_MAX_OPEN_CONNS
--storage-max-open-conns
5Max open connections in pool
SSL_PINNING_STORAGE_CONN_MAX_IDLE_TIME
--storage-conn-max-idle-time
5mMax idle time for a connection
SSL_PINNING_STORAGE_CONN_MAX_LIFETIME
--storage-conn-max-lifetime
30mMax lifetime of a connection

TLS

Env varDefaultDescription
SSL_PINNING_TLS_DIR{config-path}/tlsDirectory containing prv.pem and pub.pem
SSL_PINNING_TLS_DUMP_INTERVAL5sHow often keys are flushed to storage
SSL_PINNING_TLS_TIMEOUT5sTLS dial timeout per domain

Logging

Env var / FlagDefaultDescription
SSL_PINNING_LOG_FORMAT
--log-format
jsonjson · text
SSL_PINNING_LOG_LEVEL
--log-level
infodebug · info · warn · error
SSL_PINNING_LOG_PRETTY
--log-pretty
falsePretty-print log output

Global CLI flags

FlagDefaultDescription
--config-fileconfig.yamlConfiguration file name
--config-path/etc/ssl-pinningConfiguration file directory

API

EndpointDescription
GET /api/v1/{file}Signed JSON pin registry (e.g. /api/v1/api.example.com.json)
GET /health/livenessLiveness probe
GET /health/readinessReadiness probe
GET /health/startupStartup probe
GET /metricsPrometheus metrics (port 9090)