Skip to content

Commit 4dbcfaf

Browse files
authored
Merge pull request #5842 from jabellard/external_ca_cert
Support Custom API Server CA Certificate for Karmada Instance in Operator
2 parents 2f5dc4f + 3a09ac9 commit 4dbcfaf

File tree

9 files changed

+203
-44
lines changed

9 files changed

+203
-44
lines changed

charts/karmada-operator/crds/operator.karmada.io_karmadas.yaml

+26
Original file line numberDiff line numberDiff line change
@@ -3673,6 +3673,32 @@ spec:
36733673
type: string
36743674
type: object
36753675
type: object
3676+
customCertificate:
3677+
description: |-
3678+
CustomCertificate specifies the configuration to customize the certificates
3679+
for Karmada components or control the certificate generation process, such as
3680+
the algorithm, validity period, etc.
3681+
Currently, it only supports customizing the CA certificate for limited components.
3682+
properties:
3683+
apiServerCACert:
3684+
description: |-
3685+
APIServerCACert references a Kubernetes secret containing the CA certificate
3686+
for component karmada-apiserver.
3687+
The secret must contain the following data keys:
3688+
- tls.crt: The TLS certificate.
3689+
- tls.key: The TLS private key.
3690+
If specified, this CA will be used to issue client certificates for
3691+
all components that access the APIServer as clients.
3692+
properties:
3693+
name:
3694+
description: Name is the name of resource being referenced.
3695+
type: string
3696+
namespace:
3697+
description: Namespace is the namespace for the resource being
3698+
referenced.
3699+
type: string
3700+
type: object
3701+
type: object
36763702
featureGates:
36773703
additionalProperties:
36783704
type: boolean

operator/config/crds/operator.karmada.io_karmadas.yaml

+26
Original file line numberDiff line numberDiff line change
@@ -3673,6 +3673,32 @@ spec:
36733673
type: string
36743674
type: object
36753675
type: object
3676+
customCertificate:
3677+
description: |-
3678+
CustomCertificate specifies the configuration to customize the certificates
3679+
for Karmada components or control the certificate generation process, such as
3680+
the algorithm, validity period, etc.
3681+
Currently, it only supports customizing the CA certificate for limited components.
3682+
properties:
3683+
apiServerCACert:
3684+
description: |-
3685+
APIServerCACert references a Kubernetes secret containing the CA certificate
3686+
for component karmada-apiserver.
3687+
The secret must contain the following data keys:
3688+
- tls.crt: The TLS certificate.
3689+
- tls.key: The TLS private key.
3690+
If specified, this CA will be used to issue client certificates for
3691+
all components that access the APIServer as clients.
3692+
properties:
3693+
name:
3694+
description: Name is the name of resource being referenced.
3695+
type: string
3696+
namespace:
3697+
description: Namespace is the namespace for the resource being
3698+
referenced.
3699+
type: string
3700+
type: object
3701+
type: object
36763702
featureGates:
36773703
additionalProperties:
36783704
type: boolean

operator/pkg/apis/operator/v1alpha1/type.go

+20
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,26 @@ type KarmadaSpec struct {
113113
// By default, the operator will only attempt to download the tarball if it's not yet present in the local cache.
114114
// +optional
115115
CRDTarball *CRDTarball `json:"crdTarball,omitempty"`
116+
117+
// CustomCertificate specifies the configuration to customize the certificates
118+
// for Karmada components or control the certificate generation process, such as
119+
// the algorithm, validity period, etc.
120+
// Currently, it only supports customizing the CA certificate for limited components.
121+
// +optional
122+
CustomCertificate *CustomCertificate `json:"customCertificate,omitempty"`
123+
}
124+
125+
// CustomCertificate holds the configuration for generating the certificate.
126+
type CustomCertificate struct {
127+
// APIServerCACert references a Kubernetes secret containing the CA certificate
128+
// for component karmada-apiserver.
129+
// The secret must contain the following data keys:
130+
// - tls.crt: The TLS certificate.
131+
// - tls.key: The TLS private key.
132+
// If specified, this CA will be used to issue client certificates for
133+
// all components that access the APIServer as clients.
134+
// +optional
135+
APIServerCACert *LocalSecretReference `json:"apiServerCACert,omitempty"`
116136
}
117137

118138
// ImageRegistry represents an image registry as well as the

operator/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go

+26
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

operator/pkg/certs/certs.go

+5
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,11 @@ type KarmadaCert struct {
218218
key []byte
219219
}
220220

221+
// NewKarmadaCert is used to create a new Karmada cert
222+
func NewKarmadaCert(pairName, caName string, cert, key []byte) *KarmadaCert {
223+
return &KarmadaCert{pairName: pairName, caName: caName, cert: cert, key: key}
224+
}
225+
221226
// CertData returns certificate cert data.
222227
func (cert *KarmadaCert) CertData() []byte {
223228
return cert.cert

operator/pkg/init.go

+42-32
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,14 @@ var (
4242

4343
// InitOptions defines all the init workflow options.
4444
type InitOptions struct {
45-
Name string
46-
Namespace string
47-
Kubeconfig *rest.Config
48-
KarmadaVersion string
49-
CRDTarball operatorv1alpha1.CRDTarball
50-
KarmadaDataDir string
51-
Karmada *operatorv1alpha1.Karmada
45+
Name string
46+
Namespace string
47+
Kubeconfig *rest.Config
48+
KarmadaVersion string
49+
CRDTarball operatorv1alpha1.CRDTarball
50+
CustomCertificateConfig operatorv1alpha1.CustomCertificate
51+
KarmadaDataDir string
52+
Karmada *operatorv1alpha1.Karmada
5253
}
5354

5455
// Validate is used to validate the initOptions before creating initJob.
@@ -75,19 +76,20 @@ var _ tasks.InitData = &initData{}
7576
type initData struct {
7677
sync.Once
7778
certs.CertStore
78-
name string
79-
namespace string
80-
karmadaVersion *utilversion.Version
81-
controlplaneConfig *rest.Config
82-
controlplaneAddress string
83-
remoteClient clientset.Interface
84-
karmadaClient clientset.Interface
85-
dnsDomain string
86-
CRDTarball operatorv1alpha1.CRDTarball
87-
karmadaDataDir string
88-
privateRegistry string
89-
featureGates map[string]bool
90-
components *operatorv1alpha1.KarmadaComponents
79+
name string
80+
namespace string
81+
karmadaVersion *utilversion.Version
82+
controlplaneConfig *rest.Config
83+
controlplaneAddress string
84+
remoteClient clientset.Interface
85+
karmadaClient clientset.Interface
86+
dnsDomain string
87+
CRDTarball operatorv1alpha1.CRDTarball
88+
CustomCertificateConfig operatorv1alpha1.CustomCertificate
89+
karmadaDataDir string
90+
privateRegistry string
91+
featureGates map[string]bool
92+
components *operatorv1alpha1.KarmadaComponents
9193
}
9294

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

167169
return &initData{
168-
name: opt.Name,
169-
namespace: opt.Namespace,
170-
karmadaVersion: version,
171-
controlplaneAddress: address,
172-
remoteClient: remoteClient,
173-
CRDTarball: opt.CRDTarball,
174-
karmadaDataDir: opt.KarmadaDataDir,
175-
privateRegistry: privateRegistry,
176-
components: opt.Karmada.Spec.Components,
177-
featureGates: opt.Karmada.Spec.FeatureGates,
178-
dnsDomain: *opt.Karmada.Spec.HostCluster.Networking.DNSDomain,
179-
CertStore: certs.NewCertStore(),
170+
name: opt.Name,
171+
namespace: opt.Namespace,
172+
karmadaVersion: version,
173+
controlplaneAddress: address,
174+
remoteClient: remoteClient,
175+
CRDTarball: opt.CRDTarball,
176+
CustomCertificateConfig: opt.CustomCertificateConfig,
177+
karmadaDataDir: opt.KarmadaDataDir,
178+
privateRegistry: privateRegistry,
179+
components: opt.Karmada.Spec.Components,
180+
featureGates: opt.Karmada.Spec.FeatureGates,
181+
dnsDomain: *opt.Karmada.Spec.HostCluster.Networking.DNSDomain,
182+
CertStore: certs.NewCertStore(),
180183
}, nil
181184
}
182185

@@ -226,6 +229,10 @@ func (data *initData) CrdTarball() operatorv1alpha1.CRDTarball {
226229
return data.CRDTarball
227230
}
228231

232+
func (data *initData) CustomCertificate() operatorv1alpha1.CustomCertificate {
233+
return data.CustomCertificateConfig
234+
}
235+
229236
func (data *initData) KarmadaVersion() string {
230237
return data.karmadaVersion.String()
231238
}
@@ -278,6 +285,9 @@ func NewInitOptWithKarmada(karmada *operatorv1alpha1.Karmada) InitOpt {
278285
if karmada.Spec.CRDTarball != nil {
279286
o.CRDTarball = *karmada.Spec.CRDTarball
280287
}
288+
if karmada.Spec.CustomCertificate != nil {
289+
o.CustomCertificateConfig = *karmada.Spec.CustomCertificate
290+
}
281291
}
282292
}
283293

operator/pkg/tasks/init/cert.go

+39
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@ import (
2222
"fmt"
2323

2424
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
clientset "k8s.io/client-go/kubernetes"
2526
"k8s.io/klog/v2"
2627

28+
operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
2729
"github.com/karmada-io/karmada/operator/pkg/certs"
30+
"github.com/karmada-io/karmada/operator/pkg/constants"
2831
"github.com/karmada-io/karmada/operator/pkg/util"
2932
"github.com/karmada-io/karmada/operator/pkg/workflow"
3033
)
@@ -101,6 +104,26 @@ func runCATask(kc *certs.CertConfig) func(d workflow.RunData) error {
101104
if kc.CAName != "" {
102105
return fmt.Errorf("this function should only be used for CAs, but cert %s has CA %s", kc.Name, kc.CAName)
103106
}
107+
108+
customCertConfig := data.CustomCertificate()
109+
if kc.Name == constants.CaCertAndKeyName && customCertConfig.APIServerCACert != nil {
110+
secretRef := customCertConfig.APIServerCACert
111+
klog.V(4).InfoS("[certs] Loading custom CA certificate", "secret", secretRef.Name, "namespace", secretRef.Namespace)
112+
113+
certData, keyData, err := loadCACertFromSecret(data.RemoteClient(), secretRef)
114+
if err != nil {
115+
return fmt.Errorf("failed to load custom CA certificate: %w", err)
116+
}
117+
118+
klog.V(2).InfoS("[certs] Successfully loaded custom CA certificate", "secret", secretRef.Name)
119+
120+
customKarmadaCert := certs.NewKarmadaCert(kc.Name, kc.CAName, certData, keyData)
121+
122+
data.AddCert(customKarmadaCert)
123+
klog.V(2).InfoS("[certs] Successfully added custom CA certificate to cert store", "certName", kc.Name)
124+
return nil
125+
}
126+
104127
klog.V(4).InfoS("[certs] Creating a new certificate authority", "certName", kc.Name)
105128

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

141+
func loadCACertFromSecret(client clientset.Interface, ref *operatorv1alpha1.LocalSecretReference) ([]byte, []byte, error) {
142+
secret, err := client.CoreV1().Secrets(ref.Namespace).Get(context.TODO(), ref.Name, metav1.GetOptions{})
143+
if err != nil {
144+
return nil, nil, fmt.Errorf("failed to retrieve secret %s/%s: %w", ref.Namespace, ref.Name, err)
145+
}
146+
147+
certData := secret.Data[constants.TLSCertDataKey]
148+
keyData := secret.Data[constants.TLSPrivateKeyDataKey]
149+
150+
if len(certData) == 0 || len(keyData) == 0 {
151+
return nil, nil, fmt.Errorf("secret %s/%s is missing required keys: %s and %s", ref.Namespace, ref.Name, constants.TLSCertDataKey, constants.TLSPrivateKeyDataKey)
152+
}
153+
154+
return certData, keyData, nil
155+
}
156+
118157
func runCertTask(cc, caCert *certs.CertConfig) func(d workflow.RunData) error {
119158
return func(r workflow.RunData) error {
120159
data, ok := r.(InitData)

operator/pkg/tasks/init/data.go

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type InitData interface {
3636
KarmadaClient() clientset.Interface
3737
DataDir() string
3838
CrdTarball() operatorv1alpha1.CRDTarball
39+
CustomCertificate() operatorv1alpha1.CustomCertificate
3940
KarmadaVersion() string
4041
Components() *operatorv1alpha1.KarmadaComponents
4142
FeatureGates() map[string]bool

operator/pkg/tasks/init/test_helpers.go

+18-12
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,19 @@ func (m *MyTestData) Get() string {
4646

4747
// TestInitData contains the configuration and state required to initialize Karmada components.
4848
type TestInitData struct {
49-
Name string
50-
Namespace string
51-
ControlplaneConfigREST *rest.Config
52-
DataDirectory string
53-
CrdTarballArchive operatorv1alpha1.CRDTarball
54-
KarmadaVersionRelease string
55-
ComponentsUnits *operatorv1alpha1.KarmadaComponents
56-
FeatureGatesOptions map[string]bool
57-
RemoteClientConnector clientset.Interface
58-
KarmadaClientConnector clientset.Interface
59-
ControlplaneAddr string
60-
Certs []*certs.KarmadaCert
49+
Name string
50+
Namespace string
51+
ControlplaneConfigREST *rest.Config
52+
DataDirectory string
53+
CrdTarballArchive operatorv1alpha1.CRDTarball
54+
CustomCertificateConfig operatorv1alpha1.CustomCertificate
55+
KarmadaVersionRelease string
56+
ComponentsUnits *operatorv1alpha1.KarmadaComponents
57+
FeatureGatesOptions map[string]bool
58+
RemoteClientConnector clientset.Interface
59+
KarmadaClientConnector clientset.Interface
60+
ControlplaneAddr string
61+
Certs []*certs.KarmadaCert
6162
}
6263

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

112+
// CustomCertificate returns the custom certificate config.
113+
func (t *TestInitData) CustomCertificate() operatorv1alpha1.CustomCertificate {
114+
return t.CustomCertificateConfig
115+
}
116+
111117
// KarmadaVersion returns the version of Karmada being used.
112118
func (t *TestInitData) KarmadaVersion() string {
113119
return t.KarmadaVersionRelease

0 commit comments

Comments
 (0)