Skip to content

Commit 4bd6bcc

Browse files
committed
helmrepo: adopt Kubernetes TLS secrets for .spec.certSecretRef
Adopt Kubernetes TLS secrets API to check for TLS data in the Secret referred to by `.spec.certSecretRef`, i.e. check for keys `tls.crt` and `tls.key` for the certificate and private key. Use `ca.crt` for the CA certificate. Signed-off-by: Sanskar Jaiswal <[email protected]>
1 parent de31a12 commit 4bd6bcc

File tree

11 files changed

+487
-225
lines changed

11 files changed

+487
-225
lines changed

api/v1beta2/helmrepository_types.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,21 @@ type HelmRepositorySpec struct {
5656
// +optional
5757
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
5858

59-
// CertSecretRef specifies the Secret containing the TLS authentication
60-
// data. The secret must contain a 'certFile' and 'keyFile', and/or 'caFile'
61-
// fields. It takes precedence over the values specified in the Secret
62-
// referred to by `.spec.secretRef`.
59+
// CertSecretRef can be given the name of a Secret containing
60+
// either or both of
61+
//
62+
// - a PEM-encoded client certificate (`tls.crt`) and private
63+
// key (`tls.key`);
64+
// - a PEM-encoded CA certificate (`ca.crt`)
65+
//
66+
// and whichever are supplied, will be used for connecting to the
67+
// registry. The client cert and key are useful if you are
68+
// authenticating with a certificate; the CA cert is useful if
69+
// you are using a self-signed server certificate. The Secret must
70+
// be of type `Opaque` or `kubernetes.io/tls`.
71+
//
72+
// It takes precedence over the values specified in the Secret referred
73+
// to by `.spec.secretRef`.
6374
// +optional
6475
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
6576

config/crd/bases/source.toolkit.fluxcd.io_helmrepositories.yaml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -297,10 +297,15 @@ spec:
297297
- namespaceSelectors
298298
type: object
299299
certSecretRef:
300-
description: CertSecretRef specifies the Secret containing the TLS
301-
authentication data. The secret must contain a 'certFile' and 'keyFile',
302-
and/or 'caFile' fields. It takes precedence over the values specified
303-
in the Secret referred to by `.spec.secretRef`.
300+
description: "CertSecretRef can be given the name of a Secret containing
301+
either or both of \n - a PEM-encoded client certificate (`tls.crt`)
302+
and private key (`tls.key`); - a PEM-encoded CA certificate (`ca.crt`)
303+
\n and whichever are supplied, will be used for connecting to the
304+
registry. The client cert and key are useful if you are authenticating
305+
with a certificate; the CA cert is useful if you are using a self-signed
306+
server certificate. The Secret must be of type `Opaque` or `kubernetes.io/tls`.
307+
\n It takes precedence over the values specified in the Secret referred
308+
to by `.spec.secretRef`."
304309
properties:
305310
name:
306311
description: Name of the referent.

docs/api/v1beta2/source.md

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -811,10 +811,20 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
811811
</td>
812812
<td>
813813
<em>(Optional)</em>
814-
<p>CertSecretRef specifies the Secret containing the TLS authentication
815-
data. The secret must contain a &lsquo;certFile&rsquo; and &lsquo;keyFile&rsquo;, and/or &lsquo;caFile&rsquo;
816-
fields. It takes precedence over the values specified in the Secret
817-
referred to by <code>.spec.secretRef</code>.</p>
814+
<p>CertSecretRef can be given the name of a Secret containing
815+
either or both of</p>
816+
<ul>
817+
<li>a PEM-encoded client certificate (<code>tls.crt</code>) and private
818+
key (<code>tls.key</code>);</li>
819+
<li>a PEM-encoded CA certificate (<code>ca.crt</code>)</li>
820+
</ul>
821+
<p>and whichever are supplied, will be used for connecting to the
822+
registry. The client cert and key are useful if you are
823+
authenticating with a certificate; the CA cert is useful if
824+
you are using a self-signed server certificate. The Secret must
825+
be of type <code>Opaque</code> or <code>kubernetes.io/tls</code>.</p>
826+
<p>It takes precedence over the values specified in the Secret referred
827+
to by <code>.spec.secretRef</code>.</p>
818828
</td>
819829
</tr>
820830
<tr>
@@ -2503,10 +2513,20 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
25032513
</td>
25042514
<td>
25052515
<em>(Optional)</em>
2506-
<p>CertSecretRef specifies the Secret containing the TLS authentication
2507-
data. The secret must contain a &lsquo;certFile&rsquo; and &lsquo;keyFile&rsquo;, and/or &lsquo;caFile&rsquo;
2508-
fields. It takes precedence over the values specified in the Secret
2509-
referred to by <code>.spec.secretRef</code>.</p>
2516+
<p>CertSecretRef can be given the name of a Secret containing
2517+
either or both of</p>
2518+
<ul>
2519+
<li>a PEM-encoded client certificate (<code>tls.crt</code>) and private
2520+
key (<code>tls.key</code>);</li>
2521+
<li>a PEM-encoded CA certificate (<code>ca.crt</code>)</li>
2522+
</ul>
2523+
<p>and whichever are supplied, will be used for connecting to the
2524+
registry. The client cert and key are useful if you are
2525+
authenticating with a certificate; the CA cert is useful if
2526+
you are using a self-signed server certificate. The Secret must
2527+
be of type <code>Opaque</code> or <code>kubernetes.io/tls</code>.</p>
2528+
<p>It takes precedence over the values specified in the Secret referred
2529+
to by <code>.spec.secretRef</code>.</p>
25102530
</td>
25112531
</tr>
25122532
<tr>

docs/spec/v1beta2/helmrepositories.md

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -467,32 +467,33 @@ flux create secret oci ghcr-auth \
467467
--password=${GITHUB_PAT}
468468
```
469469

470-
**Note:** Support for specifying TLS authentication data using this API has been
470+
**Warning:** Support for specifying TLS authentication data using this API has been
471471
deprecated. Please use [`.spec.certSecretRef`](#cert-secret-reference) instead.
472472
If the controller uses the secret specfied by this field to configure TLS, then
473473
a deprecation warning will be logged.
474474

475475
### Cert secret reference
476476

477-
`.spec.certSecretRef.name` is an optional field to specify a secret containing TLS
478-
certificate data. The secret can contain the following keys:
477+
`.spec.certSecretRef.name` is an optional field to specify a secret containing
478+
TLS certificate data. The secret can contain the following keys:
479479

480-
* `certFile` and `keyFile`, to specify the client certificate and private key used for
481-
TLS client authentication. These must be used in conjunction, i.e. specifying one without
482-
the other will lead to an error.
483-
* `caFile`, to specify the CA certificate used to verify the server, which is required
484-
if the server is using a self-signed certificate.
480+
* `tls.crt` and `tls.key`, to specify the client certificate and private key used
481+
for TLS client authentication. These must be used in conjunction, i.e.
482+
specifying one without the other will lead to an error.
483+
* `ca.crt`, to specify the CA certificate used to verify the server, which is
484+
required if the server is using a self-signed certificate.
485485

486-
If the server is using a self-signed certificate and has TLS client authentication enabled,
487-
all three values are required.
486+
If the server is using a self-signed certificate and has TLS client
487+
authentication enabled, all three values are required.
488488

489-
All the files in the secret are expected to be [PEM-encoded][pem-encoding]. Assuming you have
490-
three files; `client.key`, `client.crt` and `ca.crt` for the client private key, client
491-
certificate and the CA certificate respectively, you can generate the required secret using
492-
the `flux creat secret helm` command:
489+
The Secret should be of type `Opaque` or `kubernetes.io/tls`. All the files in
490+
the Secret are expected to be [PEM-encoded][pem-encoding]. Assuming you have
491+
three files; `client.key`, `client.crt` and `ca.crt` for the client private key,
492+
client certificate and the CA certificate respectively, you can generate the
493+
required Secret using the `flux create secret tls` command:
493494

494495
```sh
495-
flux create secret helm tls --key-file=client.key --cert-file=client.crt --ca-file=ca.crt
496+
flux create secret tls --tls-key-file=client.key --tls-crt-file=client.crt --ca-crt-file=ca.crt
496497
```
497498

498499
Example usage:
@@ -515,11 +516,12 @@ kind: Secret
515516
metadata:
516517
name: example-tls
517518
namespace: default
519+
type: kubernetes.io/tls # or Opaque
518520
data:
519-
certFile: <BASE64>
520-
keyFile: <BASE64>
521+
tls.crt: <BASE64>
522+
tls.key: <BASE64>
521523
# NOTE: Can be supplied without the above values
522-
caFile: <BASE64>
524+
ca.crt: <BASE64>
523525
```
524526

525527
### Pass credentials

internal/controller/helmchart_controller_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2248,7 +2248,7 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
22482248
registryOpts registryOptions
22492249
secretOpts secretOptions
22502250
secret *corev1.Secret
2251-
certsecret *corev1.Secret
2251+
certSecret *corev1.Secret
22522252
insecure bool
22532253
provider string
22542254
providerImg string
@@ -2363,16 +2363,16 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
23632363
Type: corev1.SecretTypeDockerConfigJson,
23642364
Data: map[string][]byte{},
23652365
},
2366-
certsecret: &corev1.Secret{
2366+
certSecret: &corev1.Secret{
23672367
ObjectMeta: metav1.ObjectMeta{
23682368
Name: "certs-secretref",
23692369
},
23702370
Data: map[string][]byte{
2371-
"caFile": []byte("invalid caFile"),
2371+
"ca.crt": []byte("invalid caFile"),
23722372
},
23732373
},
23742374
assertConditions: []metav1.Condition{
2375-
*conditions.TrueCondition(sourcev1.FetchFailedCondition, "Unknown", "unknown build error: failed to construct Helm client's TLS config: cannot append certificate into certificate pool: invalid caFile"),
2375+
*conditions.TrueCondition(sourcev1.FetchFailedCondition, "Unknown", "unknown build error: failed to construct Helm client's TLS config: cannot append certificate into certificate pool: invalid CA certificate"),
23762376
},
23772377
},
23782378
{
@@ -2393,14 +2393,14 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
23932393
Type: corev1.SecretTypeDockerConfigJson,
23942394
Data: map[string][]byte{},
23952395
},
2396-
certsecret: &corev1.Secret{
2396+
certSecret: &corev1.Secret{
23972397
ObjectMeta: metav1.ObjectMeta{
23982398
Name: "certs-secretref",
23992399
},
24002400
Data: map[string][]byte{
2401-
"caFile": tlsCA,
2402-
"certFile": clientPublicKey,
2403-
"keyFile": clientPrivateKey,
2401+
"ca.crt": tlsCA,
2402+
"tls.crt": clientPublicKey,
2403+
"tls.key": clientPrivateKey,
24042404
},
24052405
},
24062406
assertConditions: []metav1.Condition{
@@ -2472,11 +2472,11 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
24722472
clientBuilder.WithObjects(tt.secret)
24732473
}
24742474

2475-
if tt.certsecret != nil {
2475+
if tt.certSecret != nil {
24762476
repo.Spec.CertSecretRef = &meta.LocalObjectReference{
2477-
Name: tt.certsecret.Name,
2477+
Name: tt.certSecret.Name,
24782478
}
2479-
clientBuilder.WithObjects(tt.certsecret)
2479+
clientBuilder.WithObjects(tt.certSecret)
24802480
}
24812481

24822482
clientBuilder.WithObjects(repo)

internal/controller/helmrepository_controller_oci_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -325,12 +325,12 @@ func TestHelmRepositoryOCIReconciler_authStrategy(t *testing.T) {
325325
Name: "certs-secretref",
326326
},
327327
Data: map[string][]byte{
328-
"caFile": []byte("invalid caFile"),
328+
"ca.crt": []byte("invalid caFile"),
329329
},
330330
},
331331
assertConditions: []metav1.Condition{
332332
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingWithRetryReason, "processing object: new generation 0 -> 1"),
333-
*conditions.FalseCondition(meta.ReadyCondition, sourcev1.AuthenticationFailedReason, "cannot append certificate into certificate pool: invalid caFile"),
333+
*conditions.FalseCondition(meta.ReadyCondition, sourcev1.AuthenticationFailedReason, "cannot append certificate into certificate pool: invalid CA certificate"),
334334
},
335335
},
336336
{
@@ -356,9 +356,9 @@ func TestHelmRepositoryOCIReconciler_authStrategy(t *testing.T) {
356356
Name: "certs-secretref",
357357
},
358358
Data: map[string][]byte{
359-
"caFile": tlsCA,
360-
"certFile": clientPublicKey,
361-
"keyFile": clientPrivateKey,
359+
"ca.crt": tlsCA,
360+
"tls.crt": clientPublicKey,
361+
"tls.key": clientPrivateKey,
362362
},
363363
},
364364
assertConditions: []metav1.Condition{

internal/controller/helmrepository_controller_test.go

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import (
5656
"github.com/fluxcd/source-controller/internal/helm/repository"
5757
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
5858
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
59+
stls "github.com/fluxcd/source-controller/internal/tls"
5960
)
6061

6162
func TestHelmRepositoryReconciler_deleteBeforeFinalizer(t *testing.T) {
@@ -434,16 +435,76 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
434435
Name: "ca-file",
435436
},
436437
Data: map[string][]byte{
437-
"caFile": tlsCA,
438+
"ca.crt": tlsCA,
439+
},
440+
},
441+
beforeFunc: func(t *WithT, obj *helmv1.HelmRepository, rev digest.Digest) {
442+
obj.Spec.CertSecretRef = &meta.LocalObjectReference{Name: "ca-file"}
443+
},
444+
assertConditions: []metav1.Condition{
445+
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new index revision"),
446+
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new index revision"),
447+
},
448+
},
449+
{
450+
name: "HTTPS with certSecretRef makes ArtifactOutdated=True",
451+
protocol: "https",
452+
server: options{
453+
publicKey: tlsPublicKey,
454+
privateKey: tlsPrivateKey,
455+
ca: tlsCA,
456+
},
457+
secret: &corev1.Secret{
458+
ObjectMeta: metav1.ObjectMeta{
459+
Name: "ca-file",
460+
},
461+
Data: map[string][]byte{
462+
"ca.crt": tlsCA,
438463
},
439464
},
440465
beforeFunc: func(t *WithT, obj *helmv1.HelmRepository, rev digest.Digest) {
441466
obj.Spec.CertSecretRef = &meta.LocalObjectReference{Name: "ca-file"}
442467
},
468+
want: sreconcile.ResultSuccess,
443469
assertConditions: []metav1.Condition{
444470
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new index revision"),
445471
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new index revision"),
446472
},
473+
afterFunc: func(t *WithT, obj *helmv1.HelmRepository, artifact sourcev1.Artifact, chartRepo *repository.ChartRepository) {
474+
t.Expect(chartRepo.Path).ToNot(BeEmpty())
475+
t.Expect(chartRepo.Index).ToNot(BeNil())
476+
t.Expect(artifact.Revision).ToNot(BeEmpty())
477+
},
478+
},
479+
{
480+
name: "HTTPS with secretRef and caFile key makes ArtifactOutdated=True",
481+
protocol: "https",
482+
server: options{
483+
publicKey: tlsPublicKey,
484+
privateKey: tlsPrivateKey,
485+
ca: tlsCA,
486+
},
487+
secret: &corev1.Secret{
488+
ObjectMeta: metav1.ObjectMeta{
489+
Name: "ca-file",
490+
},
491+
Data: map[string][]byte{
492+
"caFile": tlsCA,
493+
},
494+
},
495+
beforeFunc: func(t *WithT, obj *helmv1.HelmRepository, rev digest.Digest) {
496+
obj.Spec.SecretRef = &meta.LocalObjectReference{Name: "ca-file"}
497+
},
498+
want: sreconcile.ResultSuccess,
499+
assertConditions: []metav1.Condition{
500+
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new index revision"),
501+
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new index revision"),
502+
},
503+
afterFunc: func(t *WithT, obj *helmv1.HelmRepository, artifact sourcev1.Artifact, chartRepo *repository.ChartRepository) {
504+
t.Expect(chartRepo.Path).ToNot(BeEmpty())
505+
t.Expect(chartRepo.Index).ToNot(BeNil())
506+
t.Expect(artifact.Revision).ToNot(BeEmpty())
507+
},
447508
},
448509
{
449510
name: "HTTP without secretRef makes ArtifactOutdated=True",
@@ -502,7 +563,7 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
502563
Name: "invalid-ca",
503564
},
504565
Data: map[string][]byte{
505-
"caFile": []byte("invalid"),
566+
"ca.crt": []byte("invalid"),
506567
},
507568
},
508569
beforeFunc: func(t *WithT, obj *helmv1.HelmRepository, rev digest.Digest) {
@@ -512,7 +573,7 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
512573
},
513574
wantErr: true,
514575
assertConditions: []metav1.Condition{
515-
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "cannot append certificate into certificate pool: invalid caFile"),
576+
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "cannot append certificate into certificate pool: invalid CA certificate"),
516577
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
517578
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
518579
},
@@ -769,10 +830,16 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
769830
if tt.url != "" {
770831
repoURL = tt.url
771832
}
772-
tlsConf, _, serr = getter.TLSClientConfigFromSecret(*secret, repoURL)
833+
tlsConf, _, serr = stls.KubeTLSClientConfigFromSecret(*secret, repoURL)
773834
if serr != nil {
774835
validSecret = false
775836
}
837+
if tlsConf == nil {
838+
tlsConf, _, serr = stls.TLSClientConfigFromSecret(*secret, repoURL)
839+
if serr != nil {
840+
validSecret = false
841+
}
842+
}
776843
newChartRepo, err = repository.NewChartRepository(obj.Spec.URL, "", testGetters, tlsConf, getterOpts...)
777844
} else {
778845
newChartRepo, err = repository.NewChartRepository(obj.Spec.URL, "", testGetters, nil)

0 commit comments

Comments
 (0)