Skip to content

Commit c79f432

Browse files
committed
fixup! runtime/secrets: add package for consolidated secret handling
TLSConfigFromSecret: support flexible certificate configurations Implement review feedback from stefanprodan and matheuscscp: - Support CA-only configuration for server verification - Support cert+key-only for mTLS client authentication - Support both CA and cert+key for full mTLS - Add validation to error when cert exists without key or vice versa - Simplify tlsCertificateData with method-based validation - Update tests to handle different certificate scenarios dynamically Signed-off-by: cappyzawa <[email protected]>
1 parent b4cca3a commit c79f432

File tree

2 files changed

+104
-47
lines changed

2 files changed

+104
-47
lines changed

runtime/secrets/reader.go

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,33 @@ type tlsCertificateData struct {
3636
caCert []byte
3737
}
3838

39+
func (t *tlsCertificateData) validate() error {
40+
hasCert := len(t.cert) > 0
41+
hasKey := len(t.key) > 0
42+
hasCA := len(t.caCert) > 0
43+
44+
if hasCert != hasKey {
45+
if hasCert {
46+
return fmt.Errorf("found certificate but missing private key")
47+
}
48+
return fmt.Errorf("found private key but missing certificate")
49+
}
50+
51+
if !hasCert && !hasCA {
52+
return fmt.Errorf("no CA certificate or client certificate pair found")
53+
}
54+
55+
return nil
56+
}
57+
58+
func (t *tlsCertificateData) hasCertPair() bool {
59+
return len(t.cert) > 0 && len(t.key) > 0
60+
}
61+
62+
func (t *tlsCertificateData) hasCA() bool {
63+
return len(t.caCert) > 0
64+
}
65+
3966
// TLSConfigFromSecret creates a TLS configuration from a Kubernetes secret.
4067
//
4168
// The function looks for TLS certificate data in the secret using standard
@@ -165,37 +192,31 @@ func getSecret(ctx context.Context, c client.Client, name, namespace string) (*c
165192
}
166193

167194
func getTLSCertificateData(secret *corev1.Secret, supportDeprecated bool) (*tlsCertificateData, error) {
168-
tlsCert, err := getSecretData(secret, TLSCertKey, TLSCertFileKey, supportDeprecated)
169-
if err != nil {
170-
return nil, fmt.Errorf("failed to get TLS certificate: %w", err)
195+
data := &tlsCertificateData{
196+
cert: getSecretData(secret, TLSCertKey, TLSCertFileKey, supportDeprecated),
197+
key: getSecretData(secret, TLSKeyKey, TLSKeyFileKey, supportDeprecated),
198+
caCert: getSecretData(secret, CACertKey, CACertFileKey, supportDeprecated),
171199
}
172200

173-
tlsKey, err := getSecretData(secret, TLSKeyKey, TLSKeyFileKey, supportDeprecated)
174-
if err != nil {
175-
return nil, fmt.Errorf("failed to get TLS private key: %w", err)
201+
if err := data.validate(); err != nil {
202+
return nil, err
176203
}
177204

178-
// CA certificate is optional, ignore error if not found
179-
caCert, _ := getSecretData(secret, CACertKey, CACertFileKey, supportDeprecated)
180-
181-
return &tlsCertificateData{
182-
cert: tlsCert,
183-
key: tlsKey,
184-
caCert: caCert,
185-
}, nil
205+
return data, nil
186206
}
187207

188208
func buildTLSConfig(certData *tlsCertificateData) (*tls.Config, error) {
189-
cert, err := tls.X509KeyPair(certData.cert, certData.key)
190-
if err != nil {
191-
return nil, fmt.Errorf("failed to parse TLS certificate and key: %w", err)
192-
}
209+
tlsConfig := &tls.Config{}
193210

194-
tlsConfig := &tls.Config{
195-
Certificates: []tls.Certificate{cert},
211+
if certData.hasCertPair() {
212+
cert, err := tls.X509KeyPair(certData.cert, certData.key)
213+
if err != nil {
214+
return nil, fmt.Errorf("failed to parse TLS certificate and key: %w", err)
215+
}
216+
tlsConfig.Certificates = []tls.Certificate{cert}
196217
}
197218

198-
if len(certData.caCert) > 0 {
219+
if certData.hasCA() {
199220
caCertPool := x509.NewCertPool()
200221
if !caCertPool.AppendCertsFromPEM(certData.caCert) {
201222
return nil, fmt.Errorf("failed to parse CA certificate")
@@ -206,16 +227,16 @@ func buildTLSConfig(certData *tlsCertificateData) (*tls.Config, error) {
206227
return tlsConfig, nil
207228
}
208229

209-
func getSecretData(secret *corev1.Secret, key, fallbackKey string, supportDeprecated bool) ([]byte, error) {
230+
func getSecretData(secret *corev1.Secret, key, fallbackKey string, supportDeprecated bool) []byte {
210231
if data, exists := secret.Data[key]; exists {
211-
return data, nil
232+
return data
212233
}
213234

214235
if supportDeprecated {
215236
if data, exists := secret.Data[fallbackKey]; exists {
216-
return data, nil
237+
return data
217238
}
218239
}
219240

220-
return nil, &KeyNotFoundError{Key: key}
241+
return nil
221242
}

runtime/secrets/reader_test.go

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -217,73 +217,96 @@ func TestTLSConfigFromSecret(t *testing.T) {
217217
errMsg: "secret default/tls-secret not found",
218218
},
219219
{
220-
name: "missing certificate",
220+
name: "deprecated fields without option",
221221
secret: &corev1.Secret{
222222
ObjectMeta: metav1.ObjectMeta{
223223
Name: "tls-secret",
224224
Namespace: testNS,
225225
},
226226
Data: map[string][]byte{
227-
secrets.TLSKeyKey: tlsKey,
227+
secrets.TLSCertFileKey: tlsCert,
228+
secrets.TLSKeyFileKey: tlsKey,
228229
},
229230
},
230-
errMsg: "failed to get TLS certificate",
231+
errMsg: "no CA certificate or client certificate pair found",
231232
},
232233
{
233-
name: "missing private key",
234+
name: "invalid certificate data",
234235
secret: &corev1.Secret{
235236
ObjectMeta: metav1.ObjectMeta{
236237
Name: "tls-secret",
237238
Namespace: testNS,
238239
},
239240
Data: map[string][]byte{
240-
secrets.TLSCertKey: tlsCert,
241+
secrets.TLSCertKey: []byte("invalid-cert-data"),
242+
secrets.TLSKeyKey: []byte("invalid-key-data"),
241243
},
242244
},
243-
errMsg: "failed to get TLS private key",
245+
errMsg: "failed to parse TLS certificate and key",
244246
},
245247
{
246-
name: "deprecated fields without option",
248+
name: "invalid CA certificate",
247249
secret: &corev1.Secret{
248250
ObjectMeta: metav1.ObjectMeta{
249251
Name: "tls-secret",
250252
Namespace: testNS,
251253
},
252254
Data: map[string][]byte{
253-
secrets.TLSCertFileKey: tlsCert,
254-
secrets.TLSKeyFileKey: tlsKey,
255+
secrets.TLSCertKey: tlsCert,
256+
secrets.TLSKeyKey: tlsKey,
257+
secrets.CACertKey: []byte("invalid-ca-data"),
255258
},
256259
},
257-
errMsg: `key 'tls.crt' not found in secret`,
260+
errMsg: "failed to parse CA certificate",
258261
},
259262
{
260-
name: "invalid certificate data",
263+
name: "CA certificate only",
261264
secret: &corev1.Secret{
262265
ObjectMeta: metav1.ObjectMeta{
263266
Name: "tls-secret",
264267
Namespace: testNS,
265268
},
266269
Data: map[string][]byte{
267-
secrets.TLSCertKey: []byte("invalid-cert-data"),
268-
secrets.TLSKeyKey: []byte("invalid-key-data"),
270+
secrets.CACertKey: caCert,
269271
},
270272
},
271-
errMsg: "failed to parse TLS certificate and key",
272273
},
273274
{
274-
name: "invalid CA certificate",
275+
name: "certificate without key",
275276
secret: &corev1.Secret{
276277
ObjectMeta: metav1.ObjectMeta{
277278
Name: "tls-secret",
278279
Namespace: testNS,
279280
},
280281
Data: map[string][]byte{
281282
secrets.TLSCertKey: tlsCert,
282-
secrets.TLSKeyKey: tlsKey,
283-
secrets.CACertKey: []byte("invalid-ca-data"),
284283
},
285284
},
286-
errMsg: "failed to parse CA certificate",
285+
errMsg: "found certificate but missing private key",
286+
},
287+
{
288+
name: "key without certificate",
289+
secret: &corev1.Secret{
290+
ObjectMeta: metav1.ObjectMeta{
291+
Name: "tls-secret",
292+
Namespace: testNS,
293+
},
294+
Data: map[string][]byte{
295+
secrets.TLSKeyKey: tlsKey,
296+
},
297+
},
298+
errMsg: "found private key but missing certificate",
299+
},
300+
{
301+
name: "no certificates at all",
302+
secret: &corev1.Secret{
303+
ObjectMeta: metav1.ObjectMeta{
304+
Name: "tls-secret",
305+
Namespace: testNS,
306+
},
307+
Data: map[string][]byte{},
308+
},
309+
errMsg: "no CA certificate or client certificate pair found",
287310
},
288311
}
289312

@@ -303,11 +326,24 @@ func TestTLSConfigFromSecret(t *testing.T) {
303326
} else {
304327
g.Expect(err).ToNot(HaveOccurred())
305328
g.Expect(tlsConfig).ToNot(BeNil())
306-
g.Expect(tlsConfig.Certificates).To(HaveLen(1))
307329

308-
expectedCert, err := tls.X509KeyPair(tlsCert, tlsKey)
309-
g.Expect(err).ToNot(HaveOccurred())
310-
g.Expect(tlsConfig.Certificates[0]).To(Equal(expectedCert))
330+
hasCert := len(tt.secret.Data[secrets.TLSCertKey]) > 0 || len(tt.secret.Data[secrets.TLSCertFileKey]) > 0
331+
hasKey := len(tt.secret.Data[secrets.TLSKeyKey]) > 0 || len(tt.secret.Data[secrets.TLSKeyFileKey]) > 0
332+
hasCertPair := hasCert && hasKey
333+
334+
if hasCertPair {
335+
g.Expect(tlsConfig.Certificates).To(HaveLen(1))
336+
expectedCert, err := tls.X509KeyPair(tlsCert, tlsKey)
337+
g.Expect(err).ToNot(HaveOccurred())
338+
g.Expect(tlsConfig.Certificates[0]).To(Equal(expectedCert))
339+
} else {
340+
g.Expect(tlsConfig.Certificates).To(BeEmpty())
341+
}
342+
343+
hasCA := len(tt.secret.Data[secrets.CACertKey]) > 0 || len(tt.secret.Data[secrets.CACertFileKey]) > 0
344+
if hasCA {
345+
g.Expect(tlsConfig.RootCAs).ToNot(BeNil())
346+
}
311347
}
312348
})
313349
}

0 commit comments

Comments
 (0)