Files
devops-infra-helm-charts-gcp/helm-templates/redis/values.yaml
T
Mukul SharmaandClaude Opus 5 76b4ddd2de Add a Redis chart for toolshed's managed cache add-on
toolshed provisions a per-app Redis ACL user, scoped to its own key
prefix, on request (internal/dbprovision.EnsureRedisUser). Nothing in this
cluster ran Redis — Harbor's internal one is Harbor's and is not ACL
configured — so there was nowhere for that to point.

Hand-written rather than vendoring Bitnami's, same reasoning as the
sibling postgresql chart: Broadcom has been retiring and freezing images
behind that repo (infra issue #4, where it broke Contour twice), and Redis
publishes no official chart either.

The authentication design is the part worth reading before changing
anything. Redis is started with an ACL file and NO requirepass, and that
distinction is a security property rather than a style choice:

- toolshed persists provisioned users with ACL SAVE, which requires an
  aclfile. Without it every provisioned user is lost on the next restart.
- But ACL SAVE also serialises the default user. With requirepass, the
  saved entry comes back as `user default on nopass ~* &* +@all`, and
  after the next restart the ACL file wins — leaving Redis open to
  UNAUTHENTICATED access with full permissions. Verified directly: with
  requirepass, the restarted server answered an unauthenticated PING with
  PONG and served a key.

So the default user is defined in the ACL file instead, seeded once by an
init container that deliberately never overwrites an existing file —
overwriting would delete every user toolshed had provisioned into it,
reintroducing the same lockout from the other end. The documented
consequence is that rotating the admin password in Vault does not
propagate on its own; that needs ACL SETUSER default + ACL SAVE against
the running server.

Sized for a node at its ceiling: 32Mi requested, 96Mi limit, maxmemory
48mb. The limit sits above maxmemory on purpose, so Redis reaches its own
eviction policy rather than being OOM-killed, which would lose the whole
instance instead of the coldest keys. Snapshotting is off — what must
survive a restart is the ACL file, which ACL SAVE writes independently of
RDB, and cached values are by definition reconstructible.

allkeys-lru because this backs a connection kind called "cache" and
eviction under pressure is that contract; values.yaml says plainly that an
app using Redis as its only copy of something wants noeviction instead.

Verified: helm template, then a real deploy to a k3d cluster — provisioned
users through toolshed's own code, deleted the pod, and confirmed all five
came back with their key patterns intact, the init container declined to
overwrite, unauthenticated access got NOAUTH, and a user writing outside
its prefix got NOPERM.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wajog7nELA3i8JWTjxYGHF
2026-09-09 12:35:02 +05:30

85 lines
3.6 KiB
YAML

# Chart defaults. Real configuration lives in
# helm-overrides/k8s-admin-prd-ase1/redis/custom-values.yaml.
fullnameOverride: redis
image:
# Pulled from Docker Hub, like every other infra component here (gitea,
# vault, harbor, postgresql). The base-images mirror in Harbor exists to
# remove Docker Hub from the *application build* path — it is not in play
# for platform components.
repository: redis
tag: "7-alpine"
pullPolicy: IfNotPresent
# Name of the Secret holding the admin password. Created by External
# Secrets from Vault, not by this chart — a chart that generates its own
# password regenerates it on every render, which would rewrite the ACL file
# and lock every already-provisioned app out of its own data.
existingSecret: redis-credentials
secretKeys:
password: password
service:
port: 6379
persistence:
enabled: true
# local-path-provisioner, this cluster's default StorageClass. Small: this
# holds the ACL file and (if enabled) an RDB snapshot, not a dataset of
# any size — maxmemory below is the real ceiling on what Redis will hold.
# The volume is not resizable in place with this provisioner, so it is
# sized up front.
storageClass: local-path
size: 1Gi
config:
# ACL FILE, NOT requirepass. This distinction is load-bearing and easy to
# "simplify" into a security hole, so it is written down here rather than
# left to be rediscovered:
#
# toolshed provisions per-app users with ACL SETUSER, and persists them
# with ACL SAVE (internal/dbprovision.EnsureRedisUser) — without that
# save, every provisioned user is lost on the next restart and every app
# using Redis fails to authenticate with credentials that still look
# valid. ACL SAVE requires an aclfile; that is why one is configured.
#
# But ACL SAVE also writes the *default* user's state to that file. With
# `requirepass` set and the default user defined only by it, the saved
# entry comes back as `user default on nopass ~* &* +@all` — and after
# the next restart the ACL file wins, leaving Redis accepting
# UNAUTHENTICATED connections with full access. Verified directly, not
# inferred: with requirepass the restarted server answered an
# unauthenticated PING with PONG and served a key.
#
# Defining the default user in the ACL file instead (seeded by the init
# container, see the StatefulSet) keeps its password across every
# subsequent ACL SAVE — the same restart then correctly answers
# `NOAUTH Authentication required.`
#
# If you ever add `requirepass` here, you reintroduce that hole.
maxmemory: 48mb
# allkeys-lru, because this backs a connection kind literally called
# "cache" and eviction under pressure is that contract. An app using
# Redis as its only copy of something wants noeviction instead — at
# which case writes start failing when full rather than data silently
# disappearing. Neither is safe for every use; this one matches the name.
maxmemoryPolicy: allkeys-lru
# Snapshotting off. What must survive a restart is the ACL file, which is
# written by ACL SAVE independently of RDB/AOF. Cached values are by
# definition reconstructible, and on a node at its memory ceiling a
# background save's copy-on-write spike is a real risk for no benefit.
save: ""
# Tuned for a node with 8GB total that is already near its ceiling. The
# request is what the scheduler reserves; the limit is sized above
# maxmemory so Redis hits its own eviction policy rather than being
# OOM-killed by the kernel, which loses the whole instance instead of the
# coldest keys.
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
memory: 96Mi