Add .certSecretRef for Bucket API
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
This commit is contained in:
parent
b41c6532a7
commit
bc16734391
|
@ -83,6 +83,23 @@ type BucketSpec struct {
|
||||||
// +optional
|
// +optional
|
||||||
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
|
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.
|
// Interval at which the Bucket Endpoint is checked for updates.
|
||||||
// This interval is approximate and may be subject to jitter to ensure
|
// This interval is approximate and may be subject to jitter to ensure
|
||||||
// efficient use of resources.
|
// efficient use of resources.
|
||||||
|
|
|
@ -123,6 +123,11 @@ func (in *BucketSpec) DeepCopyInto(out *BucketSpec) {
|
||||||
*out = new(meta.LocalObjectReference)
|
*out = new(meta.LocalObjectReference)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.CertSecretRef != nil {
|
||||||
|
in, out := &in.CertSecretRef, &out.CertSecretRef
|
||||||
|
*out = new(meta.LocalObjectReference)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
out.Interval = in.Interval
|
out.Interval = in.Interval
|
||||||
if in.Timeout != nil {
|
if in.Timeout != nil {
|
||||||
in, out := &in.Timeout, &out.Timeout
|
in, out := &in.Timeout, &out.Timeout
|
||||||
|
|
|
@ -329,6 +329,32 @@ spec:
|
||||||
bucketName:
|
bucketName:
|
||||||
description: BucketName is the name of the object storage bucket.
|
description: BucketName is the name of the object storage bucket.
|
||||||
type: string
|
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:
|
endpoint:
|
||||||
description: Endpoint is the object storage address the BucketName
|
description: Endpoint is the object storage address the BucketName
|
||||||
is located at.
|
is located at.
|
||||||
|
|
|
@ -165,6 +165,32 @@ for the Bucket.</p>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
<code>interval</code><br>
|
||||||
<em>
|
<em>
|
||||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||||
|
@ -1489,6 +1515,32 @@ for the Bucket.</p>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
<code>interval</code><br>
|
||||||
<em>
|
<em>
|
||||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||||
|
|
|
@ -763,6 +763,67 @@ See [Provider](#provider) for more (provider specific) examples.
|
||||||
|
|
||||||
See [Provider](#provider) for more (provider specific) examples.
|
See [Provider](#provider) for more (provider specific) examples.
|
||||||
|
|
||||||
|
### Cert secret reference
|
||||||
|
|
||||||
|
`.spec.certSecretRef.name` is an optional field to specify a secret containing
|
||||||
|
TLS certificate data. The secret can contain the following keys:
|
||||||
|
|
||||||
|
* `tls.crt` and `tls.key`, to specify the client certificate and private key used
|
||||||
|
for TLS client authentication. These must be used in conjunction, i.e.
|
||||||
|
specifying one without the other will lead to an error.
|
||||||
|
* `ca.crt`, to specify the CA certificate used to verify the server, which is
|
||||||
|
required if the server is using a self-signed certificate.
|
||||||
|
|
||||||
|
If the server is using a self-signed certificate and has TLS client
|
||||||
|
authentication enabled, all three values are required.
|
||||||
|
|
||||||
|
The Secret should be of type `Opaque` or `kubernetes.io/tls`. All the files in
|
||||||
|
the Secret are expected to be [PEM-encoded][pem-encoding]. Assuming you have
|
||||||
|
three files; `client.key`, `client.crt` and `ca.crt` for the client private key,
|
||||||
|
client certificate and the CA certificate respectively, you can generate the
|
||||||
|
required Secret using the `flux create secret tls` command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
flux create secret tls minio-tls --tls-key-file=client.key --tls-crt-file=client.crt --ca-crt-file=ca.crt
|
||||||
|
```
|
||||||
|
|
||||||
|
If TLS client authentication is not required, you can generate the secret with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
flux create secret tls minio-tls --ca-crt-file=ca.crt
|
||||||
|
```
|
||||||
|
|
||||||
|
This API is only supported for the `generic` [provider](#provider).
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||||
|
kind: Bucket
|
||||||
|
metadata:
|
||||||
|
name: example
|
||||||
|
namespace: example
|
||||||
|
spec:
|
||||||
|
interval: 5m
|
||||||
|
bucketName: example
|
||||||
|
provider: generic
|
||||||
|
endpoint: minio.example.com
|
||||||
|
certSecretRef:
|
||||||
|
name: minio-tls
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: minio-tls
|
||||||
|
namespace: example
|
||||||
|
type: kubernetes.io/tls # or Opaque
|
||||||
|
stringData:
|
||||||
|
tls.crt: <PEM-encoded cert>
|
||||||
|
tls.key: <PEM-encoded key>
|
||||||
|
ca.crt: <PEM-encoded cert>
|
||||||
|
```
|
||||||
|
|
||||||
### Insecure
|
### Insecure
|
||||||
|
|
||||||
`.spec.insecure` is an optional field to allow connecting to an insecure (HTTP)
|
`.spec.insecure` is an optional field to allow connecting to an insecure (HTTP)
|
||||||
|
|
|
@ -18,6 +18,7 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
stdtls "crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
@ -57,6 +58,7 @@ import (
|
||||||
"github.com/fluxcd/source-controller/internal/index"
|
"github.com/fluxcd/source-controller/internal/index"
|
||||||
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
|
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
|
||||||
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
|
"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/azure"
|
||||||
"github.com/fluxcd/source-controller/pkg/gcp"
|
"github.com/fluxcd/source-controller/pkg/gcp"
|
||||||
"github.com/fluxcd/source-controller/pkg/minio"
|
"github.com/fluxcd/source-controller/pkg/minio"
|
||||||
|
@ -421,7 +423,7 @@ func (r *BucketReconciler) reconcileStorage(ctx context.Context, sp *patch.Seria
|
||||||
// the provider. If this fails, it records v1beta2.FetchFailedCondition=True on
|
// the provider. If this fails, it records v1beta2.FetchFailedCondition=True on
|
||||||
// the object and returns early.
|
// 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) {
|
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)
|
secret, err := r.getSecret(ctx, obj.Spec.SecretRef, obj.GetNamespace())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
|
||||||
|
@ -460,7 +462,13 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
|
||||||
return sreconcile.ResultEmpty, e
|
return sreconcile.ResultEmpty, e
|
||||||
}
|
}
|
||||||
if provider, err = minio.NewClient(obj, secret); err != nil {
|
tlsConfig, err := r.getTLSConfig(ctx, obj)
|
||||||
|
if err != nil {
|
||||||
|
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
||||||
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
|
||||||
|
return sreconcile.ResultEmpty, e
|
||||||
|
}
|
||||||
|
if provider, err = minio.NewClient(obj, secret, tlsConfig); err != nil {
|
||||||
e := serror.NewGeneric(err, "ClientError")
|
e := serror.NewGeneric(err, "ClientError")
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
|
||||||
return sreconcile.ResultEmpty, e
|
return sreconcile.ResultEmpty, e
|
||||||
|
@ -663,15 +671,15 @@ func (r *BucketReconciler) garbageCollect(ctx context.Context, obj *bucketv1.Buc
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBucketSecret attempts to fetch the Secret reference if specified on the
|
// getSecret attempts to fetch a Secret reference if specified. It returns any client error.
|
||||||
// obj. It returns any client error.
|
func (r *BucketReconciler) getSecret(ctx context.Context, secretRef *meta.LocalObjectReference,
|
||||||
func (r *BucketReconciler) getBucketSecret(ctx context.Context, obj *bucketv1.Bucket) (*corev1.Secret, error) {
|
namespace string) (*corev1.Secret, error) {
|
||||||
if obj.Spec.SecretRef == nil {
|
if secretRef == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
secretName := types.NamespacedName{
|
secretName := types.NamespacedName{
|
||||||
Namespace: obj.GetNamespace(),
|
Namespace: namespace,
|
||||||
Name: obj.Spec.SecretRef.Name,
|
Name: secretRef.Name,
|
||||||
}
|
}
|
||||||
secret := &corev1.Secret{}
|
secret := &corev1.Secret{}
|
||||||
if err := r.Get(ctx, secretName, secret); err != nil {
|
if err := r.Get(ctx, secretName, secret); err != nil {
|
||||||
|
@ -680,6 +688,21 @@ func (r *BucketReconciler) getBucketSecret(ctx context.Context, obj *bucketv1.Bu
|
||||||
return secret, nil
|
return secret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *BucketReconciler) getTLSConfig(ctx context.Context, obj *bucketv1.Bucket) (*stdtls.Config, error) {
|
||||||
|
certSecret, err := r.getSecret(ctx, obj.Spec.CertSecretRef, obj.GetNamespace())
|
||||||
|
if err != nil || certSecret == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig, _, err := tls.KubeTLSClientConfigFromSecret(*certSecret, obj.Spec.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create TLS config: %w", err)
|
||||||
|
}
|
||||||
|
if tlsConfig == nil {
|
||||||
|
return nil, fmt.Errorf("certificate secret does not contain any TLS configuration")
|
||||||
|
}
|
||||||
|
return tlsConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
// eventLogf records events, and logs at the same time.
|
// eventLogf records events, and logs at the same time.
|
||||||
//
|
//
|
||||||
// This log is different from the debug log in the EventRecorder, in the sense
|
// This log is different from the debug log in the EventRecorder, in the sense
|
||||||
|
|
|
@ -510,6 +510,47 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
|
||||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Observes non-existing certSecretRef",
|
||||||
|
bucketName: "dummy",
|
||||||
|
beforeFunc: func(obj *bucketv1.Bucket) {
|
||||||
|
obj.Spec.CertSecretRef = &meta.LocalObjectReference{
|
||||||
|
Name: "dummy",
|
||||||
|
}
|
||||||
|
conditions.MarkReconciling(obj, meta.ProgressingReason, "foo")
|
||||||
|
conditions.MarkUnknown(obj, meta.ReadyCondition, "foo", "bar")
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
assertIndex: index.NewDigester(),
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret '/dummy': secrets \"dummy\" not found"),
|
||||||
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||||
|
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Observes invalid certSecretRef",
|
||||||
|
bucketName: "dummy",
|
||||||
|
secret: &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeFunc: func(obj *bucketv1.Bucket) {
|
||||||
|
obj.Spec.CertSecretRef = &meta.LocalObjectReference{
|
||||||
|
Name: "dummy",
|
||||||
|
}
|
||||||
|
conditions.MarkReconciling(obj, meta.ProgressingReason, "foo")
|
||||||
|
conditions.MarkUnknown(obj, meta.ReadyCondition, "foo", "bar")
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
assertIndex: index.NewDigester(),
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||||
|
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||||
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "certificate secret does not contain any TLS configuration"),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Observes non-existing bucket name",
|
name: "Observes non-existing bucket name",
|
||||||
bucketName: "dummy",
|
bucketName: "dummy",
|
||||||
|
|
|
@ -18,6 +18,7 @@ package minio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ type MinioClient struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates a new Minio storage client.
|
// 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{
|
opt := minio.Options{
|
||||||
Region: bucket.Spec.Region,
|
Region: bucket.Spec.Region,
|
||||||
Secure: !bucket.Spec.Insecure,
|
Secure: !bucket.Spec.Insecure,
|
||||||
|
@ -60,6 +61,18 @@ func NewClient(bucket *sourcev1.Bucket, secret *corev1.Secret) (*MinioClient, er
|
||||||
opt.Creds = credentials.NewIAM("")
|
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)
|
||||||
|
}
|
||||||
|
transport.TLSClientConfig = tlsConfig.Clone()
|
||||||
|
opt.Transport = transport
|
||||||
|
}
|
||||||
|
|
||||||
client, err := minio.New(bucket.Spec.Endpoint, &opt)
|
client, err := minio.New(bucket.Spec.Endpoint, &opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -18,6 +18,9 @@ package minio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
@ -48,7 +51,7 @@ const (
|
||||||
var (
|
var (
|
||||||
// testMinioVersion is the version (image tag) of the Minio server image
|
// testMinioVersion is the version (image tag) of the Minio server image
|
||||||
// used to test against.
|
// used to test against.
|
||||||
testMinioVersion = "RELEASE.2022-12-12T19-27-27Z"
|
testMinioVersion = "RELEASE.2024-05-07T06-41-25Z"
|
||||||
// testMinioRootUser is the root user of the Minio server.
|
// testMinioRootUser is the root user of the Minio server.
|
||||||
testMinioRootUser = "fluxcd"
|
testMinioRootUser = "fluxcd"
|
||||||
// testMinioRootPassword is the root password of the Minio server.
|
// testMinioRootPassword is the root password of the Minio server.
|
||||||
|
@ -59,6 +62,8 @@ var (
|
||||||
// testMinioClient is the Minio client used to test against, it is set
|
// testMinioClient is the Minio client used to test against, it is set
|
||||||
// by TestMain after booting the Minio server.
|
// by TestMain after booting the Minio server.
|
||||||
testMinioClient *MinioClient
|
testMinioClient *MinioClient
|
||||||
|
// testTLSConfig is the TLS configuration used to connect to the Minio server.
|
||||||
|
testTLSConfig *tls.Config
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -115,6 +120,14 @@ func TestMain(m *testing.M) {
|
||||||
log.Fatalf("could not connect to docker: %s", err)
|
log.Fatalf("could not connect to docker: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load a private key and certificate from a self-signed CA for the Minio server and
|
||||||
|
// a client TLS configuration to connect to the Minio server.
|
||||||
|
var serverCert, serverKey string
|
||||||
|
serverCert, serverKey, testTLSConfig, err = loadServerCertAndClientTLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not load server cert and client TLS config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Pull the image, create a container based on it, and run it
|
// Pull the image, create a container based on it, and run it
|
||||||
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
|
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
|
||||||
Repository: "minio/minio",
|
Repository: "minio/minio",
|
||||||
|
@ -128,6 +141,10 @@ func TestMain(m *testing.M) {
|
||||||
"MINIO_ROOT_PASSWORD=" + testMinioRootPassword,
|
"MINIO_ROOT_PASSWORD=" + testMinioRootPassword,
|
||||||
},
|
},
|
||||||
Cmd: []string{"server", "/data", "--console-address", ":9001"},
|
Cmd: []string{"server", "/data", "--console-address", ":9001"},
|
||||||
|
Mounts: []string{
|
||||||
|
fmt.Sprintf("%s:/root/.minio/certs/public.crt", serverCert),
|
||||||
|
fmt.Sprintf("%s:/root/.minio/certs/private.key", serverKey),
|
||||||
|
},
|
||||||
}, func(config *docker.HostConfig) {
|
}, func(config *docker.HostConfig) {
|
||||||
config.AutoRemove = true
|
config.AutoRemove = true
|
||||||
})
|
})
|
||||||
|
@ -145,7 +162,7 @@ func TestMain(m *testing.M) {
|
||||||
testMinioAddress = fmt.Sprintf("127.0.0.1:%v", resource.GetPort("9000/tcp"))
|
testMinioAddress = fmt.Sprintf("127.0.0.1:%v", resource.GetPort("9000/tcp"))
|
||||||
|
|
||||||
// Construct a Minio client using the address of the Minio server.
|
// Construct a Minio client using the address of the Minio server.
|
||||||
testMinioClient, err = NewClient(bucketStub(bucket, testMinioAddress), secret.DeepCopy())
|
testMinioClient, err = NewClient(bucketStub(bucket, testMinioAddress), secret.DeepCopy(), testTLSConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("cannot create Minio client: %s", err)
|
log.Fatalf("cannot create Minio client: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -178,19 +195,19 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNewClient(t *testing.T) {
|
||||||
minioClient, err := NewClient(bucketStub(bucket, testMinioAddress), secret.DeepCopy())
|
minioClient, err := NewClient(bucketStub(bucket, testMinioAddress), secret.DeepCopy(), testTLSConfig)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, minioClient != nil)
|
assert.Assert(t, minioClient != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClientEmptySecret(t *testing.T) {
|
func TestNewClientEmptySecret(t *testing.T) {
|
||||||
minioClient, err := NewClient(bucketStub(bucket, testMinioAddress), emptySecret.DeepCopy())
|
minioClient, err := NewClient(bucketStub(bucket, testMinioAddress), emptySecret.DeepCopy(), testTLSConfig)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, minioClient != nil)
|
assert.Assert(t, minioClient != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClientAwsProvider(t *testing.T) {
|
func TestNewClientAwsProvider(t *testing.T) {
|
||||||
minioClient, err := NewClient(bucketStub(bucketAwsProvider, testMinioAddress), nil)
|
minioClient, err := NewClient(bucketStub(bucketAwsProvider, testMinioAddress), nil, nil)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, minioClient != nil)
|
assert.Assert(t, minioClient != nil)
|
||||||
}
|
}
|
||||||
|
@ -295,7 +312,7 @@ func TestValidateSecret(t *testing.T) {
|
||||||
func bucketStub(bucket sourcev1.Bucket, endpoint string) *sourcev1.Bucket {
|
func bucketStub(bucket sourcev1.Bucket, endpoint string) *sourcev1.Bucket {
|
||||||
b := bucket.DeepCopy()
|
b := bucket.DeepCopy()
|
||||||
b.Spec.Endpoint = endpoint
|
b.Spec.Endpoint = endpoint
|
||||||
b.Spec.Insecure = true
|
b.Spec.Insecure = false
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,3 +368,37 @@ func getObjectFile() string {
|
||||||
timeout: 30s
|
timeout: 30s
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadServerCertAndClientTLSConfig() (serverCert string, serverKey string, clientConf *tls.Config, err error) {
|
||||||
|
const certsDir = "../../internal/controller/testdata/certs"
|
||||||
|
clientConf = &tls.Config{}
|
||||||
|
|
||||||
|
serverCert, err = filepath.Abs(filepath.Join(certsDir, "server.pem"))
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, fmt.Errorf("failed to get server cert path: %w", err)
|
||||||
|
}
|
||||||
|
serverKey, err = filepath.Abs(filepath.Join(certsDir, "server-key.pem"))
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, fmt.Errorf("failed to get server key path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := os.ReadFile(filepath.Join(certsDir, "ca.pem"))
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, fmt.Errorf("failed to load CA: %w", err)
|
||||||
|
}
|
||||||
|
caPool := x509.NewCertPool()
|
||||||
|
if !caPool.AppendCertsFromPEM(b) {
|
||||||
|
return "", "", nil, errors.New("failed to append CA to pool")
|
||||||
|
}
|
||||||
|
clientConf.RootCAs = caPool
|
||||||
|
|
||||||
|
clientCert := filepath.Join(certsDir, "client.pem")
|
||||||
|
clientKey := filepath.Join(certsDir, "client-key.pem")
|
||||||
|
client, err := tls.LoadX509KeyPair(clientCert, clientKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, fmt.Errorf("failed to load client cert and key: %w", err)
|
||||||
|
}
|
||||||
|
clientConf.Certificates = []tls.Certificate{client}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue