feat(slice 2B.3): chart Restore half — injectedWildcards conditional (0.5.7)
Add the chart-side machinery that lets Tower bypass the cert-manager
Certificate path on Reconnect by injecting a Vault-stashed wildcard
cert directly as a kubernetes.io/tls Secret.
values.yaml:
certManager.injectedWildcards: []
Each entry: { root, primary, crt, key }. Empty list = legacy ACME-only.
templates/tenants-wildcard-cert.yaml:
Build $injectedRoots index from injectedWildcards[]; per-domain
Certificate is skipped when its root has an injected entry.
templates/tenants-wildcard-secret.yaml (NEW):
Per injected entry, render kubernetes.io/tls Secret using the same
name the cert path would have produced (tenants-wildcard-tls primary,
tenants-wildcard-<root-as-dashes>-tls non-primary). Sync-wave 2 to
match the cert path's timing. Label odoosky.io/wildcard-source=
vault-injected so harvester can skip them.
Verified via helm template + self-signed dummy cert:
- Pure injection: 0 Certificate, 1 Secret (correct name + base64)
- Pure ACME: 1 Certificate, 0 Secret (status quo)
- Mixed (2 domains, 1 injected): 1 Certificate + 1 Secret
Inert without Tower wiring — existing clusters render identically to
0.5.6 because injectedWildcards defaults to []. Pushed first as the
foundation layer for the upcoming Tower restore + harvester slices.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,8 +23,8 @@ description: |
|
|||||||
Git).
|
Git).
|
||||||
|
|
||||||
type: application
|
type: application
|
||||||
version: 0.5.6
|
version: 0.5.7
|
||||||
appVersion: "0.5.6"
|
appVersion: "0.5.7"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: cert-manager
|
- name: cert-manager
|
||||||
|
|||||||
@@ -46,8 +46,19 @@
|
|||||||
"primary" true
|
"primary" true
|
||||||
"verified" true) }}
|
"verified" true) }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{/* Slice 2B.3 — index of roots that have a Vault-stashed cert
|
||||||
|
injected via certManager.injectedWildcards[]. We skip the
|
||||||
|
Certificate resource entirely for those; the sibling
|
||||||
|
tenants-wildcard-secret.yaml renders the kubernetes.io/tls
|
||||||
|
Secret directly so no ACME order is placed. */}}
|
||||||
|
{{- $injectedRoots := dict }}
|
||||||
|
{{- range .Values.certManager.injectedWildcards | default (list) }}
|
||||||
|
{{- if and .root .crt .key }}
|
||||||
|
{{- $_ := set $injectedRoots .root true }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
{{- range $i, $d := $domains }}
|
{{- range $i, $d := $domains }}
|
||||||
{{- if and $d.verified $d.wildcardHost }}
|
{{- if and $d.verified $d.wildcardHost (not (hasKey $injectedRoots $d.root)) }}
|
||||||
{{- $suffix := "" }}
|
{{- $suffix := "" }}
|
||||||
{{- if not $d.primary }}
|
{{- if not $d.primary }}
|
||||||
{{- $suffix = printf "-%s" (replace "." "-" $d.root) }}
|
{{- $suffix = printf "-%s" (replace "." "-" $d.root) }}
|
||||||
|
|||||||
62
templates/tenants-wildcard-secret.yaml
Normal file
62
templates/tenants-wildcard-secret.yaml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# tenants-wildcard injected Secret(s) — Slice 2B.3 (2026-05-04).
|
||||||
|
#
|
||||||
|
# This template is the "Restore" half of the Vault Stash/Restore
|
||||||
|
# flow. Tower harvests successfully-issued wildcard cert Secrets
|
||||||
|
# into per-tenant Vault paths (`v3/tenants/<id>/certificates/<root>`).
|
||||||
|
# On Reconnect for the same tenant + root, Tower reads the stash back
|
||||||
|
# and passes it as helm values:
|
||||||
|
#
|
||||||
|
# certManager.injectedWildcards:
|
||||||
|
# - root: "acme.com"
|
||||||
|
# primary: true # mirrors tenant.domains[i].primary
|
||||||
|
# crt: "<PEM cert chain>"
|
||||||
|
# key: "<PEM private key>"
|
||||||
|
#
|
||||||
|
# When an entry is present, this file emits a kubernetes.io/tls
|
||||||
|
# Secret with the SAME name the cert-manager Certificate path would
|
||||||
|
# have produced — so existing IngressRoutes and per-instance
|
||||||
|
# references don't have to change to use the injected variant.
|
||||||
|
#
|
||||||
|
# tenants-wildcard-cert.yaml's matching skip-condition keeps the
|
||||||
|
# Certificate resource from rendering for the same root, so no
|
||||||
|
# ACME order is placed and the Let's Encrypt rate-limit budget is
|
||||||
|
# preserved across reconnect churn.
|
||||||
|
#
|
||||||
|
# Naming contract (must mirror tenants-wildcard-cert.yaml exactly):
|
||||||
|
# primary → `tenants-wildcard-tls`
|
||||||
|
# non-primary → `tenants-wildcard-<root-with-dots-as-dashes>-tls`
|
||||||
|
#
|
||||||
|
# Empty / missing fields on an entry → silently skip that entry.
|
||||||
|
# Tower is responsible for populating crt + key + root before passing
|
||||||
|
# the entry through; a half-formed entry shouldn't render a broken
|
||||||
|
# Secret that would mask the real ACME path.
|
||||||
|
{{- range .Values.certManager.injectedWildcards | default (list) }}
|
||||||
|
{{- if and .root .crt .key }}
|
||||||
|
{{- $suffix := "" }}
|
||||||
|
{{- if not .primary }}
|
||||||
|
{{- $suffix = printf "-%s" (replace "." "-" .root) }}
|
||||||
|
{{- end }}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: {{ printf "tenants-wildcard%s-tls" $suffix | quote }}
|
||||||
|
namespace: tenants
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/managed-by: cluster-platform-v3
|
||||||
|
odoosky.io/domain-root: {{ .root | quote }}
|
||||||
|
odoosky.io/wildcard-source: vault-injected
|
||||||
|
{{- if .primary }}
|
||||||
|
odoosky.io/domain-primary: "true"
|
||||||
|
{{- end }}
|
||||||
|
annotations:
|
||||||
|
# Wave 2 to land in the same step as the (skipped) Certificate
|
||||||
|
# resource would have. Keeps the substrate-Ready timing model
|
||||||
|
# identical between ACME-issued and injected paths.
|
||||||
|
argocd.argoproj.io/sync-wave: "2"
|
||||||
|
type: kubernetes.io/tls
|
||||||
|
data:
|
||||||
|
tls.crt: {{ .crt | b64enc | quote }}
|
||||||
|
tls.key: {{ .key | b64enc | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
21
values.yaml
21
values.yaml
@@ -53,6 +53,27 @@ acme:
|
|||||||
# the actual subchart values live below under the dep name `cert-manager`.
|
# the actual subchart values live below under the dep name `cert-manager`.
|
||||||
certManager:
|
certManager:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
# injectedWildcards — Slice 2B.3 (2026-05-04). Tower's per-tenant
|
||||||
|
# Vault-stash flow harvests successfully-issued wildcard cert
|
||||||
|
# Secrets and re-injects them on Reconnect to bypass Let's Encrypts
|
||||||
|
# 5-cert/identifier/168h rate limit. When an entry is present
|
||||||
|
# for a tenant.domains[].root, the chart:
|
||||||
|
# - SKIPS the cert-manager Certificate resource for that root
|
||||||
|
# (so no ACME order is placed)
|
||||||
|
# - Renders a kubernetes.io/tls Secret with the injected crt/key
|
||||||
|
# under the SAME name the cert-manager path would have used
|
||||||
|
# (`tenants-wildcard-tls` for primary, `tenants-wildcard-<root-
|
||||||
|
# with-dots-as-dashes>-tls` otherwise) so existing
|
||||||
|
# IngressRoutes don't need to change.
|
||||||
|
# Empty list = legacy ACME-only path. Per-domain — a tenant can
|
||||||
|
# mix injected + ACME-issued certs across multiple roots.
|
||||||
|
#
|
||||||
|
# Each entry shape:
|
||||||
|
# - root: "acme.com"
|
||||||
|
# - primary: true # mirrors tenant.domains[i].primary
|
||||||
|
# - crt: "<PEM cert chain>"
|
||||||
|
# - key: "<PEM private key>"
|
||||||
|
injectedWildcards: []
|
||||||
|
|
||||||
# cert-manager — values passed THROUGH to the upstream jetstack subchart
|
# cert-manager — values passed THROUGH to the upstream jetstack subchart
|
||||||
# (Chart.yaml dependency name = "cert-manager"). Subchart values must
|
# (Chart.yaml dependency name = "cert-manager"). Subchart values must
|
||||||
|
|||||||
Reference in New Issue
Block a user