Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ func init() {

utilruntime.Must(operatorv1alpha1.AddToScheme(scheme))

// Register cert-manager scheme to enable CRD detection and optional watches
// Note: cert-manager CRDs don't need to be installed if only using auto provider.
// The controller will detect at startup whether cert-manager CRDs exist and
// conditionally enable Certificate watches only if they are present.
utilruntime.Must(certv1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}
Expand Down
6 changes: 3 additions & 3 deletions config/crd/bases/operator.etcd.io_etcdclusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.19.0
controller-gen.kubebuilder.io/version: v0.20.0
name: etcdclusters.operator.etcd.io
spec:
group: operator.etcd.io
Expand Down Expand Up @@ -296,8 +296,8 @@ spec:
CurrentVersion is the observed etcd version of the cluster.
This is typically derived from the version of the healthy leader or a consensus among healthy members.
type: string
leaderId:
description: LeaderId is the hex-encoded ID of the current etcd cluster
leaderID:
description: LeaderID is the hex-encoded ID of the current etcd cluster
Comment on lines +299 to +300
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for this kind of change, it should be in a separate PR, so that it can be merged soon.

leader, if one exists and is known.
type: string
memberCount:
Expand Down
4 changes: 2 additions & 2 deletions config/manager/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: controller
newName: etcd-operator
newTag: v0.1
newName: arkasaha30/etcd-operator
newTag: auto-cert1
Comment on lines +7 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you forgot to unstage this file.

24 changes: 19 additions & 5 deletions internal/controller/etcdcluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func (r *EtcdClusterReconciler) fetchAndValidateState(ctx context.Context, req c

// Ensure the operator has TLS credentials when the cluster requests TLS.
if ec.Spec.TLS != nil {
if err := createClientCertificate(ctx, ec, r.Client); err != nil {
if err := createClientCertificate(ctx, ec, r.Client, r.Scheme); err != nil {
logger.Error(err, "Failed to create Client Certificate.")
}
} else {
Expand Down Expand Up @@ -337,11 +337,25 @@ func (r *EtcdClusterReconciler) reconcileClusterState(ctx context.Context, s *re
// SetupWithManager sets up the controller with the Manager.
func (r *EtcdClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.Recorder = mgr.GetEventRecorderFor("etcdcluster-controller")
return ctrl.NewControllerManagedBy(mgr).
setupLog := ctrl.Log.WithName("setup")

builder := ctrl.NewControllerManagedBy(mgr).
For(&ecv1alpha1.EtcdCluster{}).
Owns(&appsv1.StatefulSet{}).
Owns(&corev1.Service{}).
Owns(&corev1.ConfigMap{}).
Owns(&certv1.Certificate{}).
Complete(r)
Owns(&corev1.ConfigMap{})

// Conditionally watch cert-manager Certificate resources if CRDs are installed
// This allows the controller to react to Certificate status changes when using cert-manager provider
certList := &certv1.CertificateList{}
if err := mgr.GetClient().List(context.Background(), certList); err == nil {
// cert-manager CRDs are installed, add Certificate watch
builder = builder.Owns(&certv1.Certificate{})
setupLog.Info("cert-manager CRDs detected, enabling Certificate watches")
} else {
// cert-manager CRDs not installed, skip Certificate watch
setupLog.Info("cert-manager CRDs not detected, only auto provider will be available. Restart the controller after cert-manager CRDs are installed")
}

return builder.Complete(r)
}
24 changes: 12 additions & 12 deletions internal/controller/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func reconcileStatefulSet(ctx context.Context, logger logr.Logger, ec *ecv1alpha
}

// Add server and peer certificate
err = applyEtcdMemberCerts(ctx, ec, c)
err = applyEtcdMemberCerts(ctx, ec, c, scheme)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -656,14 +656,14 @@ func createAutoCertificateConfig(ec *ecv1alpha1.EtcdCluster) (*certInterface.Con
return config, nil
}

func createCertificate(ec *ecv1alpha1.EtcdCluster, ctx context.Context, c client.Client, certName string) error {
func createCertificate(ec *ecv1alpha1.EtcdCluster, ctx context.Context, c client.Client, scheme *runtime.Scheme, certName string) error {
// The TLS field is present but spec is empty
providerName := ec.Spec.TLS.Provider
if providerName == "" {
providerName = string(certificate.Auto)
}

cert, certErr := certificate.NewProvider(certificate.ProviderType(providerName), c)
cert, certErr := certificate.NewProvider(certificate.ProviderType(providerName), c, scheme)
if certErr != nil {
// TODO: instead of error, set default autoConfig
return certErr
Expand Down Expand Up @@ -707,9 +707,9 @@ func createCertificate(ec *ecv1alpha1.EtcdCluster, ctx context.Context, c client
return nil
}

func createClientCertificate(ctx context.Context, ec *ecv1alpha1.EtcdCluster, c client.Client) error {
func createClientCertificate(ctx context.Context, ec *ecv1alpha1.EtcdCluster, c client.Client, scheme *runtime.Scheme) error {
certName := getClientCertName(ec.Name)
err := createCertificate(ec, ctx, c, certName)
err := createCertificate(ec, ctx, c, scheme, certName)
if err != nil {
return err
}
Expand All @@ -720,9 +720,9 @@ func createClientCertificate(ctx context.Context, ec *ecv1alpha1.EtcdCluster, c
return err
}

func createServerCertificate(ctx context.Context, ec *ecv1alpha1.EtcdCluster, c client.Client) error {
func createServerCertificate(ctx context.Context, ec *ecv1alpha1.EtcdCluster, c client.Client, scheme *runtime.Scheme) error {
serverCertName := getServerCertName(ec.Name)
err := createCertificate(ec, ctx, c, serverCertName)
err := createCertificate(ec, ctx, c, scheme, serverCertName)
if err != nil {
return err
}
Expand All @@ -733,9 +733,9 @@ func createServerCertificate(ctx context.Context, ec *ecv1alpha1.EtcdCluster, c
return nil
}

func createPeerCertificate(ctx context.Context, ec *ecv1alpha1.EtcdCluster, c client.Client) error {
func createPeerCertificate(ctx context.Context, ec *ecv1alpha1.EtcdCluster, c client.Client, scheme *runtime.Scheme) error {
peerCertName := getPeerCertName(ec.Name)
err := createCertificate(ec, ctx, c, peerCertName)
err := createCertificate(ec, ctx, c, scheme, peerCertName)
if err != nil {
return err
}
Expand All @@ -746,13 +746,13 @@ func createPeerCertificate(ctx context.Context, ec *ecv1alpha1.EtcdCluster, c cl
return nil
}

func applyEtcdMemberCerts(ctx context.Context, ec *ecv1alpha1.EtcdCluster, c client.Client) error {
func applyEtcdMemberCerts(ctx context.Context, ec *ecv1alpha1.EtcdCluster, c client.Client, scheme *runtime.Scheme) error {
if ec.Spec.TLS != nil {
err := createServerCertificate(ctx, ec, c)
err := createServerCertificate(ctx, ec, c, scheme)
if err != nil {
return err
}
err = createPeerCertificate(ctx, ec, c)
err = createPeerCertificate(ctx, ec, c, scheme)
if err != nil {
return err
}
Expand Down
59 changes: 44 additions & 15 deletions pkg/certificate/auto/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
corev1 "k8s.io/api/core/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"

interfaces "go.etcd.io/etcd-operator/pkg/certificate/interfaces"
Expand All @@ -40,7 +41,8 @@ type Provider struct {

var _ interfaces.Provider = (*Provider)(nil)

func New(c client.Client) interfaces.Provider {
func New(c client.Client, scheme *runtime.Scheme) interfaces.Provider {
// Auto provider doesn't need any additional scheme registration
return &Provider{
Client: c,
config: nil,
Expand Down Expand Up @@ -101,26 +103,16 @@ func (ac *Provider) ValidateCertificateSecret(ctx context.Context, secretKey cli
return err
}

certificateData, exists := secret.Data["tls.crt"]
if !exists {
return interfaces.ErrTLSCert
}

decodeCertificatePem, _ := pem.Decode(certificateData)
if decodeCertificatePem == nil {
return interfaces.ErrDecodeCert
parseCert, err := parseCertificateFromSecret(secret)
if err != nil {
return err
}

privateKeyData, keyExists := secret.Data["tls.key"]
if !keyExists {
return interfaces.ErrTLSKey
}

parseCert, err := x509.ParseCertificate(decodeCertificatePem.Bytes)
if err != nil {
return fmt.Errorf("failed to parse certificate: %w", err)
}

now := time.Now()
if parseCert.NotBefore.After(now) {
return interfaces.ErrCertNotYetValid
Expand Down Expand Up @@ -164,11 +156,48 @@ func (ac *Provider) GetCertificateConfig(ctx context.Context,
return nil, fmt.Errorf("failed to get certificate: %w", err)
}

// If config was set during creation, return it
if ac.config != nil {
return ac.config, nil
}

return nil, fmt.Errorf("failed to get user-defined autoConfig")
// For existing secrets, parse the certificate to extract the config
cert, err := parseCertificateFromSecret(&autoCertSecret)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate from secret: %w", err)
}

// Extract config from the certificate
config := &interfaces.Config{
CommonName: cert.Subject.CommonName,
Organization: cert.Subject.Organization,
AltNames: interfaces.AltNames{
DNSNames: cert.DNSNames,
IPs: cert.IPAddresses,
},
}

return config, nil
}

// parseCertificateFromSecret extracts and parses the x509 certificate from a Kubernetes secret.
func parseCertificateFromSecret(secret *corev1.Secret) (*x509.Certificate, error) {
certData, ok := secret.Data[corev1.TLSCertKey]
if !ok {
return nil, interfaces.ErrTLSCert
}

block, _ := pem.Decode(certData)
if block == nil {
return nil, interfaces.ErrDecodeCert
}

cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate: %w", err)
}

return cert, nil
}

// parsePrivateKey parses the private key from the PEM-encoded data.
Expand Down
6 changes: 5 additions & 1 deletion pkg/certificate/cert_manager/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
corev1 "k8s.io/api/core/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"

interfaces "go.etcd.io/etcd-operator/pkg/certificate/interfaces"
Expand All @@ -37,7 +38,10 @@ type CertManagerProvider struct {

var _ interfaces.Provider = (*CertManagerProvider)(nil)

func New(c client.Client) interfaces.Provider {
func New(c client.Client, scheme *runtime.Scheme) interfaces.Provider {
// Note: cert-manager scheme is registered at startup in main.go to enable
// CRD detection and optional watches. The scheme parameter is kept for
// interface compatibility but not used here.
return &CertManagerProvider{
c,
}
Expand Down
7 changes: 4 additions & 3 deletions pkg/certificate/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package certificate
import (
"fmt"

"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

"go.etcd.io/etcd-operator/pkg/certificate/auto"
Expand All @@ -18,12 +19,12 @@ const (
// add more ...
)

func NewProvider(pt ProviderType, c client.Client) (certInterface.Provider, error) {
func NewProvider(pt ProviderType, c client.Client, scheme *runtime.Scheme) (certInterface.Provider, error) {
switch pt {
case Auto:
return auto.New(c), nil
return auto.New(c, scheme), nil
case CertManager:
return certManager.New(c), nil
return certManager.New(c, scheme), nil
}

return nil, fmt.Errorf("unknown provider type: %s", pt)
Expand Down
13 changes: 5 additions & 8 deletions test/e2e/auto_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"testing"
"time"

certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apiextensionsV1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
Expand Down Expand Up @@ -45,7 +44,6 @@ func TestAutoProvider(t *testing.T) {
client := cfg.Client()
_ = appsv1.AddToScheme(client.Resources().GetScheme())
_ = corev1.AddToScheme(client.Resources().GetScheme())
_ = certv1.AddToScheme(client.Resources().GetScheme())
_ = apiextensionsV1.AddToScheme(client.Resources().GetScheme())

return ctx
Expand All @@ -54,7 +52,7 @@ func TestAutoProvider(t *testing.T) {
feature.Assess("Ensure certificate",
func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
cl := cfg.Client()
acProvider := auto.New(cl.Resources().GetControllerRuntimeClient())
acProvider := auto.New(cl.Resources().GetControllerRuntimeClient(), cl.Resources().GetScheme())
secretKey := client.ObjectKey{Name: autoCertificateName, Namespace: autoCertificateNamespace}
err := acProvider.EnsureCertificateSecret(ctx, secretKey, cmConfig)
if err != nil {
Expand All @@ -66,7 +64,7 @@ func TestAutoProvider(t *testing.T) {
feature.Assess("Validate certificate secret",
func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
cl := cfg.Client()
acProvider := auto.New(cl.Resources().GetControllerRuntimeClient())
acProvider := auto.New(cl.Resources().GetControllerRuntimeClient(), cl.Resources().GetScheme())
secretKey := client.ObjectKey{Name: autoCertificateName, Namespace: autoCertificateNamespace}
err := acProvider.ValidateCertificateSecret(ctx, secretKey, cmConfig)
if err != nil {
Expand All @@ -78,7 +76,7 @@ func TestAutoProvider(t *testing.T) {
feature.Assess("Get certificate config",
func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
cl := cfg.Client()
acProvider := auto.New(cl.Resources().GetControllerRuntimeClient())
acProvider := auto.New(cl.Resources().GetControllerRuntimeClient(), cl.Resources().GetScheme())
secretKey := client.ObjectKey{Name: cmCertificateName, Namespace: cmCertificateNamespace}
err := acProvider.EnsureCertificateSecret(ctx, secretKey, cmConfig)
if err != nil {
Expand All @@ -97,7 +95,7 @@ func TestAutoProvider(t *testing.T) {
feature.Assess("Delete certificate secret",
func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
cl := cfg.Client()
acProvider := auto.New(cl.Resources().GetControllerRuntimeClient())
acProvider := auto.New(cl.Resources().GetControllerRuntimeClient(), cl.Resources().GetScheme())
secretKey := client.ObjectKey{Name: autoCertificateName, Namespace: autoCertificateNamespace}
err := acProvider.DeleteCertificateSecret(ctx, secretKey)
if err != nil {
Expand All @@ -109,7 +107,7 @@ func TestAutoProvider(t *testing.T) {
feature.Assess("Verify Delete certificate",
func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
cl := cfg.Client()
acProvider := auto.New(cl.Resources().GetControllerRuntimeClient())
acProvider := auto.New(cl.Resources().GetControllerRuntimeClient(), cl.Resources().GetScheme())
secretKey := client.ObjectKey{Name: autoCertificateName, Namespace: autoCertificateNamespace}
_, err := acProvider.GetCertificateConfig(ctx, secretKey)
if err == nil {
Expand Down Expand Up @@ -157,7 +155,6 @@ func TestClusterAutoCertCreation(t *testing.T) {
client := cfg.Client()
_ = appsv1.AddToScheme(client.Resources().GetScheme())
_ = corev1.AddToScheme(client.Resources().GetScheme())
_ = certv1.AddToScheme(client.Resources().GetScheme())
_ = apiextensionsV1.AddToScheme(client.Resources().GetScheme())

// create etcd cluster
Expand Down
Loading