Skip to content

Commit

Permalink
Add .certSecretRef for Bucket API
Browse files Browse the repository at this point in the history
Signed-off-by: Matheus Pimenta <[email protected]>
  • Loading branch information
matheuscscp committed May 9, 2024
1 parent b41c653 commit 17c9be3
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 17 deletions.
5 changes: 5 additions & 0 deletions api/v1/condition_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ const (
// required fields, or the provided credentials do not match.
AuthenticationFailedReason string = "AuthenticationFailed"

// CertificateFailedReason signals that a problem occurred while
// fetching the certificate Secret, or the expected fields are missing
// or invalid.
CertificateFailedReason string = "CertificateFailed"

// VerificationError signals that the Source's verification
// check failed.
VerificationError string = "VerificationError"
Expand Down
17 changes: 17 additions & 0 deletions api/v1beta2/bucket_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,23 @@ type BucketSpec struct {
// +optional
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`

// CertSecretRef can be given the name of a Secret containing
// either or both of
//
// - a PEM-encoded client certificate (`tls.crt`) and private
// key (`tls.key`);
// - a PEM-encoded CA certificate (`ca.crt`)
//
// and whichever are supplied, will be used for connecting to the
// bucket. The client cert and key are useful if you are
// authenticating with a certificate; the CA cert is useful if
// you are using a self-signed server certificate. The Secret must
// be of type `Opaque` or `kubernetes.io/tls`.
//
// This field is only supported for the `generic` provider.
// +optional
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`

// Interval at which the Bucket Endpoint is checked for updates.
// This interval is approximate and may be subject to jitter to ensure
// efficient use of resources.
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,32 @@ spec:
bucketName:
description: BucketName is the name of the object storage bucket.
type: string
certSecretRef:
description: |-
CertSecretRef can be given the name of a Secret containing
either or both of
- a PEM-encoded client certificate (`tls.crt`) and private
key (`tls.key`);
- a PEM-encoded CA certificate (`ca.crt`)
and whichever are supplied, will be used for connecting to the
bucket. The client cert and key are useful if you are
authenticating with a certificate; the CA cert is useful if
you are using a self-signed server certificate. The Secret must
be of type `Opaque` or `kubernetes.io/tls`.
This field is only supported for the `generic` provider.
properties:
name:
description: Name of the referent.
type: string
required:
- name
type: object
endpoint:
description: Endpoint is the object storage address the BucketName
is located at.
Expand Down
52 changes: 52 additions & 0 deletions docs/api/v1beta2/source.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,32 @@ for the Bucket.</p>
</tr>
<tr>
<td>
<code>certSecretRef</code><br>
<em>
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>CertSecretRef can be given the name of a Secret containing
either or both of</p>
<ul>
<li>a PEM-encoded client certificate (<code>tls.crt</code>) and private
key (<code>tls.key</code>);</li>
<li>a PEM-encoded CA certificate (<code>ca.crt</code>)</li>
</ul>
<p>and whichever are supplied, will be used for connecting to the
bucket. The client cert and key are useful if you are
authenticating with a certificate; the CA cert is useful if
you are using a self-signed server certificate. The Secret must
be of type <code>Opaque</code> or <code>kubernetes.io/tls</code>.</p>
<p>This field is only supported for the <code>generic</code> provider.</p>
</td>
</tr>
<tr>
<td>
<code>interval</code><br>
<em>
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
Expand Down Expand Up @@ -1489,6 +1515,32 @@ for the Bucket.</p>
</tr>
<tr>
<td>
<code>certSecretRef</code><br>
<em>
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>CertSecretRef can be given the name of a Secret containing
either or both of</p>
<ul>
<li>a PEM-encoded client certificate (<code>tls.crt</code>) and private
key (<code>tls.key</code>);</li>
<li>a PEM-encoded CA certificate (<code>ca.crt</code>)</li>
</ul>
<p>and whichever are supplied, will be used for connecting to the
bucket. The client cert and key are useful if you are
authenticating with a certificate; the CA cert is useful if
you are using a self-signed server certificate. The Secret must
be of type <code>Opaque</code> or <code>kubernetes.io/tls</code>.</p>
<p>This field is only supported for the <code>generic</code> provider.</p>
</td>
</tr>
<tr>
<td>
<code>interval</code><br>
<em>
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
Expand Down
37 changes: 29 additions & 8 deletions internal/controller/bucket_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package controller

import (
"context"
stdtls "crypto/tls"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -57,6 +58,7 @@ import (
"github.com/fluxcd/source-controller/internal/index"
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
"github.com/fluxcd/source-controller/internal/tls"
"github.com/fluxcd/source-controller/pkg/azure"
"github.com/fluxcd/source-controller/pkg/gcp"
"github.com/fluxcd/source-controller/pkg/minio"
Expand Down Expand Up @@ -421,14 +423,33 @@ func (r *BucketReconciler) reconcileStorage(ctx context.Context, sp *patch.Seria
// the provider. If this fails, it records v1beta2.FetchFailedCondition=True on
// the object and returns early.
func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.SerialPatcher, obj *bucketv1.Bucket, index *index.Digester, dir string) (sreconcile.Result, error) {
secret, err := r.getBucketSecret(ctx, obj)
objNamespace := obj.GetNamespace()

secret, err := r.getSecret(ctx, obj.Spec.SecretRef, objNamespace)
if err != nil {
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
// Return error as the world as observed may change
return sreconcile.ResultEmpty, e
}

// Fetch and validate certificate secret if specified on the object.
certSecret, err := r.getSecret(ctx, obj.Spec.CertSecretRef, objNamespace)
if err != nil {
e := serror.NewGeneric(err, sourcev1.CertificateFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
var tlsConfig *stdtls.Config
if certSecret != nil {
tlsConfig, _, err = tls.KubeTLSClientConfigFromSecret(*certSecret, obj.Spec.Endpoint)
if err != nil {
e := serror.NewGeneric(err, sourcev1.CertificateFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
}

// Construct provider client
var provider BucketProvider
switch obj.Spec.Provider {
Expand Down Expand Up @@ -460,7 +481,7 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
if provider, err = minio.NewClient(obj, secret); err != nil {
if provider, err = minio.NewClient(obj, secret, tlsConfig); err != nil {
e := serror.NewGeneric(err, "ClientError")
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
Expand Down Expand Up @@ -663,15 +684,15 @@ func (r *BucketReconciler) garbageCollect(ctx context.Context, obj *bucketv1.Buc
return nil
}

// getBucketSecret attempts to fetch the Secret reference if specified on the
// obj. It returns any client error.
func (r *BucketReconciler) getBucketSecret(ctx context.Context, obj *bucketv1.Bucket) (*corev1.Secret, error) {
if obj.Spec.SecretRef == nil {
// getSecret attempts to fetch a Secret reference if specified. It returns any client error.
func (r *BucketReconciler) getSecret(ctx context.Context, secretRef *meta.LocalObjectReference,
namespace string) (*corev1.Secret, error) {
if secretRef == nil {
return nil, nil
}
secretName := types.NamespacedName{
Namespace: obj.GetNamespace(),
Name: obj.Spec.SecretRef.Name,
Namespace: namespace,
Name: secretRef.Name,
}
secret := &corev1.Secret{}
if err := r.Get(ctx, secretName, secret); err != nil {
Expand Down
20 changes: 19 additions & 1 deletion pkg/minio/minio.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package minio

import (
"context"
"crypto/tls"
"errors"
"fmt"

Expand All @@ -36,7 +37,7 @@ type MinioClient struct {
}

// NewClient creates a new Minio storage client.
func NewClient(bucket *sourcev1.Bucket, secret *corev1.Secret) (*MinioClient, error) {
func NewClient(bucket *sourcev1.Bucket, secret *corev1.Secret, tlsConfig *tls.Config) (*MinioClient, error) {
opt := minio.Options{
Region: bucket.Spec.Region,
Secure: !bucket.Spec.Insecure,
Expand All @@ -60,6 +61,23 @@ func NewClient(bucket *sourcev1.Bucket, secret *corev1.Secret) (*MinioClient, er
opt.Creds = credentials.NewIAM("")
}

if opt.Secure && tlsConfig != nil {
// Use the default minio transport, but override the TLS config.
secure := false // true causes the TLS config to be defined internally, but here we have our own so we just pass false.
transport, err := minio.DefaultTransport(secure)
if err != nil {
// The error returned here is always nil, but we keep the check for future compatibility.
return nil, fmt.Errorf("failed to create default minio transport: %w", err)
}

// Use the same min version used by the minio.DefaultTransport() library.
minioTLSConfig := tlsConfig.Clone()
minioTLSConfig.MinVersion = tls.VersionTLS12
transport.TLSClientConfig = minioTLSConfig

opt.Transport = transport
}

client, err := minio.New(bucket.Spec.Endpoint, &opt)
if err != nil {
return nil, err
Expand Down
Loading

0 comments on commit 17c9be3

Please sign in to comment.