From 9e8c40892122be021743ed75a10cf87e4feef302 Mon Sep 17 00:00:00 2001 From: Yi Chen Date: Tue, 24 Dec 2024 17:20:53 +0800 Subject: [PATCH] Add support for using cert manager to generate webhook certificates Signed-off-by: Yi Chen --- charts/spark-operator-chart/README.md | 4 ++ .../templates/webhook/_helpers.tpl | 6 +++ .../templates/webhook/certificate.yaml | 48 +++++++++++++++++++ charts/spark-operator-chart/values.yaml | 12 +++++ cmd/operator/webhook/start.go | 1 - pkg/certificate/certificate.go | 20 ++++++++ 6 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 charts/spark-operator-chart/templates/webhook/certificate.yaml diff --git a/charts/spark-operator-chart/README.md b/charts/spark-operator-chart/README.md index 8cdc5d16b1..14a3b35cbb 100644 --- a/charts/spark-operator-chart/README.md +++ b/charts/spark-operator-chart/README.md @@ -132,6 +132,10 @@ See [helm uninstall](https://helm.sh/docs/helm/helm_uninstall) for command docum | webhook.portName | string | `"webhook"` | Specifies webhook service port name. | | webhook.failurePolicy | string | `"Fail"` | Specifies how unrecognized errors are handled. Available options are `Ignore` or `Fail`. | | webhook.timeoutSeconds | int | `10` | Specifies the timeout seconds of the webhook, the value must be between 1 and 30. | +| webhook.certManager.enable | bool | `false` | Specifies whether to use cert-manager to generate certificates for webhook. | +| webhook.certManager.issuerRef | object | `{}` | The reference to the issuer. | +| webhook.certManager.duration | string | `""` | The duration of the certificate validity. | +| webhook.certManager.renewBefore | string | `""` | The duration before the certificate expiration to renew the certificate. | | webhook.resourceQuotaEnforcement.enable | bool | `false` | Specifies whether to enable the ResourceQuota enforcement for SparkApplication resources. | | webhook.serviceAccount.create | bool | `true` | Specifies whether to create a service account for the webhook. | | webhook.serviceAccount.name | string | `""` | Optional name for the webhook service account. | diff --git a/charts/spark-operator-chart/templates/webhook/_helpers.tpl b/charts/spark-operator-chart/templates/webhook/_helpers.tpl index 00ad4fccad..363e773863 100644 --- a/charts/spark-operator-chart/templates/webhook/_helpers.tpl +++ b/charts/spark-operator-chart/templates/webhook/_helpers.tpl @@ -83,6 +83,12 @@ Create the name of the secret to be used by webhook {{ include "spark-operator.webhook.name" . }}-certs {{- end -}} +{{/* +Create the name of the certificate to be used by webhook +*/}} +{{- define "spark-operator.webhook.certificateName" -}} +{{ include "spark-operator.webhook.name" . }}-certificate +{{- end -}} {{/* Create the name of the service to be used by webhook diff --git a/charts/spark-operator-chart/templates/webhook/certificate.yaml b/charts/spark-operator-chart/templates/webhook/certificate.yaml new file mode 100644 index 0000000000..cd72107ebb --- /dev/null +++ b/charts/spark-operator-chart/templates/webhook/certificate.yaml @@ -0,0 +1,48 @@ +{{/* +Copyright 2024 The Kubeflow authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/}} + +{{- if and .Values.webhook.enable .Values.webhook.certManager.enable }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "spark-operator.webhook.certificateName" . }} + labels: + {{- include "spark-operator.labels" . | nindent 4 }} +spec: + secretName: {{ include "spark-operator.webhook.secretName" . }} + {{- with .Values.webhook.certManager.issuerRef }} + issuerRef: + {{- toYaml . | nindent 4 }} + {{- else }} + {{ fail "webhook.certManager.issuerRef is required when certManager is enabled"}} + {{- end }} + commonName: {{ include "spark-operator.webhook.serviceName" . }}.{{ .Release.Namespace }}.svc + dnsNames: + - {{ include "spark-operator.webhook.serviceName" . }}.svc + - {{ include "spark-operator.webhook.serviceName" . }}.svc.cluster.local + subject: + organizationalUnits: + - spark-operator + usages: + - server auth + - client auth + {{- with .Values.webhook.certManager.duration }} + duration: {{ . }} + {{- end }} + {{- with .Values.webhook.certManager.renewBefore }} + renewBefore: {{ . }} + {{- end }} +{{- end }} diff --git a/charts/spark-operator-chart/values.yaml b/charts/spark-operator-chart/values.yaml index b376b4968a..ab2b65e278 100644 --- a/charts/spark-operator-chart/values.yaml +++ b/charts/spark-operator-chart/values.yaml @@ -234,6 +234,18 @@ webhook: # -- Specifies the timeout seconds of the webhook, the value must be between 1 and 30. timeoutSeconds: 10 + certManager: + # -- Specifies whether to use cert-manager to generate certificates for webhook. + enable: false + # -- The reference to the issuer. + issuerRef: {} + # name: selfsigned + # kind: ClusterIssuer + # -- The duration of the certificate validity. + duration: "" + # -- The duration before the certificate expiration to renew the certificate. + renewBefore: "" + resourceQuotaEnforcement: # -- Specifies whether to enable the ResourceQuota enforcement for SparkApplication resources. enable: false diff --git a/cmd/operator/webhook/start.go b/cmd/operator/webhook/start.go index 52586edee6..4277c83eaf 100644 --- a/cmd/operator/webhook/start.go +++ b/cmd/operator/webhook/start.go @@ -242,7 +242,6 @@ func start() { Jitter: 0.1, }, func() (bool, error) { - logger.Info("Syncing webhook secret", "name", webhookSecretName, "namespace", webhookSecretNamespace) if err := certProvider.SyncSecret(context.TODO(), webhookSecretName, webhookSecretNamespace); err != nil { if errors.IsAlreadyExists(err) || errors.IsConflict(err) { return false, nil diff --git a/pkg/certificate/certificate.go b/pkg/certificate/certificate.go index 322faeb1ee..22633183ad 100644 --- a/pkg/certificate/certificate.go +++ b/pkg/certificate/certificate.go @@ -31,6 +31,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/cert" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/kubeflow/spark-operator/pkg/common" @@ -63,6 +64,9 @@ func NewProvider(client client.Client, name, namespace string) *Provider { // SyncSecret syncs the secret containing the certificates to the given name and namespace. func (cp *Provider) SyncSecret(ctx context.Context, name, namespace string) error { + logger := ctrl.LoggerFrom(ctx, "name", name, "namespace", namespace) + logger.Info("Syncing webhook secret") + secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -73,10 +77,13 @@ func (cp *Provider) SyncSecret(ctx context.Context, name, namespace string) erro Name: name, Namespace: namespace, } + if err := cp.client.Get(ctx, key, secret); err != nil { if !errors.IsNotFound(err) { return err } + + logger.Info("Creating secret as it does not exist") if err := cp.client.Create(ctx, secret); err != nil { if errors.IsAlreadyExists(err) { return err @@ -194,6 +201,9 @@ func (cp *Provider) WriteFile(path, certName, keyName string) error { } func (cp *Provider) Generate() error { + logger := ctrl.Log.WithName("") + logger.Info("Generating certificates.") + // Generate CA private caKey caKey, err := NewPrivateKey() if err != nil { @@ -277,22 +287,32 @@ func (cp *Provider) parseSecret(secret *corev1.Secret) error { } func (cp *Provider) updateSecret(ctx context.Context, secret *corev1.Secret) error { + if secret == nil { + return fmt.Errorf("secret is nil") + } + caKey, err := cp.CAKey() if err != nil { return fmt.Errorf("failed to get CA key: %v", err) } + caCert, err := cp.CACert() if err != nil { return fmt.Errorf("failed to get CA certificate: %v", err) } + serverKey, err := cp.ServerKey() if err != nil { return fmt.Errorf("failed to get server key: %v", err) } + serverCert, err := cp.ServerCert() if err != nil { return fmt.Errorf("failed to get server certificate: %v", err) } + + logger := ctrl.LoggerFrom(ctx, "name", secret.Name, "namespace", secret.Namespace) + logger.Info("Updating secret") if secret.Data == nil { secret.Data = make(map[string][]byte) }