165 lines
3.5 KiB
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)
|
|
}
|