Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

operator: Customizable Certificate Configuration #5944

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
34 changes: 34 additions & 0 deletions charts/karmada-operator/crds/operator.karmada.io_karmadas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
34 changes: 34 additions & 0 deletions operator/config/crds/operator.karmada.io_karmadas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
18 changes: 18 additions & 0 deletions operator/pkg/apis/operator/v1alpha1/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func SetObjectDefaultsKarmada(in *Karmada) {
func setDefaultsKarmada(obj *Karmada) {
setDefaultsHostCluster(obj)
setDefaultsKarmadaComponents(obj)
setDefaultsCertConfig(obj)
}

func setDefaultsKarmadaComponents(obj *Karmada) {
Expand Down Expand Up @@ -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{}
Expand Down
31 changes: 31 additions & 0 deletions operator/pkg/apis/operator/v1alpha1/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions operator/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 20 additions & 14 deletions operator/pkg/certs/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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 = &notAfter
}
}

// GetDefaultCertList returns all of karmada certConfigs, it include karmada, front and etcd.
func GetDefaultCertList() []*CertConfig {
return []*CertConfig{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
22 changes: 2 additions & 20 deletions operator/pkg/certs/certs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions operator/pkg/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
23 changes: 23 additions & 0 deletions operator/pkg/tasks/init/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package tasks

import (
"context"
"crypto/x509"
"errors"
"fmt"

Expand Down Expand Up @@ -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(),
Expand Down
2 changes: 1 addition & 1 deletion operator/pkg/tasks/init/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down