mail/pkg/tls/tls.go

165 lines
3.5 KiB
Go

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)
}