Skip to content

Commit

Permalink
Merge pull request #5842 from jabellard/external_ca_cert
Browse files Browse the repository at this point in the history
Support Custom API Server CA Certificate for Karmada Instance in Operator
  • Loading branch information
karmada-bot authored Nov 22, 2024
2 parents 2f5dc4f + 3a09ac9 commit 4dbcfaf
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 44 deletions.
26 changes: 26 additions & 0 deletions charts/karmada-operator/crds/operator.karmada.io_karmadas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3673,6 +3673,32 @@ spec:
type: string
type: object
type: object
customCertificate:
description: |-
CustomCertificate specifies the configuration to customize the certificates
for Karmada components or control the certificate generation process, such as
the algorithm, validity period, etc.
Currently, it only supports customizing the CA certificate for limited components.
properties:
apiServerCACert:
description: |-
APIServerCACert references a Kubernetes secret containing the CA certificate
for component karmada-apiserver.
The secret must contain the following data keys:
- tls.crt: The TLS certificate.
- tls.key: The TLS private key.
If specified, this CA will be used to issue client certificates for
all components that access the APIServer as clients.
properties:
name:
description: Name is the name of resource being referenced.
type: string
namespace:
description: Namespace is the namespace for the resource being
referenced.
type: string
type: object
type: object
featureGates:
additionalProperties:
type: boolean
Expand Down
26 changes: 26 additions & 0 deletions operator/config/crds/operator.karmada.io_karmadas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3673,6 +3673,32 @@ spec:
type: string
type: object
type: object
customCertificate:
description: |-
CustomCertificate specifies the configuration to customize the certificates
for Karmada components or control the certificate generation process, such as
the algorithm, validity period, etc.
Currently, it only supports customizing the CA certificate for limited components.
properties:
apiServerCACert:
description: |-
APIServerCACert references a Kubernetes secret containing the CA certificate
for component karmada-apiserver.
The secret must contain the following data keys:
- tls.crt: The TLS certificate.
- tls.key: The TLS private key.
If specified, this CA will be used to issue client certificates for
all components that access the APIServer as clients.
properties:
name:
description: Name is the name of resource being referenced.
type: string
namespace:
description: Namespace is the namespace for the resource being
referenced.
type: string
type: object
type: object
featureGates:
additionalProperties:
type: boolean
Expand Down
20 changes: 20 additions & 0 deletions operator/pkg/apis/operator/v1alpha1/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,26 @@ type KarmadaSpec struct {
// By default, the operator will only attempt to download the tarball if it's not yet present in the local cache.
// +optional
CRDTarball *CRDTarball `json:"crdTarball,omitempty"`

// CustomCertificate specifies the configuration to customize the certificates
// for Karmada components or control the certificate generation process, such as
// the algorithm, validity period, etc.
// Currently, it only supports customizing the CA certificate for limited components.
// +optional
CustomCertificate *CustomCertificate `json:"customCertificate,omitempty"`
}

// CustomCertificate holds the configuration for generating the certificate.
type CustomCertificate struct {
// APIServerCACert references a Kubernetes secret containing the CA certificate
// for component karmada-apiserver.
// The secret must contain the following data keys:
// - tls.crt: The TLS certificate.
// - tls.key: The TLS private key.
// If specified, this CA will be used to issue client certificates for
// all components that access the APIServer as clients.
// +optional
APIServerCACert *LocalSecretReference `json:"apiServerCACert,omitempty"`
}

// ImageRegistry represents an image registry as well as the
Expand Down
26 changes: 26 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.

5 changes: 5 additions & 0 deletions operator/pkg/certs/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,11 @@ type KarmadaCert struct {
key []byte
}

// NewKarmadaCert is used to create a new Karmada cert
func NewKarmadaCert(pairName, caName string, cert, key []byte) *KarmadaCert {
return &KarmadaCert{pairName: pairName, caName: caName, cert: cert, key: key}
}

// CertData returns certificate cert data.
func (cert *KarmadaCert) CertData() []byte {
return cert.cert
Expand Down
74 changes: 42 additions & 32 deletions operator/pkg/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ var (

// InitOptions defines all the init workflow options.
type InitOptions struct {
Name string
Namespace string
Kubeconfig *rest.Config
KarmadaVersion string
CRDTarball operatorv1alpha1.CRDTarball
KarmadaDataDir string
Karmada *operatorv1alpha1.Karmada
Name string
Namespace string
Kubeconfig *rest.Config
KarmadaVersion string
CRDTarball operatorv1alpha1.CRDTarball
CustomCertificateConfig operatorv1alpha1.CustomCertificate
KarmadaDataDir string
Karmada *operatorv1alpha1.Karmada
}

// Validate is used to validate the initOptions before creating initJob.
Expand All @@ -75,19 +76,20 @@ var _ tasks.InitData = &initData{}
type initData struct {
sync.Once
certs.CertStore
name string
namespace string
karmadaVersion *utilversion.Version
controlplaneConfig *rest.Config
controlplaneAddress string
remoteClient clientset.Interface
karmadaClient clientset.Interface
dnsDomain string
CRDTarball operatorv1alpha1.CRDTarball
karmadaDataDir string
privateRegistry string
featureGates map[string]bool
components *operatorv1alpha1.KarmadaComponents
name string
namespace string
karmadaVersion *utilversion.Version
controlplaneConfig *rest.Config
controlplaneAddress string
remoteClient clientset.Interface
karmadaClient clientset.Interface
dnsDomain string
CRDTarball operatorv1alpha1.CRDTarball
CustomCertificateConfig operatorv1alpha1.CustomCertificate
karmadaDataDir string
privateRegistry string
featureGates map[string]bool
components *operatorv1alpha1.KarmadaComponents
}

// NewInitJob initializes a job with list of init sub-task. and build
Expand Down Expand Up @@ -165,18 +167,19 @@ func newRunData(opt *InitOptions) (*initData, error) {
}

return &initData{
name: opt.Name,
namespace: opt.Namespace,
karmadaVersion: version,
controlplaneAddress: address,
remoteClient: remoteClient,
CRDTarball: opt.CRDTarball,
karmadaDataDir: opt.KarmadaDataDir,
privateRegistry: privateRegistry,
components: opt.Karmada.Spec.Components,
featureGates: opt.Karmada.Spec.FeatureGates,
dnsDomain: *opt.Karmada.Spec.HostCluster.Networking.DNSDomain,
CertStore: certs.NewCertStore(),
name: opt.Name,
namespace: opt.Namespace,
karmadaVersion: version,
controlplaneAddress: address,
remoteClient: remoteClient,
CRDTarball: opt.CRDTarball,
CustomCertificateConfig: opt.CustomCertificateConfig,
karmadaDataDir: opt.KarmadaDataDir,
privateRegistry: privateRegistry,
components: opt.Karmada.Spec.Components,
featureGates: opt.Karmada.Spec.FeatureGates,
dnsDomain: *opt.Karmada.Spec.HostCluster.Networking.DNSDomain,
CertStore: certs.NewCertStore(),
}, nil
}

Expand Down Expand Up @@ -226,6 +229,10 @@ func (data *initData) CrdTarball() operatorv1alpha1.CRDTarball {
return data.CRDTarball
}

func (data *initData) CustomCertificate() operatorv1alpha1.CustomCertificate {
return data.CustomCertificateConfig
}

func (data *initData) KarmadaVersion() string {
return data.karmadaVersion.String()
}
Expand Down Expand Up @@ -278,6 +285,9 @@ func NewInitOptWithKarmada(karmada *operatorv1alpha1.Karmada) InitOpt {
if karmada.Spec.CRDTarball != nil {
o.CRDTarball = *karmada.Spec.CRDTarball
}
if karmada.Spec.CustomCertificate != nil {
o.CustomCertificateConfig = *karmada.Spec.CustomCertificate
}
}
}

Expand Down
39 changes: 39 additions & 0 deletions operator/pkg/tasks/init/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ import (
"fmt"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"

operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
"github.com/karmada-io/karmada/operator/pkg/certs"
"github.com/karmada-io/karmada/operator/pkg/constants"
"github.com/karmada-io/karmada/operator/pkg/util"
"github.com/karmada-io/karmada/operator/pkg/workflow"
)
Expand Down Expand Up @@ -101,6 +104,26 @@ func runCATask(kc *certs.CertConfig) func(d workflow.RunData) error {
if kc.CAName != "" {
return fmt.Errorf("this function should only be used for CAs, but cert %s has CA %s", kc.Name, kc.CAName)
}

customCertConfig := data.CustomCertificate()
if kc.Name == constants.CaCertAndKeyName && customCertConfig.APIServerCACert != nil {
secretRef := customCertConfig.APIServerCACert
klog.V(4).InfoS("[certs] Loading custom CA certificate", "secret", secretRef.Name, "namespace", secretRef.Namespace)

certData, keyData, err := loadCACertFromSecret(data.RemoteClient(), secretRef)
if err != nil {
return fmt.Errorf("failed to load custom CA certificate: %w", err)
}

klog.V(2).InfoS("[certs] Successfully loaded custom CA certificate", "secret", secretRef.Name)

customKarmadaCert := certs.NewKarmadaCert(kc.Name, kc.CAName, certData, keyData)

data.AddCert(customKarmadaCert)
klog.V(2).InfoS("[certs] Successfully added custom CA certificate to cert store", "certName", kc.Name)
return nil
}

klog.V(4).InfoS("[certs] Creating a new certificate authority", "certName", kc.Name)

cert, err := certs.NewCertificateAuthority(kc)
Expand All @@ -115,6 +138,22 @@ func runCATask(kc *certs.CertConfig) func(d workflow.RunData) error {
}
}

func loadCACertFromSecret(client clientset.Interface, ref *operatorv1alpha1.LocalSecretReference) ([]byte, []byte, error) {
secret, err := client.CoreV1().Secrets(ref.Namespace).Get(context.TODO(), ref.Name, metav1.GetOptions{})
if err != nil {
return nil, nil, fmt.Errorf("failed to retrieve secret %s/%s: %w", ref.Namespace, ref.Name, err)
}

certData := secret.Data[constants.TLSCertDataKey]
keyData := secret.Data[constants.TLSPrivateKeyDataKey]

if len(certData) == 0 || len(keyData) == 0 {
return nil, nil, fmt.Errorf("secret %s/%s is missing required keys: %s and %s", ref.Namespace, ref.Name, constants.TLSCertDataKey, constants.TLSPrivateKeyDataKey)
}

return certData, keyData, nil
}

func runCertTask(cc, caCert *certs.CertConfig) func(d workflow.RunData) error {
return func(r workflow.RunData) error {
data, ok := r.(InitData)
Expand Down
1 change: 1 addition & 0 deletions operator/pkg/tasks/init/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type InitData interface {
KarmadaClient() clientset.Interface
DataDir() string
CrdTarball() operatorv1alpha1.CRDTarball
CustomCertificate() operatorv1alpha1.CustomCertificate
KarmadaVersion() string
Components() *operatorv1alpha1.KarmadaComponents
FeatureGates() map[string]bool
Expand Down
30 changes: 18 additions & 12 deletions operator/pkg/tasks/init/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,19 @@ func (m *MyTestData) Get() string {

// TestInitData contains the configuration and state required to initialize Karmada components.
type TestInitData struct {
Name string
Namespace string
ControlplaneConfigREST *rest.Config
DataDirectory string
CrdTarballArchive operatorv1alpha1.CRDTarball
KarmadaVersionRelease string
ComponentsUnits *operatorv1alpha1.KarmadaComponents
FeatureGatesOptions map[string]bool
RemoteClientConnector clientset.Interface
KarmadaClientConnector clientset.Interface
ControlplaneAddr string
Certs []*certs.KarmadaCert
Name string
Namespace string
ControlplaneConfigREST *rest.Config
DataDirectory string
CrdTarballArchive operatorv1alpha1.CRDTarball
CustomCertificateConfig operatorv1alpha1.CustomCertificate
KarmadaVersionRelease string
ComponentsUnits *operatorv1alpha1.KarmadaComponents
FeatureGatesOptions map[string]bool
RemoteClientConnector clientset.Interface
KarmadaClientConnector clientset.Interface
ControlplaneAddr string
Certs []*certs.KarmadaCert
}

// Ensure TestInitData implements InitData interface at compile time.
Expand Down Expand Up @@ -108,6 +109,11 @@ func (t *TestInitData) CrdTarball() operatorv1alpha1.CRDTarball {
return t.CrdTarballArchive
}

// CustomCertificate returns the custom certificate config.
func (t *TestInitData) CustomCertificate() operatorv1alpha1.CustomCertificate {
return t.CustomCertificateConfig
}

// KarmadaVersion returns the version of Karmada being used.
func (t *TestInitData) KarmadaVersion() string {
return t.KarmadaVersionRelease
Expand Down

0 comments on commit 4dbcfaf

Please sign in to comment.