Skip to content

Commit

Permalink
Added support for reusing the webhook TLS certificate across differen…
Browse files Browse the repository at this point in the history
…t deployments to prevent cases where operator takes too long to start up (#560)
  • Loading branch information
orishoshan authored Feb 13, 2025
1 parent 87de549 commit 3b82951
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 8 deletions.
11 changes: 11 additions & 0 deletions src/operator/config/rbac/manifests-patched.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ rules:
- patch
- update
- watch
- apiGroups:
- ""
resourceNames:
- intents-operator-webhook-cert
resources:
- secrets
verbs:
- get
- list
- update
- watch
- apiGroups:
- ""
resources:
Expand Down
11 changes: 11 additions & 0 deletions src/operator/config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ rules:
- patch
- update
- watch
- apiGroups:
- ""
resourceNames:
- intents-operator-webhook-cert
resources:
- secrets
verbs:
- get
- list
- update
- watch
- apiGroups:
- ""
resources:
Expand Down
27 changes: 24 additions & 3 deletions src/operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,11 +363,29 @@ func main() {

if selfSignedCert {
logrus.Infoln("Creating self signing certs")
certBundle, err :=
webhooks.GenerateSelfSignedCertificate("intents-operator-webhook-service", podNamespace)
certBundle, ok, err := webhooks.ReadCertBundleFromSecret(signalHandlerCtx, directClient, podNamespace)
if err != nil {
logrus.WithError(err).Panic("unable to create self signed certs for webhook")
logrus.WithError(err).Warn("unable to read existing certs from secret, generating new ones")
}

if !ok {
logrus.Info("webhook certs uninitialized, generating new certs")
}

if !ok || err != nil {
certBundleNew, err :=
webhooks.GenerateSelfSignedCertificate("intents-operator-webhook-service", podNamespace)
if err != nil {
logrus.WithError(err).Panic("unable to create self signed certs for webhook")
}

err = webhooks.PersistCertBundleToSecret(signalHandlerCtx, directClient, podNamespace, certBundleNew)
if err != nil {
logrus.WithError(err).Panic("unable to persist certs to secret")
}
certBundle = certBundleNew
}

err = webhooks.WriteCertToFiles(certBundle)
if err != nil {
logrus.WithError(err).Panic("failed writing certs to file system")
Expand Down Expand Up @@ -546,6 +564,9 @@ func main() {
if err := mgr.AddHealthzCheck("cache", cacheHealthChecker); err != nil {
logrus.WithError(err).Panic("unable to set up cache health check")
}
if err := mgr.AddReadyzCheck("cache", cacheHealthChecker); err != nil {
logrus.WithError(err).Panic("unable to set up ready check")
}

if err := mgr.AddHealthzCheck("intentsReconcile", health.Checker); err != nil {
logrus.WithError(err).Panic("unable to set up health check")
Expand Down
72 changes: 68 additions & 4 deletions src/operator/webhooks/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import (
"encoding/pem"
"fmt"
"github.com/otterize/intents-operator/src/shared"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/util/keyutil"
"math/big"
"net/http"
"net/url"
"os"
"path/filepath"
"sigs.k8s.io/controller-runtime/pkg/client"
"strings"
"time"

Expand All @@ -30,12 +33,15 @@ import (
)

const (
Year = 365 * 24 * time.Hour
CertDirPath = "/tmp/k8s-webhook-server/serving-certs"
CertFilename = "tls.crt"
PrivateKeyFilename = "tls.key"
Year = 365 * 24 * time.Hour
CertDirPath = "/tmp/k8s-webhook-server/serving-certs"
CertFilename = "tls.crt"
PrivateKeyFilename = "tls.key"
WebhookCertSecretName = "intents-operator-webhook-cert"
)

// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;update,resourceNames={intents-operator-webhook-cert}

type CertificateBundle struct {
CertPem []byte
PrivateKeyPem []byte
Expand Down Expand Up @@ -127,6 +133,64 @@ func GenerateSelfSignedCertificate(hostname string, namespace string) (Certifica
}, nil
}

func ReadCertBundleFromSecret(ctx context.Context, client client.Client, operatorNamespace string) (CertificateBundle, bool, error) {
var secret v1.Secret
err := client.Get(ctx, types.NamespacedName{Name: "intents-operator-webhook-cert", Namespace: operatorNamespace}, &secret)
if err != nil {
return CertificateBundle{}, false, errors.Wrap(err)
}

if secret.Data[CertFilename] == nil ||
secret.Data[PrivateKeyFilename] == nil ||
bytes.Equal(secret.Data[CertFilename], []byte("placeholder")) ||
bytes.Equal(secret.Data[PrivateKeyFilename], []byte("placeholder")) {
return CertificateBundle{}, false, nil
}

block, restData := pem.Decode(secret.Data[CertFilename])
if len(restData) != 0 {
return CertificateBundle{}, false, errors.New("failed to decode certificate")
}

cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return CertificateBundle{}, false, errors.Wrap(err)
}

// Check if the certificate is expired
if time.Now().After(cert.NotAfter) {
return CertificateBundle{}, false, errors.New("certificate is expired")
}

_, err = keyutil.ParsePrivateKeyPEM(secret.Data[PrivateKeyFilename])
if err != nil {
return CertificateBundle{}, false, errors.Wrap(err)
}

return CertificateBundle{
CertPem: secret.Data[CertFilename],
PrivateKeyPem: secret.Data[PrivateKeyFilename],
}, true, nil
}

func PersistCertBundleToSecret(ctx context.Context, client client.Client, operatorNamespace string, bundle CertificateBundle) error {
var secret v1.Secret
err := client.Get(ctx, types.NamespacedName{Name: WebhookCertSecretName, Namespace: operatorNamespace}, &secret)
if err != nil {
// secret must exist as it is created as part of Helm chart
return errors.Wrap(err)
}

secret.Data[CertFilename] = bundle.CertPem
secret.Data[PrivateKeyFilename] = bundle.PrivateKeyPem

err = client.Update(ctx, &secret)
if err != nil {
return errors.Wrap(err)
}
return nil
}

func WriteCertToFiles(bundle CertificateBundle) error {
err := os.MkdirAll(CertDirPath, 0750)
if err != nil {
Expand Down

0 comments on commit 3b82951

Please sign in to comment.