From 64bfd23893d82f231f2b87d5707549c6d98fa52d Mon Sep 17 00:00:00 2001 From: Erik Godding Boye Date: Tue, 5 Nov 2024 07:37:40 +0100 Subject: [PATCH] POC of dynamic authority Signed-off-by: Erik Godding Boye --- cmd/trust-manager/app/app.go | 10 +- cmd/trust-manager/app/options/options.go | 16 ++++ deploy/charts/trust-manager/README.md | 26 ----- .../trust-manager/templates/certificate.yaml | 94 ------------------- .../trust-manager/templates/clusterrole.yaml | 13 +++ .../trust-manager/templates/deployment.yaml | 18 ++-- .../charts/trust-manager/templates/role.yaml | 9 ++ .../trust-manager/templates/webhook.yaml | 10 +- .../charts/trust-manager/values.schema.json | 49 ---------- deploy/charts/trust-manager/values.yaml | 20 ---- go.mod | 1 + go.sum | 2 + make/test-smoke.mk | 25 ----- 13 files changed, 62 insertions(+), 231 deletions(-) delete mode 100644 deploy/charts/trust-manager/templates/certificate.yaml diff --git a/cmd/trust-manager/app/app.go b/cmd/trust-manager/app/app.go index 74c6b959..7b375923 100644 --- a/cmd/trust-manager/app/app.go +++ b/cmd/trust-manager/app/app.go @@ -17,10 +17,12 @@ limitations under the License. package app import ( + "crypto/tls" "errors" "fmt" "net/http" + "github.com/erikgb/dynamic-authority/pkg/authority" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" @@ -70,6 +72,8 @@ func NewCommand() *cobra.Command { ctrl.SetLogger(mlog) + certOperator := authority.ServingCertificateOperator{Options: opts.DynamicServing} + eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(func(format string, args ...any) { mlog.V(3).Info(fmt.Sprintf(format, args...)) }) eventBroadcaster.StartRecordingToSink(&clientv1.EventSinkImpl{Interface: cl.CoreV1().Events("")}) @@ -87,7 +91,7 @@ func NewCommand() *cobra.Command { WebhookServer: ctrlwebhook.NewServer(ctrlwebhook.Options{ Port: opts.Webhook.Port, Host: opts.Webhook.Host, - CertDir: opts.Webhook.CertDir, + TLSOpts: []func(*tls.Config){certOperator.ServingCertificate()}, }), Metrics: server.Options{ BindAddress: fmt.Sprintf("0.0.0.0:%d", opts.MetricsPort), @@ -119,6 +123,10 @@ func NewCommand() *cobra.Command { return fmt.Errorf("failed to create manager: %w", err) } + if err := certOperator.SetupWithManager(mgr); err != nil { + return fmt.Errorf("failed to setup cert operator: %w", err) + } + targetCache, err := cache.New(mgr.GetConfig(), cache.Options{ HTTPClient: mgr.GetHTTPClient(), Scheme: mgr.GetScheme(), diff --git a/cmd/trust-manager/app/options/options.go b/cmd/trust-manager/app/options/options.go index baf6ea4b..595d3900 100644 --- a/cmd/trust-manager/app/options/options.go +++ b/cmd/trust-manager/app/options/options.go @@ -23,6 +23,7 @@ import ( "os" "time" + "github.com/erikgb/dynamic-authority/pkg/authority" "github.com/go-logr/logr" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -70,6 +71,8 @@ type Options struct { // Leader election lease renew duration RenewDeadline time.Duration + + DynamicServing authority.Options } type logOptions struct { @@ -164,6 +167,7 @@ func (o *Options) addFlags(cmd *cobra.Command) { o.addBundleFlags(nfs.FlagSet("Bundle")) o.addLoggingFlags(nfs.FlagSet("Logging")) o.addWebhookFlags(nfs.FlagSet("Webhook")) + o.addDynamicServingFlags(nfs.FlagSet("Dynamic Serving")) o.kubeConfigFlags = genericclioptions.NewConfigFlags(true) o.kubeConfigFlags.AddFlags(nfs.FlagSet("Kubernetes")) @@ -248,3 +252,15 @@ func (o *Options) addWebhookFlags(fs *pflag.FlagSet) { "Certificate and private key must be named 'tls.crt' and 'tls.key' "+ "respectively.") } + +func (o *Options) addDynamicServingFlags(fs *pflag.FlagSet) { + fs.StringVar(&o.DynamicServing.Namespace, + "dynamic-serving-ca-secret-namespace", "", + "Namespace of the secret used to store the CA that signs serving certificates") + fs.StringVar(&o.DynamicServing.CASecret, + "dynamic-serving-ca-secret-name", "", + "Name of the secret used to store the CA that signs serving certificates") + fs.StringSliceVar(&o.DynamicServing.DNSNames, + "dynamic-serving-dns-names", nil, + "DNS names that should be present on certificates generated by the dynamic serving CA") +} diff --git a/deploy/charts/trust-manager/README.md b/deploy/charts/trust-manager/README.md index af2080d0..5f6db512 100644 --- a/deploy/charts/trust-manager/README.md +++ b/deploy/charts/trust-manager/README.md @@ -421,32 +421,6 @@ The nodePort set on the Service used by the webhook. > ``` Whether to issue a webhook cert using Helm, which removes the need to install cert-manager. Helm-issued certificates can be challenging to rotate and maintain, and the issued cert will have a duration of 10 years and be modified when trust-manager is updated. It's safer and easier to rely on cert-manager for issuing the webhook cert - avoid using Helm-generated certs in production. -#### **app.webhook.tls.approverPolicy.enabled** ~ `bool` -> Default value: -> ```yaml -> false -> ``` - -Whether to create an approver-policy CertificateRequestPolicy allowing auto-approval of the trust-manager webhook certificate. If you have approver-policy installed, you almost certainly want to enable this. -#### **app.webhook.tls.approverPolicy.certManagerNamespace** ~ `string` -> Default value: -> ```yaml -> cert-manager -> ``` - -The namespace in which cert-manager was installed. Only used if `app.webhook.tls.approverPolicy.enabled` is true. -#### **app.webhook.tls.approverPolicy.certManagerServiceAccount** ~ `string` -> Default value: -> ```yaml -> cert-manager -> ``` - -The name of cert-manager's Service Account. Only used if `app.webhook.tls.approverPolicy.enabled` is true. -#### **app.webhook.tls.certificate.secretTemplate** ~ `object` -> Default value: -> ```yaml -> {} -> ``` #### **app.webhook.hostNetwork** ~ `bool` > Default value: > ```yaml diff --git a/deploy/charts/trust-manager/templates/certificate.yaml b/deploy/charts/trust-manager/templates/certificate.yaml deleted file mode 100644 index d038934a..00000000 --- a/deploy/charts/trust-manager/templates/certificate.yaml +++ /dev/null @@ -1,94 +0,0 @@ -{{- if not .Values.app.webhook.tls.helmCert.enabled -}} - -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - name: {{ include "trust-manager.name" . }} - namespace: {{ include "trust-manager.namespace" . }} - labels: - {{- include "trust-manager.labels" . | nindent 4 }} -spec: - selfSigned: {} - ---- - -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: {{ include "trust-manager.name" . }} - namespace: {{ include "trust-manager.namespace" . }} - labels: - {{- include "trust-manager.labels" . | nindent 4 }} -spec: - commonName: "{{ include "trust-manager.name" . }}.{{ include "trust-manager.namespace" . }}.svc" - dnsNames: - - "{{ include "trust-manager.name" . }}.{{ include "trust-manager.namespace" . }}.svc" - secretName: {{ include "trust-manager.name" . }}-tls - {{- with .Values.app.webhook.tls.certificate.secretTemplate }} - secretTemplate: - {{- toYaml .| nindent 4 }} - {{- end }} - revisionHistoryLimit: 1 - issuerRef: - name: {{ include "trust-manager.name" . }} - kind: Issuer - group: cert-manager.io - ---- - -{{- if .Values.app.webhook.tls.approverPolicy.enabled -}} - -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: trust-manager-policy - labels: - {{- include "trust-manager.labels" . | nindent 4 }} -spec: - allowed: - commonName: - value: "{{ include "trust-manager.name" . }}.{{ include "trust-manager.namespace" . }}.svc" - required: true - dnsNames: - values: ["{{ include "trust-manager.name" . }}.{{ include "trust-manager.namespace" . }}.svc"] - required: true - selector: - issuerRef: - name: {{ include "trust-manager.name" . }} - kind: Issuer - group: cert-manager.io - ---- - -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: trust-manager-policy-role - labels: - {{- include "trust-manager.labels" . | nindent 4 }} -rules: - - apiGroups: ["policy.cert-manager.io"] - resources: ["certificaterequestpolicies"] - verbs: ["use"] - resourceNames: ["trust-manager-policy"] - ---- - -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: trust-manager-policy-binding - labels: - {{- include "trust-manager.labels" . | nindent 4 }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: trust-manager-policy-role -subjects: -- kind: ServiceAccount - name: {{ .Values.app.webhook.tls.approverPolicy.certManagerServiceAccount }} - namespace: {{ .Values.app.webhook.tls.approverPolicy.certManagerNamespace }} - -{{ end }} - -{{ end }} diff --git a/deploy/charts/trust-manager/templates/clusterrole.yaml b/deploy/charts/trust-manager/templates/clusterrole.yaml index d13bc5fe..9ec54ea3 100644 --- a/deploy/charts/trust-manager/templates/clusterrole.yaml +++ b/deploy/charts/trust-manager/templates/clusterrole.yaml @@ -5,6 +5,19 @@ metadata: {{- include "trust-manager.labels" . | nindent 4 }} name: {{ include "trust-manager.name" . }} rules: +- apiGroups: + - "admissionregistration.k8s.io" + resources: + - "validatingwebhookconfigurations" + verbs: ["get", "list", "watch"] +- apiGroups: + - "admissionregistration.k8s.io" + resources: + - "validatingwebhookconfigurations" + verbs: ["patch"] + resourceNames: + - {{ include "trust-manager.name" . }} + - apiGroups: - "trust.cert-manager.io" resources: diff --git a/deploy/charts/trust-manager/templates/deployment.yaml b/deploy/charts/trust-manager/templates/deployment.yaml index 27e742b1..9f7b5937 100644 --- a/deploy/charts/trust-manager/templates/deployment.yaml +++ b/deploy/charts/trust-manager/templates/deployment.yaml @@ -87,7 +87,11 @@ spec: # webhook - "--webhook-host={{.Values.app.webhook.host}}" - "--webhook-port={{.Values.app.webhook.port}}" - - "--webhook-certificate-dir=/tls" + - "--dynamic-serving-ca-secret-namespace={{ include "trust-manager.namespace" . }}" + - "--dynamic-serving-ca-secret-name={{ include "trust-manager.name" . }}-dynamic-ca" + - "--dynamic-serving-dns-names={{ include "trust-manager.name" . }}" + - "--dynamic-serving-dns-names={{ include "trust-manager.name" . }}.$(POD_NAMESPACE)" + - "--dynamic-serving-dns-names={{ include "trust-manager.name" . }}.$(POD_NAMESPACE).svc" {{- if .Values.defaultPackage.enabled }} - "--default-package-location=/packages/cert-manager-package-debian.json" {{- end }} @@ -97,10 +101,12 @@ spec: {{- if .Values.filterExpiredCertificates.enabled }} - "--filter-expired-certificates=true" {{- end }} + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace volumeMounts: - - mountPath: /tls - name: tls - readOnly: true - mountPath: /packages name: packages readOnly: true @@ -140,10 +146,6 @@ spec: - name: packages emptyDir: sizeLimit: 50M - - name: tls - secret: - defaultMode: 420 - secretName: {{ include "trust-manager.name" . }}-tls {{- if .Values.app.webhook.hostNetwork }} hostNetwork: true dnsPolicy: ClusterFirstWithHostNet diff --git a/deploy/charts/trust-manager/templates/role.yaml b/deploy/charts/trust-manager/templates/role.yaml index 2ba9eb6b..9db16d2e 100644 --- a/deploy/charts/trust-manager/templates/role.yaml +++ b/deploy/charts/trust-manager/templates/role.yaml @@ -14,6 +14,15 @@ rules: - "get" - "list" - "watch" +- apiGroups: + - "" + resources: + - "secrets" + verbs: + - "create" + - "patch" + resourceNames: + - {{ include "trust-manager.name" . }}-dynamic-ca --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 diff --git a/deploy/charts/trust-manager/templates/webhook.yaml b/deploy/charts/trust-manager/templates/webhook.yaml index 235857d2..66c54ada 100644 --- a/deploy/charts/trust-manager/templates/webhook.yaml +++ b/deploy/charts/trust-manager/templates/webhook.yaml @@ -1,7 +1,3 @@ -{{- if and .Values.app.webhook.tls.helmCert.enabled .Values.app.webhook.tls.approverPolicy.enabled -}} -{{- fail "cannot set .app.webhook.tls.helmCert.enabled and .Values.app.webhook.tls.approverPolicy.enabled" }} -{{- end -}} - {{- /* $ca is always generated here even if .Values.app.webhook.tls.helmCert.enabled is false because we need it to be visible in the scope for the ValidatingWebhookConfiguration below. @@ -83,10 +79,8 @@ metadata: labels: app: {{ include "trust-manager.name" . }} {{- include "trust-manager.labels" . | nindent 4 }} -{{ if not .Values.app.webhook.tls.helmCert.enabled }} - annotations: - cert-manager.io/inject-ca-from: "{{ include "trust-manager.namespace" . }}/{{ include "trust-manager.name" . }}" -{{ end }} + cert-manager.io/inject-dynamic-ca-from-secret-namespace: "{{ include "trust-manager.namespace" . }}" + cert-manager.io/inject-dynamic-ca-from-secret-name: "{{ include "trust-manager.name" . }}-dynamic-ca" webhooks: - name: trust.cert-manager.io diff --git a/deploy/charts/trust-manager/values.schema.json b/deploy/charts/trust-manager/values.schema.json index e362c2ae..0bbe36b9 100644 --- a/deploy/charts/trust-manager/values.schema.json +++ b/deploy/charts/trust-manager/values.schema.json @@ -389,61 +389,12 @@ "helm-values.app.webhook.tls": { "additionalProperties": false, "properties": { - "approverPolicy": { - "$ref": "#/$defs/helm-values.app.webhook.tls.approverPolicy" - }, - "certificate": { - "$ref": "#/$defs/helm-values.app.webhook.tls.certificate" - }, "helmCert": { "$ref": "#/$defs/helm-values.app.webhook.tls.helmCert" } }, "type": "object" }, - "helm-values.app.webhook.tls.approverPolicy": { - "additionalProperties": false, - "properties": { - "certManagerNamespace": { - "$ref": "#/$defs/helm-values.app.webhook.tls.approverPolicy.certManagerNamespace" - }, - "certManagerServiceAccount": { - "$ref": "#/$defs/helm-values.app.webhook.tls.approverPolicy.certManagerServiceAccount" - }, - "enabled": { - "$ref": "#/$defs/helm-values.app.webhook.tls.approverPolicy.enabled" - } - }, - "type": "object" - }, - "helm-values.app.webhook.tls.approverPolicy.certManagerNamespace": { - "default": "cert-manager", - "description": "The namespace in which cert-manager was installed. Only used if `app.webhook.tls.approverPolicy.enabled` is true.", - "type": "string" - }, - "helm-values.app.webhook.tls.approverPolicy.certManagerServiceAccount": { - "default": "cert-manager", - "description": "The name of cert-manager's Service Account. Only used if `app.webhook.tls.approverPolicy.enabled` is true.", - "type": "string" - }, - "helm-values.app.webhook.tls.approverPolicy.enabled": { - "default": false, - "description": "Whether to create an approver-policy CertificateRequestPolicy allowing auto-approval of the trust-manager webhook certificate. If you have approver-policy installed, you almost certainly want to enable this.", - "type": "boolean" - }, - "helm-values.app.webhook.tls.certificate": { - "additionalProperties": false, - "properties": { - "secretTemplate": { - "$ref": "#/$defs/helm-values.app.webhook.tls.certificate.secretTemplate" - } - }, - "type": "object" - }, - "helm-values.app.webhook.tls.certificate.secretTemplate": { - "default": {}, - "type": "object" - }, "helm-values.app.webhook.tls.helmCert": { "additionalProperties": false, "properties": { diff --git a/deploy/charts/trust-manager/values.yaml b/deploy/charts/trust-manager/values.yaml index 92c9fca1..7e9c6d13 100644 --- a/deploy/charts/trust-manager/values.yaml +++ b/deploy/charts/trust-manager/values.yaml @@ -242,26 +242,6 @@ app: # It's safer and easier to rely on cert-manager for issuing the webhook cert - avoid using Helm-generated certs in production. enabled: false - approverPolicy: - # Whether to create an approver-policy CertificateRequestPolicy allowing auto-approval of the trust-manager webhook certificate. If you have approver-policy installed, you almost certainly want to enable this. - enabled: false - - # The namespace in which cert-manager was installed. Only used if `app.webhook.tls.approverPolicy.enabled` is true. - certManagerNamespace: "cert-manager" - - # The name of cert-manager's Service Account. Only used if `app.webhook.tls.approverPolicy.enabled` is true. - certManagerServiceAccount: "cert-manager" - - # Add labels/annotations to secrets created by Certificate resources when using cert-manager provisioned TLS certificate. - certificate: - secretTemplate: {} - # For example: - # annotations: - # my-secret-annotation-1: "foo" - # my-secret-annotation-2: "bar" - # labels: - # my-secret-label: foo - # This value specifies if the app should be started in hostNetwork mode. It is required for use in some managed Kubernetes clusters (such as AWS EKS) with custom CNI. hostNetwork: false diff --git a/go.mod b/go.mod index 0a8d2aa8..2158028a 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/cert-manager/trust-manager go 1.22.0 require ( + github.com/erikgb/dynamic-authority v0.0.0-20241105063705-d20d55e85d05 github.com/go-logr/logr v1.4.2 github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.35.1 diff --git a/go.sum b/go.sum index 15d6bcc8..a84008f6 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,8 @@ github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtz github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikgb/dynamic-authority v0.0.0-20241105063705-d20d55e85d05 h1:KZR8PfgX2SkZh7NxL8UGbe5VT6hP2jwrfAgUpIlNa68= +github.com/erikgb/dynamic-authority v0.0.0-20241105063705-d20d55e85d05/go.mod h1:rsJ6ZXqGxojBpTDo9YEaI2lJDpesjH05kuMlR9Qdxzk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= diff --git a/make/test-smoke.mk b/make/test-smoke.mk index feb295af..969b3436 100644 --- a/make/test-smoke.mk +++ b/make/test-smoke.mk @@ -12,30 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -.PHONY: smoke-setup-cert-manager -smoke-setup-cert-manager: | kind-cluster $(NEEDS_HELM) $(NEEDS_KUBECTL) - $(HELM) upgrade \ - --install \ - --create-namespace \ - --wait \ - --version $(quay.io/jetstack/cert-manager-controller.TAG) \ - --namespace cert-manager \ - --repo https://charts.jetstack.io \ - --set installCRDs=true \ - --set image.repository=$(quay.io/jetstack/cert-manager-controller.REPO) \ - --set image.tag=$(quay.io/jetstack/cert-manager-controller.TAG) \ - --set image.pullPolicy=Never \ - --set cainjector.image.repository=$(quay.io/jetstack/cert-manager-cainjector.REPO) \ - --set cainjector.image.tag=$(quay.io/jetstack/cert-manager-cainjector.TAG) \ - --set cainjector.image.pullPolicy=Never \ - --set webhook.image.repository=$(quay.io/jetstack/cert-manager-webhook.REPO) \ - --set webhook.image.tag=$(quay.io/jetstack/cert-manager-webhook.TAG) \ - --set webhook.image.pullPolicy=Never \ - --set startupapicheck.image.repository=$(quay.io/jetstack/cert-manager-startupapicheck.REPO) \ - --set startupapicheck.image.tag=$(quay.io/jetstack/cert-manager-startupapicheck.TAG) \ - --set startupapicheck.image.pullPolicy=Never \ - cert-manager cert-manager >/dev/null - # The "install" target can be run on its own with any currently active cluster, # we can't use any other cluster then a target containing "test-smoke" is run. # When a "test-smoke" target is run, the currently active cluster must be the kind @@ -48,7 +24,6 @@ test-smoke-deps: INSTALL_OPTIONS := test-smoke-deps: INSTALL_OPTIONS += --set image.repository=$(oci_manager_image_name_development) test-smoke-deps: INSTALL_OPTIONS += --set defaultPackageImage.repository=$(oci_package_debian_image_name_development) test-smoke-deps: INSTALL_OPTIONS += --set secretTargets.enabled=true --set secretTargets.authorizedSecretsAll=true -test-smoke-deps: smoke-setup-cert-manager test-smoke-deps: install .PHONY: test-smoke