Skip to content

Commit 01b4d8c

Browse files
committed
Add .certSecretRef for Bucket API
Signed-off-by: Matheus Pimenta <[email protected]>
1 parent b41c653 commit 01b4d8c

File tree

8 files changed

+259
-15
lines changed

8 files changed

+259
-15
lines changed

api/v1beta2/bucket_types.go

+17
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,23 @@ type BucketSpec struct {
8383
// +optional
8484
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
8585

86+
// CertSecretRef can be given the name of a Secret containing
87+
// either or both of
88+
//
89+
// - a PEM-encoded client certificate (`tls.crt`) and private
90+
// key (`tls.key`);
91+
// - a PEM-encoded CA certificate (`ca.crt`)
92+
//
93+
// and whichever are supplied, will be used for connecting to the
94+
// bucket. The client cert and key are useful if you are
95+
// authenticating with a certificate; the CA cert is useful if
96+
// you are using a self-signed server certificate. The Secret must
97+
// be of type `Opaque` or `kubernetes.io/tls`.
98+
//
99+
// This field is only supported for the `generic` provider.
100+
// +optional
101+
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
102+
86103
// Interval at which the Bucket Endpoint is checked for updates.
87104
// This interval is approximate and may be subject to jitter to ensure
88105
// efficient use of resources.

api/v1beta2/zz_generated.deepcopy.go

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

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

+26
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,32 @@ spec:
329329
bucketName:
330330
description: BucketName is the name of the object storage bucket.
331331
type: string
332+
certSecretRef:
333+
description: |-
334+
CertSecretRef can be given the name of a Secret containing
335+
either or both of
336+
337+
338+
- a PEM-encoded client certificate (`tls.crt`) and private
339+
key (`tls.key`);
340+
- a PEM-encoded CA certificate (`ca.crt`)
341+
342+
343+
and whichever are supplied, will be used for connecting to the
344+
bucket. The client cert and key are useful if you are
345+
authenticating with a certificate; the CA cert is useful if
346+
you are using a self-signed server certificate. The Secret must
347+
be of type `Opaque` or `kubernetes.io/tls`.
348+
349+
350+
This field is only supported for the `generic` provider.
351+
properties:
352+
name:
353+
description: Name of the referent.
354+
type: string
355+
required:
356+
- name
357+
type: object
332358
endpoint:
333359
description: Endpoint is the object storage address the BucketName
334360
is located at.

docs/api/v1beta2/source.md

+52
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,32 @@ for the Bucket.</p>
165165
</tr>
166166
<tr>
167167
<td>
168+
<code>certSecretRef</code><br>
169+
<em>
170+
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
171+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
172+
</a>
173+
</em>
174+
</td>
175+
<td>
176+
<em>(Optional)</em>
177+
<p>CertSecretRef can be given the name of a Secret containing
178+
either or both of</p>
179+
<ul>
180+
<li>a PEM-encoded client certificate (<code>tls.crt</code>) and private
181+
key (<code>tls.key</code>);</li>
182+
<li>a PEM-encoded CA certificate (<code>ca.crt</code>)</li>
183+
</ul>
184+
<p>and whichever are supplied, will be used for connecting to the
185+
bucket. The client cert and key are useful if you are
186+
authenticating with a certificate; the CA cert is useful if
187+
you are using a self-signed server certificate. The Secret must
188+
be of type <code>Opaque</code> or <code>kubernetes.io/tls</code>.</p>
189+
<p>This field is only supported for the <code>generic</code> provider.</p>
190+
</td>
191+
</tr>
192+
<tr>
193+
<td>
168194
<code>interval</code><br>
169195
<em>
170196
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
@@ -1489,6 +1515,32 @@ for the Bucket.</p>
14891515
</tr>
14901516
<tr>
14911517
<td>
1518+
<code>certSecretRef</code><br>
1519+
<em>
1520+
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
1521+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
1522+
</a>
1523+
</em>
1524+
</td>
1525+
<td>
1526+
<em>(Optional)</em>
1527+
<p>CertSecretRef can be given the name of a Secret containing
1528+
either or both of</p>
1529+
<ul>
1530+
<li>a PEM-encoded client certificate (<code>tls.crt</code>) and private
1531+
key (<code>tls.key</code>);</li>
1532+
<li>a PEM-encoded CA certificate (<code>ca.crt</code>)</li>
1533+
</ul>
1534+
<p>and whichever are supplied, will be used for connecting to the
1535+
bucket. The client cert and key are useful if you are
1536+
authenticating with a certificate; the CA cert is useful if
1537+
you are using a self-signed server certificate. The Secret must
1538+
be of type <code>Opaque</code> or <code>kubernetes.io/tls</code>.</p>
1539+
<p>This field is only supported for the <code>generic</code> provider.</p>
1540+
</td>
1541+
</tr>
1542+
<tr>
1543+
<td>
14921544
<code>interval</code><br>
14931545
<em>
14941546
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">

docs/spec/v1beta2/buckets.md

+61
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,67 @@ See [Provider](#provider) for more (provider specific) examples.
763763

764764
See [Provider](#provider) for more (provider specific) examples.
765765

766+
### Cert secret reference
767+
768+
`.spec.certSecretRef.name` is an optional field to specify a secret containing
769+
TLS certificate data. The secret can contain the following keys:
770+
771+
* `tls.crt` and `tls.key`, to specify the client certificate and private key used
772+
for TLS client authentication. These must be used in conjunction, i.e.
773+
specifying one without the other will lead to an error.
774+
* `ca.crt`, to specify the CA certificate used to verify the server, which is
775+
required if the server is using a self-signed certificate.
776+
777+
If the server is using a self-signed certificate and has TLS client
778+
authentication enabled, all three values are required.
779+
780+
The Secret should be of type `Opaque` or `kubernetes.io/tls`. All the files in
781+
the Secret are expected to be [PEM-encoded][pem-encoding]. Assuming you have
782+
three files; `client.key`, `client.crt` and `ca.crt` for the client private key,
783+
client certificate and the CA certificate respectively, you can generate the
784+
required Secret using the `flux create secret tls` command:
785+
786+
```sh
787+
flux create secret tls minio-tls --tls-key-file=client.key --tls-crt-file=client.crt --ca-crt-file=ca.crt
788+
```
789+
790+
If TLS client authentication is not required, you can generate the secret with:
791+
792+
```sh
793+
flux create secret tls minio-tls --ca-crt-file=ca.crt
794+
```
795+
796+
This API is only supported for the `generic` [provider](#provider).
797+
798+
Example usage:
799+
800+
```yaml
801+
---
802+
apiVersion: source.toolkit.fluxcd.io/v1beta2
803+
kind: Bucket
804+
metadata:
805+
name: example
806+
namespace: example
807+
spec:
808+
interval: 5m
809+
bucketName: example
810+
provider: generic
811+
endpoint: minio.example.com
812+
certSecretRef:
813+
name: minio-tls
814+
---
815+
apiVersion: v1
816+
kind: Secret
817+
metadata:
818+
name: minio-tls
819+
namespace: example
820+
type: kubernetes.io/tls # or Opaque
821+
stringData:
822+
tls.crt: <PEM-encoded cert>
823+
tls.key: <PEM-encoded key>
824+
ca.crt: <PEM-encoded cert>
825+
```
826+
766827
### Insecure
767828

768829
`.spec.insecure` is an optional field to allow connecting to an insecure (HTTP)

internal/controller/bucket_controller.go

+27-8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controller
1818

1919
import (
2020
"context"
21+
stdtls "crypto/tls"
2122
"errors"
2223
"fmt"
2324
"os"
@@ -57,6 +58,7 @@ import (
5758
"github.com/fluxcd/source-controller/internal/index"
5859
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
5960
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
61+
"github.com/fluxcd/source-controller/internal/tls"
6062
"github.com/fluxcd/source-controller/pkg/azure"
6163
"github.com/fluxcd/source-controller/pkg/gcp"
6264
"github.com/fluxcd/source-controller/pkg/minio"
@@ -421,7 +423,9 @@ func (r *BucketReconciler) reconcileStorage(ctx context.Context, sp *patch.Seria
421423
// the provider. If this fails, it records v1beta2.FetchFailedCondition=True on
422424
// the object and returns early.
423425
func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.SerialPatcher, obj *bucketv1.Bucket, index *index.Digester, dir string) (sreconcile.Result, error) {
424-
secret, err := r.getBucketSecret(ctx, obj)
426+
objNamespace := obj.GetNamespace()
427+
428+
secret, err := r.getSecret(ctx, obj.Spec.SecretRef, objNamespace)
425429
if err != nil {
426430
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
427431
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
@@ -460,7 +464,13 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
460464
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
461465
return sreconcile.ResultEmpty, e
462466
}
463-
if provider, err = minio.NewClient(obj, secret); err != nil {
467+
tlsConfig, err := r.getTLSConfig(ctx, obj)
468+
if err != nil {
469+
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
470+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
471+
return sreconcile.ResultEmpty, e
472+
}
473+
if provider, err = minio.NewClient(obj, secret, tlsConfig); err != nil {
464474
e := serror.NewGeneric(err, "ClientError")
465475
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
466476
return sreconcile.ResultEmpty, e
@@ -663,15 +673,15 @@ func (r *BucketReconciler) garbageCollect(ctx context.Context, obj *bucketv1.Buc
663673
return nil
664674
}
665675

666-
// getBucketSecret attempts to fetch the Secret reference if specified on the
667-
// obj. It returns any client error.
668-
func (r *BucketReconciler) getBucketSecret(ctx context.Context, obj *bucketv1.Bucket) (*corev1.Secret, error) {
669-
if obj.Spec.SecretRef == nil {
676+
// getSecret attempts to fetch a Secret reference if specified. It returns any client error.
677+
func (r *BucketReconciler) getSecret(ctx context.Context, secretRef *meta.LocalObjectReference,
678+
namespace string) (*corev1.Secret, error) {
679+
if secretRef == nil {
670680
return nil, nil
671681
}
672682
secretName := types.NamespacedName{
673-
Namespace: obj.GetNamespace(),
674-
Name: obj.Spec.SecretRef.Name,
683+
Namespace: namespace,
684+
Name: secretRef.Name,
675685
}
676686
secret := &corev1.Secret{}
677687
if err := r.Get(ctx, secretName, secret); err != nil {
@@ -680,6 +690,15 @@ func (r *BucketReconciler) getBucketSecret(ctx context.Context, obj *bucketv1.Bu
680690
return secret, nil
681691
}
682692

693+
func (r *BucketReconciler) getTLSConfig(ctx context.Context, obj *bucketv1.Bucket) (*stdtls.Config, error) {
694+
certSecret, err := r.getSecret(ctx, obj.Spec.CertSecretRef, obj.GetNamespace())
695+
if err != nil || certSecret == nil {
696+
return nil, err
697+
}
698+
tlsConfig, _, err := tls.KubeTLSClientConfigFromSecret(*certSecret, obj.Spec.Endpoint)
699+
return tlsConfig, err
700+
}
701+
683702
// eventLogf records events, and logs at the same time.
684703
//
685704
// This log is different from the debug log in the EventRecorder, in the sense

pkg/minio/minio.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package minio
1818

1919
import (
2020
"context"
21+
"crypto/tls"
2122
"errors"
2223
"fmt"
2324

@@ -36,7 +37,7 @@ type MinioClient struct {
3637
}
3738

3839
// NewClient creates a new Minio storage client.
39-
func NewClient(bucket *sourcev1.Bucket, secret *corev1.Secret) (*MinioClient, error) {
40+
func NewClient(bucket *sourcev1.Bucket, secret *corev1.Secret, tlsConfig *tls.Config) (*MinioClient, error) {
4041
opt := minio.Options{
4142
Region: bucket.Spec.Region,
4243
Secure: !bucket.Spec.Insecure,
@@ -60,6 +61,18 @@ func NewClient(bucket *sourcev1.Bucket, secret *corev1.Secret) (*MinioClient, er
6061
opt.Creds = credentials.NewIAM("")
6162
}
6263

64+
if opt.Secure && tlsConfig != nil {
65+
// Use the default minio transport, but override the TLS config.
66+
secure := false // true causes the TLS config to be defined internally, but here we have our own so we just pass false.
67+
transport, err := minio.DefaultTransport(secure)
68+
if err != nil {
69+
// The error returned here is always nil, but we keep the check for future compatibility.
70+
return nil, fmt.Errorf("failed to create default minio transport: %w", err)
71+
}
72+
transport.TLSClientConfig = tlsConfig.Clone()
73+
opt.Transport = transport
74+
}
75+
6376
client, err := minio.New(bucket.Spec.Endpoint, &opt)
6477
if err != nil {
6578
return nil, err

0 commit comments

Comments
 (0)