Skip to content

Commit b1f5d95

Browse files
committed
feat(ingress): serve operator wildcard via default SSL certificate
When publishing.certificates.wildcardSecretName is set, the root tenant's ingress-nginx controller now serves the operator-supplied wildcard Secret as its --default-ssl-certificate, and the platform service Ingresses stop requesting their own per-host ACME certificates. The flag is set only on the publishing controller (Release.Namespace == expose-ingress) because ingress-nginx loads the Secret from its own namespace; child-tenant controllers cannot reach a Secret in another namespace, so they keep the existing ACME path. Each service Ingress (dashboard, keycloak, grafana, alerta, bucket, linstor-gui, harbor, seaweedfs S3, bootbox) drops its cert-manager cluster-issuer annotation, the http01 challenge-class annotation, and its tls block in wildcard mode; nginx then falls back to the default certificate for those hosts. Other annotations and routing are unchanged. Each file keeps its existing render guard intact, and the annotations block collapses cleanly where the cert annotations were its only content. Assisted-By: Claude <[email protected]> Signed-off-by: Aleksei Sviridkin <[email protected]>
1 parent 6e219c2 commit b1f5d95

26 files changed

Lines changed: 602 additions & 0 deletions

File tree

‎packages/apps/harbor/Makefile‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ NAME=harbor
22

33
include ../../../hack/package.mk
44

5+
test:
6+
helm unittest .
7+
58
generate:
69
cozyvalues-gen -m 'harbor' -v values.yaml -s values.schema.json -r README.md -g ../../../api/apps/v1alpha1/harbor/types.go
710
../../../hack/update-crd.sh

‎packages/apps/harbor/templates/ingress.yaml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{{- $harborHost := .Values.host | default (printf "%s.%s" .Release.Name $host) }}
55
{{- $solver := (index .Values._cluster "solver") | default "http01" }}
66
{{- $clusterIssuer := (index .Values._cluster "issuer-name") | default "letsencrypt-prod" }}
7+
{{- $wildcardSecret := (index .Values._cluster "wildcard-secret-name") | default "" }}
78
{{- if not $gateway }}
89
---
910
apiVersion: networking.k8s.io/v1
@@ -16,16 +17,21 @@ metadata:
1617
nginx.ingress.kubernetes.io/proxy-send-timeout: "900"
1718
nginx.ingress.kubernetes.io/ssl-redirect: "true"
1819
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
20+
{{- /* Operator wildcard mode: nginx serves the default SSL cert, so skip per-host ACME. */}}
21+
{{- if not $wildcardSecret }}
1922
{{- if eq $solver "http01" }}
2023
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }}
2124
{{- end }}
2225
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
26+
{{- end }}
2327
spec:
2428
ingressClassName: {{ $ingress }}
29+
{{- if not $wildcardSecret }}
2530
tls:
2631
- hosts:
2732
- {{ $harborHost | quote }}
2833
secretName: {{ .Release.Name }}-ingress-tls
34+
{{- end }}
2935
rules:
3036
- host: {{ $harborHost | quote }}
3137
http:
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
suite: harbor ingress wildcard mode drops per-host ACME
2+
templates:
3+
- templates/ingress.yaml
4+
5+
release:
6+
name: harbor
7+
namespace: tenant-root
8+
9+
# harbor gates its Ingress on an empty _namespace.gateway. In wildcard
10+
# mode the cert-manager + http01 annotations and the tls block are
11+
# dropped; the other nginx annotations stay.
12+
13+
tests:
14+
- it: drops cert-manager annotation and tls block when wildcard secret is set
15+
set:
16+
_namespace:
17+
host: example.org
18+
ingress: tenant-root
19+
gateway: ""
20+
_cluster:
21+
issuer-name: letsencrypt-prod
22+
solver: http01
23+
wildcard-secret-name: wildcard-tls
24+
asserts:
25+
- equal:
26+
path: kind
27+
value: Ingress
28+
- notExists:
29+
path: metadata.annotations["cert-manager.io/cluster-issuer"]
30+
- notExists:
31+
path: spec.tls
32+
- exists:
33+
path: metadata.annotations["nginx.ingress.kubernetes.io/proxy-body-size"]
34+
35+
- it: keeps cert-manager annotation and tls block without wildcard secret
36+
set:
37+
_namespace:
38+
host: example.org
39+
ingress: tenant-root
40+
gateway: ""
41+
_cluster:
42+
issuer-name: letsencrypt-prod
43+
solver: http01
44+
asserts:
45+
- equal:
46+
path: metadata.annotations["cert-manager.io/cluster-issuer"]
47+
value: letsencrypt-prod
48+
- exists:
49+
path: spec.tls[0].secretName

‎packages/extra/bootbox/Makefile‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ NAMESPACE=tenant-root
33

44
include ../../../hack/package.mk
55

6+
test:
7+
helm unittest .
8+
69
generate:
710
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
811
../../../hack/update-crd.sh

‎packages/extra/bootbox/templates/matchbox/ingress.yaml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,34 @@
22
{{- $clusterIssuer := (index .Values._cluster "issuer-name") | default "letsencrypt-prod" }}
33
{{- $ingress := .Values._namespace.ingress }}
44
{{- $host := .Values._namespace.host }}
5+
{{- $wildcardSecret := (index .Values._cluster "wildcard-secret-name") | default "" }}
56
apiVersion: networking.k8s.io/v1
67
kind: Ingress
78
metadata:
89
name: bootbox
910
labels:
1011
app: bootbox
12+
{{- /* Operator wildcard mode: nginx serves the default SSL cert, so skip per-host ACME. Render annotations only when something remains. */}}
13+
{{- if or (not $wildcardSecret) .Values.whitelistHTTP }}
1114
annotations:
15+
{{- if not $wildcardSecret }}
1216
{{- if eq $solver "http01" }}
1317
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }}
1418
{{- end }}
1519
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
20+
{{- end }}
1621
{{- if .Values.whitelistHTTP }}
1722
nginx.ingress.kubernetes.io/whitelist-source-range: "{{ join "," (.Values.whitelist | default "0.0.0.0/32") }}"
1823
{{- end }}
24+
{{- end }}
1925
spec:
2026
ingressClassName: {{ $ingress }}
27+
{{- if not $wildcardSecret }}
2128
tls:
2229
- hosts:
2330
- "{{ printf "bootbox.%s" (.Values.host | default $host) }}"
2431
secretName: bootbox-tls
32+
{{- end }}
2533
rules:
2634
- host: "{{ printf "bootbox.%s" (.Values.host | default $host) }}"
2735
http:
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
suite: bootbox ingress wildcard mode drops per-host ACME
2+
templates:
3+
- templates/matchbox/ingress.yaml
4+
5+
release:
6+
name: bootbox
7+
namespace: tenant-root
8+
9+
# bootbox deploys into tenant-root and uses the publishing tenant's
10+
# ingress controller (_namespace.ingress), so in wildcard mode it must
11+
# also stop minting its own per-host ACME cert. The cert-manager + http01
12+
# annotations and the tls block are dropped. bootbox's only other
13+
# annotation (whitelist-source-range) is conditional, so the annotations
14+
# block must collapse entirely when wildcard is set and no whitelist is
15+
# configured.
16+
17+
tests:
18+
- it: collapses the annotations block and drops tls when wildcard set and no whitelist
19+
set:
20+
whitelistHTTP: false
21+
_namespace:
22+
host: example.org
23+
ingress: tenant-root
24+
_cluster:
25+
issuer-name: letsencrypt-prod
26+
solver: http01
27+
wildcard-secret-name: wildcard-tls
28+
asserts:
29+
- equal:
30+
path: kind
31+
value: Ingress
32+
- notExists:
33+
path: metadata.annotations
34+
- notExists:
35+
path: spec.tls
36+
37+
- it: keeps cert-manager annotation and tls block without wildcard secret
38+
set:
39+
_namespace:
40+
host: example.org
41+
ingress: tenant-root
42+
_cluster:
43+
issuer-name: letsencrypt-prod
44+
solver: http01
45+
asserts:
46+
- equal:
47+
path: metadata.annotations["cert-manager.io/cluster-issuer"]
48+
value: letsencrypt-prod
49+
- equal:
50+
path: spec.tls[0].secretName
51+
value: bootbox-tls
52+
53+
- it: keeps whitelist annotation but drops cert-manager when wildcard set with whitelist
54+
set:
55+
whitelistHTTP: true
56+
whitelist:
57+
- "192.0.2.0/24"
58+
_namespace:
59+
host: example.org
60+
ingress: tenant-root
61+
_cluster:
62+
issuer-name: letsencrypt-prod
63+
solver: http01
64+
wildcard-secret-name: wildcard-tls
65+
asserts:
66+
- notExists:
67+
path: metadata.annotations["cert-manager.io/cluster-issuer"]
68+
- equal:
69+
path: metadata.annotations["nginx.ingress.kubernetes.io/whitelist-source-range"]
70+
value: "192.0.2.0/24"
71+
- notExists:
72+
path: spec.tls

‎packages/extra/ingress/templates/nginx-ingress.yaml‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }}
22
{{- $exposeExternalIPs := (index .Values._cluster "expose-external-ips") | default "" | nospace }}
3+
{{- $wildcardSecret := (index .Values._cluster "wildcard-secret-name") | default "" }}
34
{{- /* PROXY-protocol is enabled either explicitly per-app (.Values.proxyProtocol)
45
or, for the host control plane, via the platform-level publishing.proxyProtocol
56
switch carried in _cluster.proxy-protocol and applied only to the external-facing
@@ -47,6 +48,19 @@ spec:
4748
controller:
4849
replicaCount: {{ .Values.replicas }}
4950
ingressClass: {{ .Release.Namespace }}
51+
{{- /*
52+
Operator-supplied wildcard mode. Serve the pre-existing
53+
wildcard Secret as the default SSL certificate so per-host
54+
ACME certs are unnecessary. Only the publishing controller
55+
(Release.Namespace == expose-ingress) gets the flag: the
56+
Secret lives in that namespace and ingress-nginx must read it
57+
from its own namespace, so child-tenant controllers cannot
58+
point at it. The reference is therefore always same-namespace.
59+
*/}}
60+
{{- if and $wildcardSecret (eq .Release.Namespace $exposeIngress) }}
61+
extraArgs:
62+
default-ssl-certificate: "{{ $exposeIngress }}/{{ $wildcardSecret }}"
63+
{{- end }}
5064
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 10 }}
5165
ingressClassResource:
5266
name: {{ .Release.Namespace }}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
suite: ingress-nginx default-ssl-certificate wiring
2+
templates:
3+
- templates/nginx-ingress.yaml
4+
5+
release:
6+
name: ingress
7+
namespace: tenant-root
8+
9+
# When the operator supplies a wildcard Secret
10+
# (_cluster.wildcard-secret-name), the publishing tenant's ingress
11+
# controller must serve it as the default SSL certificate so per-host
12+
# ACME certs are no longer needed. The reference is same-namespace
13+
# (publishing namespace == controller namespace), and the flag is set
14+
# only on the publishing controller — child-tenant controllers cannot
15+
# read a Secret from another namespace, so they must NOT get the flag.
16+
17+
tests:
18+
- it: sets default-ssl-certificate on the publishing controller when wildcard is set
19+
set:
20+
_cluster:
21+
expose-ingress: tenant-root
22+
wildcard-secret-name: wildcard-tls
23+
asserts:
24+
- equal:
25+
path: spec.values.ingress-nginx.controller.extraArgs.default-ssl-certificate
26+
value: tenant-root/wildcard-tls
27+
28+
- it: does not set default-ssl-certificate when no wildcard secret
29+
set:
30+
_cluster:
31+
expose-ingress: tenant-root
32+
asserts:
33+
- notExists:
34+
path: spec.values.ingress-nginx.controller.extraArgs.default-ssl-certificate
35+
36+
- it: does not set default-ssl-certificate on a non-publishing tenant controller
37+
release:
38+
name: ingress
39+
namespace: tenant-foo
40+
set:
41+
_cluster:
42+
expose-ingress: tenant-root
43+
wildcard-secret-name: wildcard-tls
44+
asserts:
45+
- notExists:
46+
path: spec.values.ingress-nginx.controller.extraArgs.default-ssl-certificate

‎packages/extra/seaweedfs/Makefile‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ NAME=seaweedfs
22

33
include ../../../hack/package.mk
44

5+
test:
6+
helm unittest .
7+
58
generate:
69
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
710
../../../hack/update-crd.sh

‎packages/extra/seaweedfs/templates/seaweedfs.yaml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
{{- $host := .Values._namespace.host }}
9898
{{- $solver := (index .Values._cluster "solver") | default "http01" }}
9999
{{- $clusterIssuer := (index .Values._cluster "issuer-name") | default "letsencrypt-prod" }}
100+
{{- $wildcardSecret := (index .Values._cluster "wildcard-secret-name") | default "" }}
100101
apiVersion: helm.toolkit.fluxcd.io/v2
101102
kind: HelmRelease
102103
metadata:
@@ -256,14 +257,19 @@ spec:
256257
nginx.ingress.kubernetes.io/client-body-timeout: "3600"
257258
nginx.ingress.kubernetes.io/client-header-timeout: "120"
258259
nginx.ingress.kubernetes.io/service-upstream: "true"
260+
{{- /* Operator wildcard mode: nginx serves the default SSL cert, so skip per-host ACME. */}}
261+
{{- if not $wildcardSecret }}
259262
{{- if eq $solver "http01" }}
260263
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }}
261264
{{- end }}
262265
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
266+
{{- end }}
267+
{{- if not $wildcardSecret }}
263268
tls:
264269
- hosts:
265270
- {{ .Values.host | default (printf "s3.%s" $host) }}
266271
secretName: {{ .Release.Name }}-s3-ingress-tls
272+
{{- end }}
267273
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.s3.resourcesPreset .Values.s3.resources $) | nindent 10 }}
268274
cosi:
269275
driverName: "{{ .Release.Namespace }}.seaweedfs.objectstorage.k8s.io"

0 commit comments

Comments
 (0)