From 03aad2bca919e0e686f5017523edb6198e305872 Mon Sep 17 00:00:00 2001 From: zhzhuang-zju Date: Thu, 12 Dec 2024 20:49:20 +0800 Subject: [PATCH] operator: Customizable Certificate Configuration Signed-off-by: zhzhuang-zju --- .../crds/operator.karmada.io_karmadas.yaml | 34 +++++++++++++++++++ .../crds/operator.karmada.io_karmadas.yaml | 34 +++++++++++++++++++ .../pkg/apis/operator/v1alpha1/defaults.go | 18 ++++++++++ operator/pkg/apis/operator/v1alpha1/type.go | 31 +++++++++++++++++ .../v1alpha1/zz_generated.deepcopy.go | 34 +++++++++++++++++++ operator/pkg/certs/certs.go | 34 +++++++++++-------- operator/pkg/certs/certs_test.go | 22 ++---------- operator/pkg/init.go | 12 +++++++ operator/pkg/tasks/init/cert.go | 23 +++++++++++++ operator/pkg/tasks/init/upload.go | 2 +- 10 files changed, 209 insertions(+), 35 deletions(-) diff --git a/charts/karmada-operator/crds/operator.karmada.io_karmadas.yaml b/charts/karmada-operator/crds/operator.karmada.io_karmadas.yaml index e636ddc1f615..d806a440bda1 100644 --- a/charts/karmada-operator/crds/operator.karmada.io_karmadas.yaml +++ b/charts/karmada-operator/crds/operator.karmada.io_karmadas.yaml @@ -3698,6 +3698,40 @@ spec: referenced. type: string type: object + certConfig: + description: CertConfig represents the config to generate certificate + by karmada-operator. + properties: + expiry: + description: |- + Expiry is the duration of the certificate's validity period. + The unit is days, where 1 represents one day. + Defaults to 365. + Ignored when NotAfter and NotBefore are not nil. + format: int32 + type: integer + notAfter: + description: |- + NotAfter specifies the end date of the certificate's validity period. + This field can be nil, indicating that the end time will be calculated based on Expiry and NotBefore. + format: date-time + type: string + notBefore: + description: |- + NotBefore specifies the start date of the certificate's validity period. + If NotBefore and NotAfter are both nil, the certificate will inherit the NotBefore date from the CA certificate during its generation. + If NotBefore is nil, the start date of the certificate's validity period will be calculated based on Expiry and NotAfter. + format: date-time + type: string + publicKeyAlgorithm: + description: |- + PublicKeyAlgorithm is the public key algorithm used for the certificate. + This can be RSA or ECDSA. + For ECDSA, using the P-256 elliptic curve. + For RSA, the key is generated with a size of 3072 bits. + Defaults to RSA + type: string + type: object type: object featureGates: additionalProperties: diff --git a/operator/config/crds/operator.karmada.io_karmadas.yaml b/operator/config/crds/operator.karmada.io_karmadas.yaml index e636ddc1f615..d806a440bda1 100644 --- a/operator/config/crds/operator.karmada.io_karmadas.yaml +++ b/operator/config/crds/operator.karmada.io_karmadas.yaml @@ -3698,6 +3698,40 @@ spec: referenced. type: string type: object + certConfig: + description: CertConfig represents the config to generate certificate + by karmada-operator. + properties: + expiry: + description: |- + Expiry is the duration of the certificate's validity period. + The unit is days, where 1 represents one day. + Defaults to 365. + Ignored when NotAfter and NotBefore are not nil. + format: int32 + type: integer + notAfter: + description: |- + NotAfter specifies the end date of the certificate's validity period. + This field can be nil, indicating that the end time will be calculated based on Expiry and NotBefore. + format: date-time + type: string + notBefore: + description: |- + NotBefore specifies the start date of the certificate's validity period. + If NotBefore and NotAfter are both nil, the certificate will inherit the NotBefore date from the CA certificate during its generation. + If NotBefore is nil, the start date of the certificate's validity period will be calculated based on Expiry and NotAfter. + format: date-time + type: string + publicKeyAlgorithm: + description: |- + PublicKeyAlgorithm is the public key algorithm used for the certificate. + This can be RSA or ECDSA. + For ECDSA, using the P-256 elliptic curve. + For RSA, the key is generated with a size of 3072 bits. + Defaults to RSA + type: string + type: object type: object featureGates: additionalProperties: diff --git a/operator/pkg/apis/operator/v1alpha1/defaults.go b/operator/pkg/apis/operator/v1alpha1/defaults.go index 8135e87abfc6..9a77d54c2852 100644 --- a/operator/pkg/apis/operator/v1alpha1/defaults.go +++ b/operator/pkg/apis/operator/v1alpha1/defaults.go @@ -74,6 +74,7 @@ func SetObjectDefaultsKarmada(in *Karmada) { func setDefaultsKarmada(obj *Karmada) { setDefaultsHostCluster(obj) setDefaultsKarmadaComponents(obj) + setDefaultsCertConfig(obj) } func setDefaultsKarmadaComponents(obj *Karmada) { @@ -108,6 +109,23 @@ func setDefaultsHostCluster(obj *Karmada) { } } +func setDefaultsCertConfig(obj *Karmada) { + if obj.Spec.CustomCertificate == nil { + obj.Spec.CustomCertificate = &CustomCertificate{ + CertConfig: &CertConfig{}, + } + } + if obj.Spec.CustomCertificate.CertConfig == nil { + obj.Spec.CustomCertificate.CertConfig = &CertConfig{} + } + if obj.Spec.CustomCertificate.CertConfig.Expiry == 0 { + obj.Spec.CustomCertificate.CertConfig.Expiry = 365 + } + if obj.Spec.CustomCertificate.CertConfig.PublicKeyAlgorithm == nil { + obj.Spec.CustomCertificate.CertConfig.PublicKeyAlgorithm = ptr.To[string]("RSA") + } +} + func setDefaultsEtcd(obj *KarmadaComponents) { if obj.Etcd == nil { obj.Etcd = &Etcd{} diff --git a/operator/pkg/apis/operator/v1alpha1/type.go b/operator/pkg/apis/operator/v1alpha1/type.go index 69731e83a771..824499b5e6a8 100644 --- a/operator/pkg/apis/operator/v1alpha1/type.go +++ b/operator/pkg/apis/operator/v1alpha1/type.go @@ -133,6 +133,37 @@ type CustomCertificate struct { // all components that access the APIServer as clients. // +optional APIServerCACert *LocalSecretReference `json:"apiServerCACert,omitempty"` + + // CertConfig represents the config to generate certificate by karmada-operator. + // +optional + CertConfig *CertConfig `json:"certConfig,omitempty"` +} + +// CertConfig represents the configuration for creating a signing certificate. +// It includes settings such as expiry, not_before, not_after, and public key algorithm. +type CertConfig struct { + // Expiry is the duration of the certificate's validity period. + // The unit is days, where 1 represents one day. + // Defaults to 365. + // Ignored when NotAfter and NotBefore are not nil. + // +optional + Expiry int32 `json:"expiry,omitempty"` + // NotBefore specifies the start date of the certificate's validity period. + // If NotBefore and NotAfter are both nil, the certificate will inherit the NotBefore date from the CA certificate during its generation. + // If NotBefore is nil, the start date of the certificate's validity period will be calculated based on Expiry and NotAfter. + // +optional + NotBefore *metav1.Time `json:"notBefore,omitempty"` + // NotAfter specifies the end date of the certificate's validity period. + // This field can be nil, indicating that the end time will be calculated based on Expiry and NotBefore. + // +optional + NotAfter *metav1.Time `json:"notAfter,omitempty"` + // PublicKeyAlgorithm is the public key algorithm used for the certificate. + // This can be RSA or ECDSA. + // For ECDSA, using the P-256 elliptic curve. + // For RSA, the key is generated with a size of 3072 bits. + // Defaults to RSA + // +optional + PublicKeyAlgorithm *string `json:"publicKeyAlgorithm,omitempty"` } // ImageRegistry represents an image registry as well as the diff --git a/operator/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go b/operator/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go index 4bdec62a012f..761e44fbcd4b 100644 --- a/operator/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go +++ b/operator/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go @@ -69,6 +69,35 @@ func (in *CRDTarball) DeepCopy() *CRDTarball { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CertConfig) DeepCopyInto(out *CertConfig) { + *out = *in + if in.NotBefore != nil { + in, out := &in.NotBefore, &out.NotBefore + *out = (*in).DeepCopy() + } + if in.NotAfter != nil { + in, out := &in.NotAfter, &out.NotAfter + *out = (*in).DeepCopy() + } + if in.PublicKeyAlgorithm != nil { + in, out := &in.PublicKeyAlgorithm, &out.PublicKeyAlgorithm + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertConfig. +func (in *CertConfig) DeepCopy() *CertConfig { + if in == nil { + return nil + } + out := new(CertConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CommonSettings) DeepCopyInto(out *CommonSettings) { *out = *in @@ -114,6 +143,11 @@ func (in *CustomCertificate) DeepCopyInto(out *CustomCertificate) { *out = new(LocalSecretReference) **out = **in } + if in.CertConfig != nil { + in, out := &in.CertConfig, &out.CertConfig + *out = new(CertConfig) + (*in).DeepCopyInto(*out) + } return } diff --git a/operator/pkg/certs/certs.go b/operator/pkg/certs/certs.go index 0132dc952ec9..461e458aad91 100644 --- a/operator/pkg/certs/certs.go +++ b/operator/pkg/certs/certs.go @@ -65,8 +65,9 @@ type altNamesMutatorFunc func(*AltNamesMutatorConfig, *CertConfig) error type CertConfig struct { Name string CAName string - NotAfter *time.Time - PublicKeyAlgorithm x509.PublicKeyAlgorithm // TODO: All public key of karmada cert use the RSA algorithm by default + NotAfter time.Time + Expiry int32 + PublicKeyAlgorithm x509.PublicKeyAlgorithm Config certutil.Config AltNamesMutatorFunc altNamesMutatorFunc } @@ -77,13 +78,6 @@ func (config *CertConfig) defaultPublicKeyAlgorithm() { } } -func (config *CertConfig) defaultNotAfter() { - if config.NotAfter == nil { - notAfter := time.Now().Add(constants.CertificateValidity).UTC() - config.NotAfter = ¬After - } -} - // GetDefaultCertList returns all of karmada certConfigs, it include karmada, front and etcd. func GetDefaultCertList() []*CertConfig { return []*CertConfig{ @@ -301,9 +295,6 @@ func CreateCertAndKeyFilesWithCA(cc *CertConfig, caCertData, caKeyData []byte) ( return nil, fmt.Errorf("must specify at least one ExtKeyUsage") } - cc.defaultNotAfter() - cc.defaultPublicKeyAlgorithm() - key, err := GeneratePrivateKey(cc.PublicKeyAlgorithm) if err != nil { return nil, fmt.Errorf("unable to create private key, err: %w", err) @@ -358,6 +349,21 @@ func NewSignedCert(cc *CertConfig, key crypto.Signer, caCert *x509.Certificate, RemoveDuplicateAltNames(&cc.Config.AltNames) + var notBefore time.Time + if cc.Config.NotBefore.IsZero() && cc.NotAfter.IsZero() { + notBefore = caCert.NotBefore + } else if cc.Config.NotBefore.IsZero() && !cc.NotAfter.IsZero() { + notBefore = cc.NotAfter.Add(-time.Hour * 24 * time.Duration(cc.Expiry)) + } else { + notBefore = cc.Config.NotBefore + } + var notAfter time.Time + if cc.NotAfter.IsZero() { + notAfter = notBefore.Add(time.Hour * 24 * time.Duration(cc.Expiry)) + } else { + notAfter = cc.NotAfter + } + certTmpl := x509.Certificate{ Subject: pkix.Name{ CommonName: cc.Config.CommonName, @@ -366,8 +372,8 @@ func NewSignedCert(cc *CertConfig, key crypto.Signer, caCert *x509.Certificate, DNSNames: cc.Config.AltNames.DNSNames, IPAddresses: cc.Config.AltNames.IPs, SerialNumber: serial, - NotBefore: caCert.NotBefore, - NotAfter: cc.NotAfter.UTC(), + NotBefore: notBefore, + NotAfter: notAfter, KeyUsage: keyUsage, ExtKeyUsage: cc.Config.Usages, BasicConstraintsValid: true, diff --git a/operator/pkg/certs/certs_test.go b/operator/pkg/certs/certs_test.go index c0eb1dd5f011..0e2780d07ba9 100644 --- a/operator/pkg/certs/certs_test.go +++ b/operator/pkg/certs/certs_test.go @@ -59,24 +59,6 @@ func TestCertConfig_defaultPublicKeyAlgorithm(t *testing.T) { } } -func TestCertConfig_defaultNotAfter(t *testing.T) { - c := &CertConfig{} - c.defaultNotAfter() - - if c.NotAfter == nil { - t.Error("expected NotAfter to be set, but it was nil") - } - - if !c.NotAfter.After(time.Now()) { - t.Errorf("expected NotAfter to be a future time, got %v", c.NotAfter) - } - - expectedTime := time.Now().Add(constants.CertificateValidity).UTC() - if c.NotAfter.Sub(expectedTime) > time.Minute { - t.Errorf("NotAfter time is too far from expected, got %v, expected %v", c.NotAfter, expectedTime) - } -} - func TestKarmadaCertRootCA(t *testing.T) { certConfig := KarmadaCertRootCA() @@ -590,7 +572,7 @@ func TestNewSignedCert_Success(t *testing.T) { AltNames: certutil.AltNames{DNSNames: expectedCertDNSNames}, Usages: expectedUsages, }, - NotAfter: &certNotAfter, + NotAfter: certNotAfter, } cert, err := NewSignedCert(cc, key, caCert, caKey, false) @@ -638,7 +620,7 @@ func TestNewSignedCert_ErrorOnEmptyCommonName(t *testing.T) { AltNames: certutil.AltNames{DNSNames: expectedCertDNSNames}, Usages: expectedUsages, }, - NotAfter: &certNotAfter, + NotAfter: certNotAfter, } _, err = NewSignedCert(cc, key, &x509.Certificate{}, key, false) diff --git a/operator/pkg/init.go b/operator/pkg/init.go index 4e4ebae40baf..ca86f346424e 100644 --- a/operator/pkg/init.go +++ b/operator/pkg/init.go @@ -63,6 +63,18 @@ func (opt *InitOptions) Validate() error { return fmt.Errorf("unexpected karmada invalid version %s", opt.KarmadaVersion) } + if opt.CustomCertificateConfig.CertConfig.Expiry < 0 { + return fmt.Errorf("invalid custom cert expiry: %d", opt.CustomCertificateConfig.CertConfig.Expiry) + } + + if opt.CustomCertificateConfig.CertConfig.NotAfter.Before(opt.CustomCertificateConfig.CertConfig.NotBefore) { + return fmt.Errorf("invalid custom cert config: notbefore, %s. notafter, %s", opt.CustomCertificateConfig.CertConfig.NotBefore, opt.CustomCertificateConfig.CertConfig.NotAfter) + } + + if *opt.CustomCertificateConfig.CertConfig.PublicKeyAlgorithm != "RSA" && *opt.CustomCertificateConfig.CertConfig.PublicKeyAlgorithm != "ECDSA" { + return fmt.Errorf("invalid custom cert config: publicKeyAlgorithm, %s", *opt.CustomCertificateConfig.CertConfig.PublicKeyAlgorithm) + } + return nil } diff --git a/operator/pkg/tasks/init/cert.go b/operator/pkg/tasks/init/cert.go index 7f779cc99ad5..7e415780a633 100644 --- a/operator/pkg/tasks/init/cert.go +++ b/operator/pkg/tasks/init/cert.go @@ -18,6 +18,7 @@ package tasks import ( "context" + "crypto/x509" "errors" "fmt" @@ -187,6 +188,28 @@ func runCertTask(cc, caCert *certs.CertConfig) func(d workflow.RunData) error { } func mutateCertConfig(data InitData, cc *certs.CertConfig) error { + cc.PublicKeyAlgorithm = x509.RSA + + customCertConfig := data.CustomCertificate().CertConfig + if customCertConfig != nil { + if customCertConfig.PublicKeyAlgorithm != nil { + switch *customCertConfig.PublicKeyAlgorithm { + case "ECDSA": + cc.PublicKeyAlgorithm = x509.ECDSA + default: + cc.PublicKeyAlgorithm = x509.RSA + } + } + + cc.Expiry = customCertConfig.Expiry + if customCertConfig.NotBefore != nil { + cc.Config.NotBefore = customCertConfig.NotBefore.UTC() + } + if customCertConfig.NotAfter != nil { + cc.NotAfter = customCertConfig.NotAfter.UTC() + } + } + if cc.AltNamesMutatorFunc != nil { err := cc.AltNamesMutatorFunc(&certs.AltNamesMutatorConfig{ Name: data.GetName(), diff --git a/operator/pkg/tasks/init/upload.go b/operator/pkg/tasks/init/upload.go index 6343371abbf3..370f36c66f3b 100644 --- a/operator/pkg/tasks/init/upload.go +++ b/operator/pkg/tasks/init/upload.go @@ -161,7 +161,7 @@ func buildKubeConfigFromSpec(data InitData, serverURL string) (*clientcmdapi.Con cc := certs.KarmadaCertClient() if err := mutateCertConfig(data, cc); err != nil { - return nil, fmt.Errorf("error when mutate cert altNames for %s, err: %w", cc.Name, err) + return nil, fmt.Errorf("error when mutate cert config for %s, err: %w", cc.Name, err) } client, err := certs.CreateCertAndKeyFilesWithCA(cc, ca.CertData(), ca.KeyData()) if err != nil {