diff --git a/internal/certificate/certificate_utils.go b/internal/certificate/certificate_utils.go new file mode 100644 index 0000000..204216d --- /dev/null +++ b/internal/certificate/certificate_utils.go @@ -0,0 +1,152 @@ +package certificate + +import ( + "context" + "errors" + "fmt" + "strings" + + certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + ecv1alpha1 "go.etcd.io/etcd-operator/api/v1alpha1" +) + +type NewCertificateManager struct { + Ctx context.Context + Client client.Client + Scheme *runtime.Scheme + EtcdCluster *ecv1alpha1.EtcdCluster + CertProvider +} + +type CertProvider interface { + CertManager +} + +type CertManager interface { + GetCMCertificate() + CreateCMCertificate() +} + +func (c *NewCertificateManager) GetCMCertificate(tlsCertName, namespace string) (*certv1.Certificate, error) { + foundCert := &certv1.Certificate{} + + err := c.Client.Get(c.Ctx, client.ObjectKey{Name: tlsCertName, Namespace: namespace}, foundCert) + if err != nil { + return nil, err + } + return foundCert, nil +} + +func (c *NewCertificateManager) CreateCMCertificate(tlsCertName string) (*certv1.Certificate, error) { + certificateResource := &certv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: tlsCertName, + Namespace: c.EtcdCluster.Namespace, + }, + Spec: certv1.CertificateSpec{ + SecretName: tlsCertName, + DNSNames: []string{fmt.Sprintf("%s-%d.%s.%s.svc.cluster.local", c.EtcdCluster.Name, c.EtcdCluster.Spec.Size, c.EtcdCluster.Name, c.EtcdCluster.Namespace)}, + IssuerRef: cmmeta.ObjectReference{ + Name: CMClusterIssuerName, + Kind: "ClusterIssuer", + }, + }, + } + + err := c.Client.Create(c.Ctx, certificateResource) + if err != nil { + return nil, err + } + return certificateResource, nil +} + +func setCertificateFuncs(c NewCertificateManager) (func(string, string) (*certv1.Certificate, error), func(string) (*certv1.Certificate, error), error) { + certProvider := c.EtcdCluster.Spec.TLS.Provider + + switch certProvider { + case "cert-manager": + return c.GetCMCertificate, c.CreateCMCertificate, nil + default: + return nil, nil, errors.New("invalid certificate provider") + + } +} + +func ReconcileMemberCertificate(c NewCertificateManager) ([]interface{}, error) { + var certificates []interface{} + logger := log.FromContext(c.Ctx) + + getCertFunc, createCertFunc, err := setCertificateFuncs(c) + if err != nil { + return nil, err + } + + for members := 1; members <= c.EtcdCluster.Spec.Size; members++ { + + clientCertName := strings.Join([]string{c.EtcdCluster.Name, c.EtcdCluster.Spec.TLS.OperatorSecret, fmt.Sprintf("%d", members)}, "-") + logger.Info("Starting reconciliation of Client Certificate", clientCertName, c.EtcdCluster.Namespace) + clientCert, clientCertErr := getCertFunc(clientCertName, c.EtcdCluster.Namespace) + if k8serrors.IsNotFound(clientCertErr) { + clientCert, clientCertErr = createCertFunc(clientCertName) + if clientCertErr != nil { + logger.Error(clientCertErr, "failed to create Client Certificate") + } + } else { + logger.Error(clientCertErr, "failed to get Client Certificate") + } + + peerCertName := strings.Join([]string{c.EtcdCluster.Name, c.EtcdCluster.Spec.TLS.Member.PeerSecret, fmt.Sprintf("%d", members)}, "-") + logger.Info("Starting reconciliation of Peer Certificate", peerCertName, c.EtcdCluster.Namespace) + peerCert, peerCertErr := getCertFunc(peerCertName, c.EtcdCluster.Namespace) + if k8serrors.IsNotFound(peerCertErr) { + peerCert, peerCertErr = createCertFunc(peerCertName) + if peerCertErr != nil { + logger.Error(peerCertErr, "failed to create Peer Certificate") + } + } else { + logger.Error(clientCertErr, "failed to get Peer Certificate") + } + + certificates = append(certificates, clientCert, peerCert) + } + + for _, cert := range certificates { + if cert == nil { + return certificates, errors.New("failed to create one or more certificate") + } + } + return certificates, nil +} + +func ReconcileServerCertificate(c NewCertificateManager) (interface{}, error) { + var serverCert interface{} + logger := log.FromContext(c.Ctx) + + getCertFunc, createCertFunc, err := setCertificateFuncs(c) + if err != nil { + return nil, err + } + + serverCertName := strings.Join([]string{c.EtcdCluster.Name, c.EtcdCluster.Spec.TLS.Member.ServerSecret}, "-") + logger.Info("Starting reconciliation of Server Certificate", serverCertName, c.EtcdCluster.Namespace) + _, serverCertErr := getCertFunc(serverCertName, c.EtcdCluster.Namespace) + if k8serrors.IsNotFound(serverCertErr) { + serverCert, serverCertErr = createCertFunc(serverCertName) + if serverCertErr != nil { + logger.Error(serverCertErr, "failed to create Server Certificate") + return nil, serverCertErr + } + } else { + logger.Error(serverCertErr, "failed to get Server Certificate") + return nil, serverCertErr + } + + return serverCert, nil +} diff --git a/internal/certificate/constants.go b/internal/certificate/constants.go new file mode 100644 index 0000000..8b4cca1 --- /dev/null +++ b/internal/certificate/constants.go @@ -0,0 +1,5 @@ +package certificate + +const ( + CMClusterIssuerName = "etcd-operator-selfsigned" +) diff --git a/internal/controller/constants.go b/internal/controller/constants.go deleted file mode 100644 index 05cdec8..0000000 --- a/internal/controller/constants.go +++ /dev/null @@ -1,5 +0,0 @@ -package controller - -const ( - CertClusterIssuerName = "etcd-operator-selfsigned" -) diff --git a/internal/controller/etcdcluster_controller.go b/internal/controller/etcdcluster_controller.go index 4a9b6e7..0adde91 100644 --- a/internal/controller/etcdcluster_controller.go +++ b/internal/controller/etcdcluster_controller.go @@ -31,6 +31,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" + cmv1 "go.etcd.io/etcd-operator/internal/certificate" + ecv1alpha1 "go.etcd.io/etcd-operator/api/v1alpha1" "go.etcd.io/etcd-operator/internal/etcdutils" clientv3 "go.etcd.io/etcd/client/v3" @@ -89,12 +91,22 @@ func (r *EtcdClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) logger.Info("Reconciling EtcdCluster", "spec", etcdCluster.Spec) - logger.Info("Reconciling EtcdCluster Server certificates", "tls", etcdCluster.Spec.TLS) - certificates, err := reconcileServerCertificate(ctx, r.Client, etcdCluster, r.Scheme, logger) + etcdCertManager := cmv1.NewCertificateManager{Ctx: ctx, Client: r.Client, Scheme: r.Scheme, EtcdCluster: etcdCluster} + logger.Info("Reconciling EtcdCluster Server certificate", "tls", etcdCluster.Spec.TLS) + serverCertificate, err := cmv1.ReconcileServerCertificate(etcdCertManager) + if err != nil { + logger.Error(err, "failed to reconcile EtcdCluster Server certificate") + } else { + logger.Info("Successfully reconciled EtcdCluster Server certificate", "tls", serverCertificate) + } + + // needs to be reconciled along with member creation + logger.Info("Reconciling EtcdCluster Member certificates", "tls", etcdCluster.Spec.TLS) + memberCertificates, err := cmv1.ReconcileMemberCertificate(etcdCertManager) if err != nil { - logger.Error(err, "failed to reconcile EtcdCluster Server certificates") + logger.Error(err, "failed to reconcile EtcdCluster Member certificates") } else { - logger.Info("Successfully reconciled EtcdCluster Server certificates", "tls", certificates) + logger.Info("Successfully reconciled EtcdCluster member certificates", "tls", memberCertificates) } // Get the statefulsets which has the same name as the EtcdCluster resource diff --git a/internal/controller/utils.go b/internal/controller/utils.go index 97f5036..1daa643 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -8,8 +8,6 @@ import ( "strings" "time" - certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -404,96 +402,3 @@ func healthCheck(sts *appsv1.StatefulSet, lg klog.Logger) (*clientv3.MemberListR return memberlistResp, healthInfos, nil } - -func reconcileMemberCertificate(ctx context.Context, c client.Client, ec *ecv1alpha1.EtcdCluster, scheme *runtime.Scheme, logger logr.Logger) ([]*certv1.Certificate, error) { - var certificates []*certv1.Certificate - - clientCertName := strings.Join([]string{ec.Name, ec.Spec.TLS.OperatorSecret}, "-") - logger.Info("Starting reconciliation of Client Certificate", clientCertName, ec.Namespace) - clientCert, clientCertErr := getCertificate(ctx, c, clientCertName, ec.Namespace) - if k8serrors.IsNotFound(clientCertErr) { - clientCert, clientCertErr = createCertificate(ctx, c, clientCertName, ec, scheme) - if clientCertErr != nil { - logger.Error(clientCertErr, "failed to create Client Certificate") - } - } else { - logger.Error(clientCertErr, "failed to get Client Certificate") - } - - peerCertName := strings.Join([]string{ec.Name, ec.Spec.TLS.Member.PeerSecret}, "-") - logger.Info("Starting reconciliation of Peer Certificate", peerCertName, ec.Namespace) - peerCert, peerCertErr := getCertificate(ctx, c, peerCertName, ec.Namespace) - if k8serrors.IsNotFound(peerCertErr) { - peerCert, peerCertErr = createCertificate(ctx, c, peerCertName, ec, scheme) - if peerCertErr != nil { - logger.Error(peerCertErr, "failed to create Peer Certificate") - } - } else { - logger.Error(clientCertErr, "failed to get Peer Certificate") - } - - certificates = append(certificates, clientCert, peerCert) - for _, cert := range certificates { - if cert == nil { - return certificates, errors.New("failed to create one or more certificate") - } - } - return certificates, nil -} - -func reconcileServerCertificate(ctx context.Context, c client.Client, ec *ecv1alpha1.EtcdCluster, scheme *runtime.Scheme, logger logr.Logger) (*certv1.Certificate, error) { - - serverCertName := strings.Join([]string{ec.Name, ec.Spec.TLS.Member.ServerSecret}, "-") - logger.Info("Starting reconciliation of Server Certificate", serverCertName, ec.Namespace) - serverCert, serverCertErr := getCertificate(ctx, c, serverCertName, ec.Namespace) - if k8serrors.IsNotFound(serverCertErr) { - serverCert, serverCertErr = createCertificate(ctx, c, serverCertName, ec, scheme) - if serverCertErr != nil { - logger.Error(serverCertErr, "failed to create Server Certificate") - return nil, serverCertErr - } - } else { - logger.Error(serverCertErr, "failed to get Server Certificate") - return nil, serverCertErr - } - - return serverCert, nil -} - -func getCertificate(ctx context.Context, c client.Client, tlsCertName, namespace string) (*certv1.Certificate, error) { - foundCert := &certv1.Certificate{} - - err := c.Get(ctx, client.ObjectKey{Name: tlsCertName, Namespace: namespace}, foundCert) - if err != nil { - return nil, err - } - return foundCert, nil -} - -func createCertificate(ctx context.Context, c client.Client, tlsCertName string, ec *ecv1alpha1.EtcdCluster, scheme *runtime.Scheme) (*certv1.Certificate, error) { - owners, ownersErr := prepareOwnerReference(ec, scheme) - if ownersErr != nil { - return nil, ownersErr - } - certificateResource := &certv1.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: tlsCertName, - Namespace: ec.Namespace, - OwnerReferences: owners, - }, - Spec: certv1.CertificateSpec{ - SecretName: tlsCertName, - DNSNames: []string{fmt.Sprintf("%s-%d.%s.%s.svc.cluster.local", ec.Name, ec.Spec.Size, ec.Name, ec.Namespace)}, - IssuerRef: cmmeta.ObjectReference{ - Name: CertClusterIssuerName, - Kind: "ClusterIssuer", - }, - }, - } - - err := c.Create(ctx, certificateResource) - if err != nil { - return nil, err - } - return certificateResource, nil -}