diff --git a/README.md b/README.md index a785e41..5577f5d 100644 --- a/README.md +++ b/README.md @@ -122,12 +122,70 @@ CRDs are located in `chart/crds/` and are installed automatically. The API group ### TLS Certificates (cert-manager) -The chart creates a self-signed cert-manager `Issuer` and two `Certificate` resources automatically: +The chart manages two cert-manager `Certificate` resources: -- **Serving certificate** — TLS for the webhook endpoint (stored in Secret `webhook-server-certificate`) -- **Metrics certificate** — TLS for the metrics endpoint (stored in Secret `metrics-server-cert`) +- **Serving certificate** — TLS for the webhook endpoint (Secret `webhook-server-certificate`). Only rendered when `webhooks.enabled=true`. +- **Metrics certificate** — TLS for the metrics endpoint (Secret `metrics-server-cert`). Always rendered. -Both certificates use the built-in self-signed issuer deployed by the chart. No external issuer is required. These resources are not configurable via `values.yaml`. +#### Self-signed Issuer + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `selfSignedIssuer.enabled` | Create a self-signed cert-manager `Issuer` (named `-selfsigned-issuer`) and use it as the default issuer for both certificates. Set to `false` when bringing your own issuer. | `true` | + +When `selfSignedIssuer.enabled=true` and no explicit `issuerRef` is configured on a certificate, the chart automatically points that certificate at the built-in self-signed issuer. When `selfSignedIssuer.enabled=false`, an `issuerRef` **must** be provided for each certificate that is rendered, otherwise the chart will fail with a descriptive error. + +#### Metrics Certificate (`metricsCert`) + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `metricsCert.issuerRef` | cert-manager issuer reference. Overrides `selfSignedIssuer` for this certificate. | self-signed issuer | +| `metricsCert.issuerRef.kind` | Issuer kind (`Issuer` or `ClusterIssuer`) | — | +| `metricsCert.issuerRef.name` | Issuer name | — | +| `metricsCert.issuerRef.group` | Issuer API group (optional) | — | +| `metricsCert.duration` | Certificate validity duration (e.g. `2160h0m0s`) | cert-manager default | +| `metricsCert.renewBefore` | How long before expiry cert-manager renews the certificate (e.g. `360h0m0s`) | cert-manager default | +| `metricsCert.privateKey.algorithm` | Private key algorithm (`RSA`, `ECDSA`, `Ed25519`) | cert-manager default | +| `metricsCert.privateKey.size` | Private key size in bits | cert-manager default | +| `metricsCert.privateKey.encoding` | Private key encoding (`PKCS1`, `PKCS8`) | cert-manager default | +| `metricsCert.privateKey.rotationPolicy` | Key rotation policy (`Never`, `Always`) | cert-manager default | +| `metricsCert.subject` | X.509 subject fields (e.g. `organizations`, `countries`) | — | + +#### Serving Certificate (`servingCert`) + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `servingCert.issuerRef` | cert-manager issuer reference. Overrides `selfSignedIssuer` for this certificate. | self-signed issuer | +| `servingCert.issuerRef.kind` | Issuer kind (`Issuer` or `ClusterIssuer`) | — | +| `servingCert.issuerRef.name` | Issuer name | — | +| `servingCert.issuerRef.group` | Issuer API group (optional) | — | +| `servingCert.duration` | Certificate validity duration (e.g. `2160h0m0s`) | cert-manager default | +| `servingCert.renewBefore` | How long before expiry cert-manager renews the certificate (e.g. `360h0m0s`) | cert-manager default | +| `servingCert.privateKey.algorithm` | Private key algorithm (`RSA`, `ECDSA`, `Ed25519`) | cert-manager default | +| `servingCert.privateKey.size` | Private key size in bits | cert-manager default | +| `servingCert.privateKey.encoding` | Private key encoding (`PKCS1`, `PKCS8`) | cert-manager default | +| `servingCert.privateKey.rotationPolicy` | Key rotation policy (`Never`, `Always`) | cert-manager default | +| `servingCert.subject` | X.509 subject fields (e.g. `organizations`, `countries`) | — | +| `servingCert.usages` | Key usages (e.g. `server auth`, `client auth`) | — | + +#### Example: Bring your own ClusterIssuer + +```yaml +selfSignedIssuer: + enabled: false + +metricsCert: + issuerRef: + kind: ClusterIssuer + name: letsencrypt-prod + duration: 8760h0m0s + renewBefore: 720h0m0s + +servingCert: + issuerRef: + kind: ClusterIssuer + name: letsencrypt-prod +``` ### Other @@ -160,7 +218,7 @@ The chart deploys: 1. **Deployment** — The controller manager pod running `/manager` with webhook and health probe endpoints 2. **Services** — A metrics service (port 8443) and a webhook service (port 443 → 9443) 3. **RBAC** — ClusterRoles and bindings for the manager, leader election, metrics auth, and per-CRD admin/editor/viewer roles -4. **Certificates** — A self-signed Issuer, a serving certificate for the webhook endpoint, and a metrics certificate (all via cert-manager) +4. **Certificates** — An optional self-signed `Issuer` (controlled by `selfSignedIssuer.enabled`), a serving certificate for the webhook endpoint (when `webhooks.enabled=true`), and a metrics certificate — all via cert-manager. Both certificates fall back to the built-in self-signed issuer unless a custom `issuerRef` is configured. 5. **ValidatingWebhookConfiguration** — Admission webhooks validating `Organization` and `Repository` resources on CREATE/UPDATE 6. **NetworkPolicy** — Allows webhook traffic from `kube-system` namespace to the controller on port 9443 7. **ServiceMonitor** — Prometheus ServiceMonitor for scraping `/metrics` from the controller diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index da8c70d..f0255ef 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -79,6 +79,13 @@ namespaceSelector: {{- end }} {{- end }} +{{/* +Name of the internal self-signed cert-manager Issuer. +*/}} +{{- define "git-hubby.selfSignedIssuerName" -}} +{{- printf "%s-selfsigned-issuer" (include "git-hubby.fullname" .) }} +{{- end }} + {{/* WATCH_NAMESPACE value: joins watchedNamespaces list with commas. */}} diff --git a/templates/metrics-certs.yaml b/templates/metrics-certs.yaml index 5dd65ad..dc19570 100644 --- a/templates/metrics-certs.yaml +++ b/templates/metrics-certs.yaml @@ -10,7 +10,27 @@ spec: }}.svc' - '{{ include "git-hubby.fullname" . }}-controller-manager-metrics-service.{{ .Release.Namespace }}.svc.{{ .Values.kubernetesClusterDomain }}' + secretName: metrics-server-cert + {{- with .Values.metricsCert.duration }} + duration: {{ . }} + {{- end }} issuerRef: + {{- if .Values.metricsCert.issuerRef }} + {{- toYaml .Values.metricsCert.issuerRef | nindent 4 }} + {{- else if .Values.selfSignedIssuer.enabled }} kind: Issuer - name: '{{ include "git-hubby.fullname" . }}-selfsigned-issuer' - secretName: metrics-server-cert + name: {{ include "git-hubby.selfSignedIssuerName" . }} + {{- else }} + {{- fail "metricsCert.issuerRef must be set when selfSignedIssuer.enabled is false" }} + {{- end }} + {{- with .Values.metricsCert.privateKey }} + privateKey: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.metricsCert.renewBefore }} + renewBefore: {{ . }} + {{- end }} + {{- with .Values.metricsCert.subject }} + subject: + {{- toYaml . | nindent 4 }} + {{- end }} diff --git a/templates/selfsigned-issuer.yaml b/templates/selfsigned-issuer.yaml index 3490a58..bf0e70d 100644 --- a/templates/selfsigned-issuer.yaml +++ b/templates/selfsigned-issuer.yaml @@ -1,8 +1,8 @@ -{{- if .Values.webhooks.enabled }} +{{- if .Values.selfSignedIssuer.enabled }} apiVersion: cert-manager.io/v1 kind: Issuer metadata: - name: {{ include "git-hubby.fullname" . }}-selfsigned-issuer + name: {{ include "git-hubby.selfSignedIssuerName" . }} labels: {{- include "git-hubby.labels" . | nindent 4 }} spec: diff --git a/templates/serving-cert.yaml b/templates/serving-cert.yaml index 2f947a2..8af21e5 100644 --- a/templates/serving-cert.yaml +++ b/templates/serving-cert.yaml @@ -15,7 +15,14 @@ spec: duration: {{ . }} {{- end }} issuerRef: + {{- if .Values.servingCert.issuerRef }} {{- toYaml .Values.servingCert.issuerRef | nindent 4 }} + {{- else if .Values.selfSignedIssuer.enabled }} + kind: Issuer + name: {{ include "git-hubby.selfSignedIssuerName" . }} + {{- else }} + {{- fail "servingCert.issuerRef must be set when selfSignedIssuer.enabled is false" }} + {{- end }} {{- with .Values.servingCert.privateKey }} privateKey: {{- toYaml . | nindent 4 }} diff --git a/tests/metrics_cert_test.yaml b/tests/metrics_cert_test.yaml new file mode 100644 index 0000000..378f2f3 --- /dev/null +++ b/tests/metrics_cert_test.yaml @@ -0,0 +1,147 @@ +suite: metrics-cert +templates: + - metrics-certs.yaml +release: + name: test-release + namespace: test-ns +tests: + - it: should render a Certificate resource + asserts: + - isKind: + of: Certificate + - equal: + path: apiVersion + value: cert-manager.io/v1 + - equal: + path: metadata.name + value: test-release-git-hubby-metrics-certs + - equal: + path: spec.secretName + value: metrics-server-cert + + - it: should set correct DNS names + asserts: + - contains: + path: spec.dnsNames + content: test-release-git-hubby-controller-manager-metrics-service.test-ns.svc + - contains: + path: spec.dnsNames + content: test-release-git-hubby-controller-manager-metrics-service.test-ns.svc.cluster.local + + - it: should include chart labels + asserts: + - isSubset: + path: metadata.labels + content: + app.kubernetes.io/name: git-hubby + app.kubernetes.io/instance: test-release + + - it: should use the internal self-signed issuer by default + asserts: + - equal: + path: spec.issuerRef.kind + value: Issuer + - equal: + path: spec.issuerRef.name + value: test-release-git-hubby-selfsigned-issuer + + - it: should use a custom issuerRef when configured + set: + metricsCert: + issuerRef: + kind: ClusterIssuer + name: letsencrypt-prod + asserts: + - equal: + path: spec.issuerRef.kind + value: ClusterIssuer + - equal: + path: spec.issuerRef.name + value: letsencrypt-prod + + - it: should use custom issuerRef even when selfSignedIssuer is enabled + set: + selfSignedIssuer.enabled: true + metricsCert: + issuerRef: + kind: ClusterIssuer + name: my-issuer + asserts: + - equal: + path: spec.issuerRef.name + value: my-issuer + + - it: should use custom issuerRef when selfSignedIssuer is disabled + set: + selfSignedIssuer.enabled: false + metricsCert: + issuerRef: + kind: ClusterIssuer + name: letsencrypt-prod + asserts: + - equal: + path: spec.issuerRef.kind + value: ClusterIssuer + - equal: + path: spec.issuerRef.name + value: letsencrypt-prod + + - it: should always render regardless of webhooks.enabled + set: + webhooks.enabled: false + asserts: + - hasDocuments: + count: 1 + + - it: should set optional duration + set: + metricsCert.duration: "8760h0m0s" + asserts: + - equal: + path: spec.duration + value: "8760h0m0s" + + - it: should set optional renewBefore + set: + metricsCert.renewBefore: "720h0m0s" + asserts: + - equal: + path: spec.renewBefore + value: "720h0m0s" + + - it: should set optional privateKey + set: + metricsCert: + privateKey: + algorithm: ECDSA + size: 256 + asserts: + - equal: + path: spec.privateKey.algorithm + value: ECDSA + - equal: + path: spec.privateKey.size + value: 256 + + - it: should set optional subject + set: + metricsCert: + subject: + organizations: + - My Org + asserts: + - equal: + path: spec.subject.organizations[0] + value: My Org + + - it: should not include optional fields when not set + asserts: + - notExists: + path: spec.duration + - notExists: + path: spec.renewBefore + - notExists: + path: spec.privateKey + - notExists: + path: spec.subject + diff --git a/tests/selfsigned_issuer_test.yaml b/tests/selfsigned_issuer_test.yaml index 62fcb8c..fadbcb3 100644 --- a/tests/selfsigned_issuer_test.yaml +++ b/tests/selfsigned_issuer_test.yaml @@ -5,7 +5,7 @@ release: name: test-release namespace: test-ns tests: - - it: should render an Issuer resource + - it: should render an Issuer resource when selfSignedIssuer.enabled is true asserts: - isKind: of: Issuer @@ -19,3 +19,25 @@ tests: path: spec.selfSigned value: {} + - it: should include chart labels + asserts: + - isSubset: + path: metadata.labels + content: + app.kubernetes.io/name: git-hubby + app.kubernetes.io/instance: test-release + + - it: should not render when selfSignedIssuer.enabled is false + set: + selfSignedIssuer.enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: should include release name in issuer name to prevent collisions + release: + name: other-release + asserts: + - equal: + path: metadata.name + value: other-release-git-hubby-selfsigned-issuer diff --git a/tests/serving_cert_test.yaml b/tests/serving_cert_test.yaml index b4dfc9e..ca787d5 100644 --- a/tests/serving_cert_test.yaml +++ b/tests/serving_cert_test.yaml @@ -25,14 +25,62 @@ tests: path: spec.dnsNames content: test-release-git-hubby-webhook-service.test-ns.svc.cluster.local - - it: should use issuerRef from values + - it: should use the internal self-signed issuer by default asserts: - equal: path: spec.issuerRef.kind value: Issuer - equal: path: spec.issuerRef.name - value: selfsigned-issuer + value: test-release-git-hubby-selfsigned-issuer + + - it: should use a custom issuerRef when configured + set: + servingCert: + issuerRef: + kind: ClusterIssuer + name: letsencrypt-prod + asserts: + - equal: + path: spec.issuerRef.kind + value: ClusterIssuer + - equal: + path: spec.issuerRef.name + value: letsencrypt-prod + + - it: should use custom issuerRef even when selfSignedIssuer is enabled + set: + selfSignedIssuer.enabled: true + servingCert: + issuerRef: + kind: ClusterIssuer + name: my-issuer + asserts: + - equal: + path: spec.issuerRef.name + value: my-issuer + + - it: should use custom issuerRef when selfSignedIssuer is disabled + set: + selfSignedIssuer.enabled: false + servingCert: + issuerRef: + kind: ClusterIssuer + name: letsencrypt-prod + asserts: + - equal: + path: spec.issuerRef.kind + value: ClusterIssuer + - equal: + path: spec.issuerRef.name + value: letsencrypt-prod + + - it: should not render when webhooks are disabled + set: + webhooks.enabled: false + asserts: + - hasDocuments: + count: 0 - it: should set optional duration set: @@ -50,6 +98,45 @@ tests: path: spec.renewBefore value: "360h0m0s" + - it: should set optional privateKey + set: + servingCert: + privateKey: + algorithm: ECDSA + size: 256 + asserts: + - equal: + path: spec.privateKey.algorithm + value: ECDSA + - equal: + path: spec.privateKey.size + value: 256 + + - it: should set optional subject + set: + servingCert: + subject: + organizations: + - My Org + asserts: + - equal: + path: spec.subject.organizations[0] + value: My Org + + - it: should set optional usages + set: + servingCert: + usages: + - server auth + - client auth + asserts: + - contains: + path: spec.usages + content: server auth + - contains: + path: spec.usages + content: client auth + - it: should not include optional fields when not set asserts: - notExists: @@ -62,4 +149,3 @@ tests: path: spec.subject - notExists: path: spec.usages - diff --git a/values.schema.json b/values.schema.json index 552a311..e202a61 100644 --- a/values.schema.json +++ b/values.schema.json @@ -168,6 +168,9 @@ "kubernetesClusterDomain": { "type": "string" }, + "metricsCert": { + "type": "object" + }, "metricsService": { "type": "object", "properties": { @@ -196,6 +199,14 @@ } } }, + "selfSignedIssuer": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } + }, "serviceAccount": { "type": "object", "properties": { @@ -220,20 +231,7 @@ } }, "servingCert": { - "type": "object", - "properties": { - "issuerRef": { - "type": "object", - "properties": { - "kind": { - "type": "string" - }, - "name": { - "type": "string" - } - } - } - } + "type": "object" }, "webhookService": { "type": "object", diff --git a/values.yaml b/values.yaml index 364c939..c8635f4 100644 --- a/values.yaml +++ b/values.yaml @@ -66,11 +66,29 @@ serviceAccount: name: "" webhookService: type: ClusterIP -servingCert: +selfSignedIssuer: + # -- When true, a self-signed cert-manager Issuer is created and used by + # metricsCert and servingCert unless those have an explicit issuerRef set. + enabled: true +metricsCert: {} + # duration: 2160h0m0s + # -- When set, overrides the selfSignedIssuer for this certificate. + # issuerRef: + # kind: ClusterIssuer + # name: letsencrypt-prod + # privateKey: + # algorithm: RSA + # size: 4096 + # renewBefore: 360h0m0s + # subject: + # organizations: + # - My Organization +servingCert: {} # duration: 2160h0m0s - issuerRef: - kind: Issuer - name: selfsigned-issuer + # -- When set, overrides the selfSignedIssuer for this certificate. + # issuerRef: + # kind: ClusterIssuer + # name: letsencrypt-prod # privateKey: # algorithm: RSA # size: 4096