Bahriya Tersane

Documentation / valkey

TLS and authentication

This guide covers how TLS and ACL bootstrap work in the chart, how clients should connect, and how to integrate with external CAs or Secret stores.

TLS — what gets encrypted

When tls.enabled: true, the chart wires TLS into every connection ValKey makes:

Connection Encrypted when tls.enabled Config rendered
Client → ValKey yes tls-port, tls-cert-file, tls-key-file, tls-ca-cert-file
Replica → Primary (replication stream) yes tls-replication yes
Sentinel ↔ Sentinel / Sentinel → ValKey yes tls-port on the Sentinel sidecar
Cluster bus (gossip) yes (cluster mode) tls-cluster yes

requireSecureTransport: true (default when TLS is on) sets port 0 and binds only the TLS port — plaintext is rejected. Drop to false if you have a migration window with mixed clients.

Certificate generation

The chart generates a self-signed CA + server certificate inline using Helm’s genCA and genSignedCert helpers. The certificate is stored in a kubernetes.io/tls Secret, and the CA is additionally published as a ConfigMap (<release>-tls-ca) for client Pods to mount.

The cert covers every DNS name ValKey might be addressed by:

  • <release> and <release>.<ns> (primary Service)
  • <release>.<ns>.svc.<clusterDomain> (FQDN — clusterDomain defaults to cluster.local)
  • <release>-headless and its FQDN
  • *.<release>-headless.<ns>.svc.<clusterDomain> (every Pod)
  • <release>-readonly (when sentinel/cluster)
  • <release>-sentinel (sentinel mode)
  • <release>-shard-<N>-headless (cluster mode, per shard)
  • 127.0.0.1 and localhost

Validity defaults to 3650 days (10 years) for the CA. On helm upgrade, the chart looks up the existing Secret and reuses the same material — certificates are never silently rotated.

Using an external CA

Set tls.existingSecret to point at a Secret of type kubernetes.io/tls containing ca.crt, tls.crt, tls.key:

tls:
  enabled: true
  existingSecret: my-cert-manager-issued-cert

This is the integration path for cert-manager. Create a Certificate resource and reference its Secret name. The chart honours external certs verbatim — rotation is the cert-manager controller’s responsibility.

Mutual TLS (mTLS)

tls:
  enabled: true
  authClients: true

authClients: true enables tls-auth-clients yes on ValKey, which requires client certificates signed by the same CA. Clients must present a valid cert signed by the CA in the TLS Secret (or a CA chain compatible with it). Pair with AUTH off when the cert is sufficient identity, or with AUTH on for defence-in-depth.

Client recipes

Mount the CA on a client Pod

spec:
  containers:
    - name: my-app
      volumeMounts:
        - name: valkey-ca
          mountPath: /etc/valkey-tls
          readOnly: true
      env:
        - name: VALKEY_CA
          value: /etc/valkey-tls/ca.crt
  volumes:
    - name: valkey-ca
      configMap:
        name: vk-valkey-tls-ca

valkey-cli

valkey-cli --tls \
  --cacert /etc/valkey-tls/ca.crt \
  -h vk-valkey.app.svc.cluster.local \
  -p 6379 \
  -a "$VALKEY_PASSWORD" \
  PING

Python (valkey-py)

import valkey

r = valkey.Valkey(
    host="vk-valkey.app.svc.cluster.local",
    port=6379,
    ssl=True,
    ssl_ca_certs="/etc/valkey-tls/ca.crt",
    username="default",
    password=os.environ["VALKEY_PASSWORD"],
)

Node.js (ioredis)

import fs from "node:fs";
import Redis from "ioredis";

const r = new Redis({
  host: "vk-valkey.app.svc.cluster.local",
  port: 6379,
  username: "default",
  password: process.env.VALKEY_PASSWORD,
  tls: { ca: fs.readFileSync("/etc/valkey-tls/ca.crt") },
});

PHP (predis/predis)

$client = new Predis\Client([
    'scheme'   => 'tls',
    'host'     => 'vk-valkey.app.svc.cluster.local',
    'port'     => 6379,
    'username' => 'default',
    'password' => getenv('VALKEY_PASSWORD'),
    'ssl'      => ['cafile' => '/etc/valkey-tls/ca.crt'],
]);

Sentinel-aware client (Node.js)

const r = new Redis({
  sentinels: [
    { host: "vk-valkey-sentinel.app.svc.cluster.local", port: 26379 },
  ],
  name: "mymaster",
  username: "default",
  password: process.env.VALKEY_PASSWORD,
  tls: { ca: fs.readFileSync("/etc/valkey-tls/ca.crt") },
  sentinelTLS: { ca: fs.readFileSync("/etc/valkey-tls/ca.crt") },
});

Cluster-aware client (Python)

from valkey.cluster import ValkeyCluster

r = ValkeyCluster(
    startup_nodes=[
        {"host": "vk-valkey.app.svc.cluster.local", "port": 6379},
    ],
    ssl=True,
    ssl_ca_certs="/etc/valkey-tls/ca.crt",
    username="default",
    password=os.environ["VALKEY_PASSWORD"],
)

Authentication

When auth.enabled: false, ValKey listens on the data port with protected-mode no and accepts any connection from the namespace. The chart relies on NetworkPolicy to limit who can reach the port.

When auth.enabled: true, the chart:

  1. Renders an ACL file from auth.users (plus the default user, set to auth.password).
  2. Stores the ACL file in the chart-managed Secret under users.acl.
  3. Stores each user’s password in the same Secret under user-<name>-password.
  4. Mounts the ACL file at /etc/valkey/acl/users.acl and instructs ValKey via aclfile.

Passwords are looked up on helm upgrade and preserved — re-running the chart never rotates them.

ACL rules cheat-sheet

Goal Rule string
Full access on ~* &* +@all
Read-only on all keys on ~* +@read
Read-write on a key prefix on ~app:* +@read +@write +@string
Pub/sub on one channel on resetkeys ~nothing &events.* +@pubsub
Lock out a user off

See https://valkey.io/topics/acl for the full grammar.

Bootstrap recipe — multi-user

auth:
  enabled: true
  password: ""             # auto-generated
  users:
    - name: app
      password: ""
      rules: "on ~app:* &app.* +@all -@dangerous"
    - name: readonly
      password: ""
      rules: "on ~* +@read +@connection"
    - name: metrics
      password: ""
      rules: "on ~* +CLIENT +PING +INFO +CLUSTER|INFO +CLUSTER|NODES"

After install:

kubectl get secret vk-valkey -n app -o json \
  | jq -r '.data | with_entries(.value |= @base64d) | {password, "user-app-password", "user-readonly-password", "user-metrics-password"}'

Using an externally-managed Secret

auth:
  enabled: true
  existingSecret: my-existing-acl-secret
  users:
    - name: app
      rules: "on ~* +@all"

The Secret must contain:

  • password — the default-user password
  • user-<name>-password for each entry in auth.users
  • users.acl — the rendered ACL file content (the chart will not render its own ACL file when existingSecret is set; the file must already exist in the Secret)

This integration path is useful when ACL passwords come from an external secret store (Vault, AWS Secrets Manager, etc.) via External Secrets Operator.