package tls import ( "context" "crypto/tls" "encoding/base64" "time" "github.com/pkg/errors" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" "k8s.io/client-go/informers" v1 "k8s.io/client-go/informers/core/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/clientcmd" ) type CertManager struct { cert *tls.Certificate informer v1.SecretInformer Log *zap.Logger secretName string secretNamespace string } func NewCertManager(name, namespace string) (*CertManager, error) { if name == "" || namespace == "" { return nil, errors.New("name and namespace are required") } cm := &CertManager{ secretName: name, secretNamespace: namespace, } kc, err := getKubernetesClient() if err != nil { return nil, errors.Wrap(err, "failed to get kubernetes client") } informerFactory := informers.NewSharedInformerFactory(kc, 10*time.Minute) cm.informer = informerFactory.Core().V1().Secrets() if _, err = cm.informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: cm.addSecretHandler, UpdateFunc: cm.updateSecretHandler, DeleteFunc: cm.deleteSecretHandler, }); err != nil { return nil, errors.Wrap(err, "failed to register event handlers with informer") } return cm, nil } func (cm *CertManager) Get(_ *tls.ClientHelloInfo) (*tls.Certificate, error) { if cm.cert == nil { return nil, errors.New("no certificate") } return cm.cert, nil } func (cm *CertManager) Watch(ctx context.Context, log *zap.Logger) error { cm.Log = log if cm.informer == nil { return errors.New("no informer in CertManager") } cm.informer.Informer().Run(ctx.Done()) return errors.New("watcher exited") } func (cm *CertManager) addSecretHandler(obj any) { secret, ok := obj.(corev1.Secret) if !ok { return } if secret.Namespace != cm.secretNamespace || secret.Name != cm.secretName { return } cert, err := CertFromSecret(&secret) if err != nil { cm.Log.Error("failed to parse secret as TLS certificate", zap.Error(err)) return } cm.cert = &cert } func (cm *CertManager) updateSecretHandler(oldObj, newObj any) { oldCertSecret, ok := oldObj.(corev1.Secret) if !ok { return } if oldCertSecret.Namespace != cm.secretNamespace || oldCertSecret.Name != cm.secretName { return } newCertSecret, ok := newObj.(corev1.Secret) if !ok { return } if newCertSecret.Name != oldCertSecret.Name { cm.secretName = newCertSecret.Name } newCertificate, err := CertFromSecret(&newCertSecret) if err != nil { cm.Log.Error("failed to parse secret as a TLS certificate", zap.Error(err)) } cm.cert = &newCertificate } func (cm *CertManager) deleteSecretHandler(obj any) { secret, ok := obj.(corev1.Secret) if !ok { return } if secret.Namespace != cm.secretNamespace || secret.Name != cm.secretName { return } cm.cert = nil } func getKubernetesClient() (*kubernetes.Clientset, error) { kubeConfig, err := clientcmd.BuildConfigFromFlags("", "") if err != nil { return nil, errors.Wrap(err, "failed to generate Kubernetes config") } return kubernetes.NewForConfig(kubeConfig) } func CertFromSecret(secret *corev1.Secret) (cert tls.Certificate, err error) { var pubCert, key []byte if _, err := base64.StdEncoding.Decode(pubCert, secret.Data["tls.crt"]); err != nil { return cert, errors.Wrap(err, "failed to decode public certificate") } if _, err := base64.StdEncoding.Decode(key, secret.Data["tls.key"]); err != nil { return cert, errors.Wrap(err, "failed to decode private key") } return tls.X509KeyPair(pubCert, key) }