Merge pull request #1852 from cappyzawa/feat/bucket-controller-runtime-secrets-migration
Migrate Bucket controller to runtime/secrets
This commit is contained in:
commit
c2b572bae0
|
|
@ -18,7 +18,7 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
stdtls "crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
@ -50,6 +50,7 @@ import (
|
||||||
"github.com/fluxcd/pkg/runtime/patch"
|
"github.com/fluxcd/pkg/runtime/patch"
|
||||||
"github.com/fluxcd/pkg/runtime/predicates"
|
"github.com/fluxcd/pkg/runtime/predicates"
|
||||||
rreconcile "github.com/fluxcd/pkg/runtime/reconcile"
|
rreconcile "github.com/fluxcd/pkg/runtime/reconcile"
|
||||||
|
"github.com/fluxcd/pkg/runtime/secrets"
|
||||||
"github.com/fluxcd/pkg/sourceignore"
|
"github.com/fluxcd/pkg/sourceignore"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||||
|
|
@ -58,7 +59,6 @@ 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"
|
||||||
|
|
@ -155,6 +155,15 @@ type BucketProvider interface {
|
||||||
Close(context.Context)
|
Close(context.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bucketCredentials contains all credentials and configuration needed for bucket providers.
|
||||||
|
type bucketCredentials struct {
|
||||||
|
secret *corev1.Secret
|
||||||
|
proxyURL *url.URL
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
stsSecret *corev1.Secret
|
||||||
|
stsTLSConfig *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
// bucketReconcileFunc is the function type for all the v1.Bucket
|
// bucketReconcileFunc is the function type for all the v1.Bucket
|
||||||
// (sub)reconcile functions. The type implementations are grouped and
|
// (sub)reconcile functions. The type implementations are grouped and
|
||||||
// executed serially to perform the complete reconcile of the object.
|
// executed serially to perform the complete reconcile of the object.
|
||||||
|
|
@ -421,147 +430,39 @@ func (r *BucketReconciler) reconcileStorage(ctx context.Context, sp *patch.Seria
|
||||||
// the provider. If this fails, it records v1.FetchFailedCondition=True on
|
// the provider. If this fails, it records v1.FetchFailedCondition=True on
|
||||||
// the object and returns early.
|
// the object and returns early.
|
||||||
func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.SerialPatcher, obj *sourcev1.Bucket, index *index.Digester, dir string) (sreconcile.Result, error) {
|
func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.SerialPatcher, obj *sourcev1.Bucket, index *index.Digester, dir string) (sreconcile.Result, error) {
|
||||||
secret, err := r.getSecret(ctx, obj.Spec.SecretRef, obj.GetNamespace())
|
creds, err := r.setupCredentials(ctx, obj)
|
||||||
if err != nil {
|
|
||||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
|
||||||
// Return error as the world as observed may change
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
proxyURL, err := r.getProxyURL(ctx, obj)
|
|
||||||
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, "%s", e)
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
||||||
return sreconcile.ResultEmpty, e
|
return sreconcile.ResultEmpty, e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct provider client
|
provider, err := r.createBucketProvider(ctx, obj, creds)
|
||||||
var provider BucketProvider
|
|
||||||
switch obj.Spec.Provider {
|
|
||||||
case sourcev1.BucketProviderGoogle:
|
|
||||||
if err = gcp.ValidateSecret(secret); err != nil {
|
|
||||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
var opts []gcp.Option
|
|
||||||
if secret != nil {
|
|
||||||
opts = append(opts, gcp.WithSecret(secret))
|
|
||||||
}
|
|
||||||
if proxyURL != nil {
|
|
||||||
opts = append(opts, gcp.WithProxyURL(proxyURL))
|
|
||||||
}
|
|
||||||
if provider, err = gcp.NewClient(ctx, opts...); err != nil {
|
|
||||||
e := serror.NewGeneric(err, "ClientError")
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
case sourcev1.BucketProviderAzure:
|
|
||||||
if err = azure.ValidateSecret(secret); err != nil {
|
|
||||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
var opts []azure.Option
|
|
||||||
if secret != nil {
|
|
||||||
opts = append(opts, azure.WithSecret(secret))
|
|
||||||
}
|
|
||||||
if proxyURL != nil {
|
|
||||||
opts = append(opts, azure.WithProxyURL(proxyURL))
|
|
||||||
}
|
|
||||||
if provider, err = azure.NewClient(obj, opts...); err != nil {
|
|
||||||
e := serror.NewGeneric(err, "ClientError")
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if err = minio.ValidateSecret(secret); err != nil {
|
|
||||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
tlsConfig, err := r.getTLSConfig(ctx, obj.Spec.CertSecretRef, obj.GetNamespace(), obj.Spec.Endpoint)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
var stallingErr *serror.Stalling
|
||||||
|
var genericErr *serror.Generic
|
||||||
|
if errors.As(err, &stallingErr) {
|
||||||
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, stallingErr.Reason, "%s", stallingErr)
|
||||||
|
return sreconcile.ResultEmpty, stallingErr
|
||||||
|
} else if errors.As(err, &genericErr) {
|
||||||
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, genericErr.Reason, "%s", genericErr)
|
||||||
|
return sreconcile.ResultEmpty, genericErr
|
||||||
|
} else {
|
||||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
||||||
return sreconcile.ResultEmpty, e
|
return sreconcile.ResultEmpty, e
|
||||||
}
|
}
|
||||||
stsSecret, err := r.getSTSSecret(ctx, obj)
|
}
|
||||||
|
changed, err := r.syncBucketArtifacts(ctx, provider, obj, index, dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
stsTLSConfig, err := r.getSTSTLSConfig(ctx, obj)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("failed to get STS TLS config: %w", err)
|
|
||||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
if sts := obj.Spec.STS; sts != nil {
|
|
||||||
if err := minio.ValidateSTSProvider(obj.Spec.Provider, sts); err != nil {
|
|
||||||
e := serror.NewStalling(err, sourcev1.InvalidSTSConfigurationReason)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
if _, err := url.Parse(sts.Endpoint); err != nil {
|
|
||||||
err := fmt.Errorf("failed to parse STS endpoint '%s': %w", sts.Endpoint, err)
|
|
||||||
e := serror.NewStalling(err, sourcev1.URLInvalidReason)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
if err := minio.ValidateSTSSecret(sts.Provider, stsSecret); err != nil {
|
|
||||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var opts []minio.Option
|
|
||||||
if secret != nil {
|
|
||||||
opts = append(opts, minio.WithSecret(secret))
|
|
||||||
}
|
|
||||||
if tlsConfig != nil {
|
|
||||||
opts = append(opts, minio.WithTLSConfig(tlsConfig))
|
|
||||||
}
|
|
||||||
if proxyURL != nil {
|
|
||||||
opts = append(opts, minio.WithProxyURL(proxyURL))
|
|
||||||
}
|
|
||||||
if stsSecret != nil {
|
|
||||||
opts = append(opts, minio.WithSTSSecret(stsSecret))
|
|
||||||
}
|
|
||||||
if stsTLSConfig != nil {
|
|
||||||
opts = append(opts, minio.WithSTSTLSConfig(stsTLSConfig))
|
|
||||||
}
|
|
||||||
if provider, err = minio.NewClient(obj, opts...); err != nil {
|
|
||||||
e := serror.NewGeneric(err, "ClientError")
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch etag index
|
|
||||||
if err = fetchEtagIndex(ctx, provider, obj, index, dir); err != nil {
|
|
||||||
e := serror.NewGeneric(err, sourcev1.BucketOperationFailedReason)
|
e := serror.NewGeneric(err, sourcev1.BucketOperationFailedReason)
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
||||||
return sreconcile.ResultEmpty, e
|
return sreconcile.ResultEmpty, e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if index has changed compared to current Artifact revision.
|
// Update artifact status if changes were detected
|
||||||
var changed bool
|
if changed {
|
||||||
if artifact := obj.Status.Artifact; artifact != nil && artifact.Revision != "" {
|
|
||||||
curRev := digest.Digest(artifact.Revision)
|
|
||||||
changed = curRev.Validate() != nil || curRev != index.Digest(curRev.Algorithm())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch the bucket objects if required to.
|
|
||||||
if artifact := obj.GetArtifact(); artifact == nil || changed {
|
|
||||||
// Mark observations about the revision on the object
|
|
||||||
defer func() {
|
|
||||||
// As fetchIndexFiles can make last-minute modifications to the etag
|
|
||||||
// index, we need to re-calculate the revision at the end
|
|
||||||
revision := index.Digest(intdigest.Canonical)
|
revision := index.Digest(intdigest.Canonical)
|
||||||
|
|
||||||
message := fmt.Sprintf("new upstream revision '%s'", revision)
|
message := fmt.Sprintf("new upstream revision '%s'", revision)
|
||||||
if obj.GetArtifact() != nil {
|
if obj.GetArtifact() != nil {
|
||||||
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "NewRevision", "%s", message)
|
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "NewRevision", "%s", message)
|
||||||
|
|
@ -569,14 +470,7 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
|
||||||
rreconcile.ProgressiveStatus(true, obj, meta.ProgressingReason, "building artifact: %s", message)
|
rreconcile.ProgressiveStatus(true, obj, meta.ProgressingReason, "building artifact: %s", message)
|
||||||
if err := sp.Patch(ctx, obj, r.patchOptions...); err != nil {
|
if err := sp.Patch(ctx, obj, r.patchOptions...); err != nil {
|
||||||
ctrl.LoggerFrom(ctx).Error(err, "failed to patch")
|
ctrl.LoggerFrom(ctx).Error(err, "failed to patch")
|
||||||
return
|
return sreconcile.ResultEmpty, err
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = fetchIndexFiles(ctx, provider, obj, index, dir); err != nil {
|
|
||||||
e := serror.NewGeneric(err, sourcev1.BucketOperationFailedReason)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -736,85 +630,6 @@ func (r *BucketReconciler) garbageCollect(ctx context.Context, obj *sourcev1.Buc
|
||||||
return nil
|
return 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: namespace,
|
|
||||||
Name: secretRef.Name,
|
|
||||||
}
|
|
||||||
secret := &corev1.Secret{}
|
|
||||||
if err := r.Get(ctx, secretName, secret); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get secret '%s': %w", secretName.String(), err)
|
|
||||||
}
|
|
||||||
return secret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTLSConfig attempts to fetch a TLS configuration from the given
|
|
||||||
// Secret reference, namespace and endpoint.
|
|
||||||
func (r *BucketReconciler) getTLSConfig(ctx context.Context,
|
|
||||||
secretRef *meta.LocalObjectReference, namespace, endpoint string) (*stdtls.Config, error) {
|
|
||||||
certSecret, err := r.getSecret(ctx, secretRef, namespace)
|
|
||||||
if err != nil || certSecret == nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig, _, err := tls.KubeTLSClientConfigFromSecret(*certSecret, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// getProxyURL attempts to fetch a proxy URL from the object's proxy secret
|
|
||||||
// reference.
|
|
||||||
func (r *BucketReconciler) getProxyURL(ctx context.Context, obj *sourcev1.Bucket) (*url.URL, error) {
|
|
||||||
namespace := obj.GetNamespace()
|
|
||||||
proxySecret, err := r.getSecret(ctx, obj.Spec.ProxySecretRef, namespace)
|
|
||||||
if err != nil || proxySecret == nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
proxyData := proxySecret.Data
|
|
||||||
address, ok := proxyData["address"]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid proxy secret '%s/%s': key 'address' is missing",
|
|
||||||
namespace, obj.Spec.ProxySecretRef.Name)
|
|
||||||
}
|
|
||||||
proxyURL, err := url.Parse(string(address))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse proxy address '%s': %w", address, err)
|
|
||||||
}
|
|
||||||
user, hasUser := proxyData["username"]
|
|
||||||
password, hasPassword := proxyData["password"]
|
|
||||||
if hasUser || hasPassword {
|
|
||||||
proxyURL.User = url.UserPassword(string(user), string(password))
|
|
||||||
}
|
|
||||||
return proxyURL, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSTSSecret attempts to fetch the secret from the object's STS secret
|
|
||||||
// reference.
|
|
||||||
func (r *BucketReconciler) getSTSSecret(ctx context.Context, obj *sourcev1.Bucket) (*corev1.Secret, error) {
|
|
||||||
if obj.Spec.STS == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return r.getSecret(ctx, obj.Spec.STS.SecretRef, obj.GetNamespace())
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSTSTLSConfig attempts to fetch the certificate secret from the object's
|
|
||||||
// STS configuration.
|
|
||||||
func (r *BucketReconciler) getSTSTLSConfig(ctx context.Context, obj *sourcev1.Bucket) (*stdtls.Config, error) {
|
|
||||||
if obj.Spec.STS == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return r.getTLSConfig(ctx, obj.Spec.STS.CertSecretRef, obj.GetNamespace(), obj.Spec.STS.Endpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -943,3 +758,168 @@ func fetchIndexFiles(ctx context.Context, provider BucketProvider, obj *sourcev1
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setupCredentials retrieves and validates secrets for authentication, TLS configuration, and proxy settings.
|
||||||
|
// It returns all credentials needed for bucket providers.
|
||||||
|
func (r *BucketReconciler) setupCredentials(ctx context.Context, obj *sourcev1.Bucket) (*bucketCredentials, error) {
|
||||||
|
var secret *corev1.Secret
|
||||||
|
if obj.Spec.SecretRef != nil {
|
||||||
|
secretName := types.NamespacedName{
|
||||||
|
Namespace: obj.GetNamespace(),
|
||||||
|
Name: obj.Spec.SecretRef.Name,
|
||||||
|
}
|
||||||
|
secret = &corev1.Secret{}
|
||||||
|
if err := r.Get(ctx, secretName, secret); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get secret '%s': %w", secretName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stsSecret *corev1.Secret
|
||||||
|
if obj.Spec.STS != nil && obj.Spec.STS.SecretRef != nil {
|
||||||
|
secretName := types.NamespacedName{
|
||||||
|
Namespace: obj.GetNamespace(),
|
||||||
|
Name: obj.Spec.STS.SecretRef.Name,
|
||||||
|
}
|
||||||
|
stsSecret = &corev1.Secret{}
|
||||||
|
if err := r.Get(ctx, secretName, stsSecret); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get STS secret '%s': %w", secretName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
proxyURL *url.URL
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
stsTLSConfig *tls.Config
|
||||||
|
)
|
||||||
|
|
||||||
|
if obj.Spec.ProxySecretRef != nil {
|
||||||
|
secretRef := types.NamespacedName{
|
||||||
|
Namespace: obj.GetNamespace(),
|
||||||
|
Name: obj.Spec.ProxySecretRef.Name,
|
||||||
|
}
|
||||||
|
proxyURL, err = secrets.ProxyURLFromSecretRef(ctx, r.Client, secretRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get proxy URL: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Spec.CertSecretRef != nil {
|
||||||
|
secretRef := types.NamespacedName{
|
||||||
|
Namespace: obj.GetNamespace(),
|
||||||
|
Name: obj.Spec.CertSecretRef.Name,
|
||||||
|
}
|
||||||
|
tlsConfig, err = secrets.TLSConfigFromSecretRef(ctx, r.Client, secretRef, obj.Spec.Endpoint, secrets.WithSystemCertPool())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get TLS config: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Spec.STS != nil && obj.Spec.STS.CertSecretRef != nil {
|
||||||
|
secretRef := types.NamespacedName{
|
||||||
|
Namespace: obj.GetNamespace(),
|
||||||
|
Name: obj.Spec.STS.CertSecretRef.Name,
|
||||||
|
}
|
||||||
|
stsTLSConfig, err = secrets.TLSConfigFromSecretRef(ctx, r.Client, secretRef, obj.Spec.STS.Endpoint, secrets.WithSystemCertPool())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get STS TLS config: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &bucketCredentials{
|
||||||
|
secret: secret,
|
||||||
|
proxyURL: proxyURL,
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
|
stsSecret: stsSecret,
|
||||||
|
stsTLSConfig: stsTLSConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createBucketProvider creates a provider-specific bucket client using the given credentials and configuration.
|
||||||
|
// It handles different bucket providers (AWS, GCP, Azure, generic) and returns the appropriate client.
|
||||||
|
func (r *BucketReconciler) createBucketProvider(ctx context.Context, obj *sourcev1.Bucket, creds *bucketCredentials) (BucketProvider, error) {
|
||||||
|
switch obj.Spec.Provider {
|
||||||
|
case sourcev1.BucketProviderGoogle:
|
||||||
|
if err := gcp.ValidateSecret(creds.secret); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var opts []gcp.Option
|
||||||
|
if creds.secret != nil {
|
||||||
|
opts = append(opts, gcp.WithSecret(creds.secret))
|
||||||
|
}
|
||||||
|
if creds.proxyURL != nil {
|
||||||
|
opts = append(opts, gcp.WithProxyURL(creds.proxyURL))
|
||||||
|
}
|
||||||
|
return gcp.NewClient(ctx, opts...)
|
||||||
|
|
||||||
|
case sourcev1.BucketProviderAzure:
|
||||||
|
if err := azure.ValidateSecret(creds.secret); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var opts []azure.Option
|
||||||
|
if creds.secret != nil {
|
||||||
|
opts = append(opts, azure.WithSecret(creds.secret))
|
||||||
|
}
|
||||||
|
if creds.proxyURL != nil {
|
||||||
|
opts = append(opts, azure.WithProxyURL(creds.proxyURL))
|
||||||
|
}
|
||||||
|
return azure.NewClient(obj, opts...)
|
||||||
|
|
||||||
|
default:
|
||||||
|
if err := minio.ValidateSecret(creds.secret); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if sts := obj.Spec.STS; sts != nil {
|
||||||
|
if err := minio.ValidateSTSProvider(obj.Spec.Provider, sts); err != nil {
|
||||||
|
return nil, serror.NewStalling(err, sourcev1.InvalidSTSConfigurationReason)
|
||||||
|
}
|
||||||
|
if _, err := url.Parse(sts.Endpoint); err != nil {
|
||||||
|
return nil, serror.NewStalling(fmt.Errorf("failed to parse STS endpoint '%s': %w", sts.Endpoint, err), sourcev1.URLInvalidReason)
|
||||||
|
}
|
||||||
|
if err := minio.ValidateSTSSecret(sts.Provider, creds.stsSecret); err != nil {
|
||||||
|
return nil, serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var opts []minio.Option
|
||||||
|
if creds.secret != nil {
|
||||||
|
opts = append(opts, minio.WithSecret(creds.secret))
|
||||||
|
}
|
||||||
|
if creds.tlsConfig != nil {
|
||||||
|
opts = append(opts, minio.WithTLSConfig(creds.tlsConfig))
|
||||||
|
}
|
||||||
|
if creds.proxyURL != nil {
|
||||||
|
opts = append(opts, minio.WithProxyURL(creds.proxyURL))
|
||||||
|
}
|
||||||
|
if creds.stsSecret != nil {
|
||||||
|
opts = append(opts, minio.WithSTSSecret(creds.stsSecret))
|
||||||
|
}
|
||||||
|
if creds.stsTLSConfig != nil {
|
||||||
|
opts = append(opts, minio.WithSTSTLSConfig(creds.stsTLSConfig))
|
||||||
|
}
|
||||||
|
return minio.NewClient(obj, opts...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncBucketArtifacts handles etag index retrieval and bucket object fetching.
|
||||||
|
// It fetches the etag index from the provider and downloads objects to the specified directory.
|
||||||
|
// Returns true if changes were detected and artifacts were updated.
|
||||||
|
func (r *BucketReconciler) syncBucketArtifacts(ctx context.Context, provider BucketProvider, obj *sourcev1.Bucket, index *index.Digester, dir string) (bool, error) {
|
||||||
|
if err := fetchEtagIndex(ctx, provider, obj, index, dir); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
var changed bool
|
||||||
|
if artifact := obj.Status.Artifact; artifact != nil && artifact.Revision != "" {
|
||||||
|
curRev := digest.Digest(artifact.Revision)
|
||||||
|
changed = curRev.Validate() != nil || curRev != index.Digest(curRev.Algorithm())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the bucket objects if required to.
|
||||||
|
if artifact := obj.GetArtifact(); artifact == nil || changed {
|
||||||
|
if err := fetchIndexFiles(ctx, provider, obj, index, dir); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -522,7 +522,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
assertIndex: index.NewDigester(),
|
assertIndex: index.NewDigester(),
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret '/dummy': secrets \"dummy\" not found"),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get TLS config: secret '/dummy' not found"),
|
||||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||||
},
|
},
|
||||||
|
|
@ -547,7 +547,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "certificate secret does not contain any TLS configuration"),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get TLS config: secret '/dummy' must contain either 'ca.crt' or both 'tls.crt' and 'tls.key'"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -563,7 +563,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
assertIndex: index.NewDigester(),
|
assertIndex: index.NewDigester(),
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret '/dummy': secrets \"dummy\" not found"),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get proxy URL: secret '/dummy' not found"),
|
||||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||||
},
|
},
|
||||||
|
|
@ -575,6 +575,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "dummy",
|
Name: "dummy",
|
||||||
},
|
},
|
||||||
|
Data: map[string][]byte{},
|
||||||
},
|
},
|
||||||
beforeFunc: func(obj *sourcev1.Bucket) {
|
beforeFunc: func(obj *sourcev1.Bucket) {
|
||||||
obj.Spec.ProxySecretRef = &meta.LocalObjectReference{
|
obj.Spec.ProxySecretRef = &meta.LocalObjectReference{
|
||||||
|
|
@ -588,7 +589,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "invalid proxy secret '/dummy': key 'address' is missing"),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get proxy URL: secret '/dummy': key 'address' not found"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -604,7 +605,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
assertIndex: index.NewDigester(),
|
assertIndex: index.NewDigester(),
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret '/dummy': secrets \"dummy\" not found"),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get STS secret '/dummy': secrets \"dummy\" not found"),
|
||||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||||
},
|
},
|
||||||
|
|
@ -648,7 +649,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
assertIndex: index.NewDigester(),
|
assertIndex: index.NewDigester(),
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret '/dummy': secrets \"dummy\" not found"),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get STS TLS config: secret '/dummy' not found"),
|
||||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||||
},
|
},
|
||||||
|
|
@ -676,7 +677,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get STS TLS config: certificate secret does not contain any TLS configuration"),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get STS TLS config: secret '/dummy' must contain either 'ca.crt' or both 'tls.crt' and 'tls.key'"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1073,7 +1074,7 @@ func TestBucketReconciler_reconcileSource_gcs(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
assertIndex: index.NewDigester(),
|
assertIndex: index.NewDigester(),
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret '/dummy': secrets \"dummy\" not found"),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get proxy URL: secret '/dummy' not found"),
|
||||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||||
},
|
},
|
||||||
|
|
@ -1097,7 +1098,7 @@ func TestBucketReconciler_reconcileSource_gcs(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
assertIndex: index.NewDigester(),
|
assertIndex: index.NewDigester(),
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "invalid proxy secret '/dummy': key 'address' is missing"),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get proxy URL: secret '/dummy': key 'address' not found"),
|
||||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||||
},
|
},
|
||||||
|
|
@ -1503,7 +1504,6 @@ func TestBucketReconciler_reconcileArtifact(t *testing.T) {
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
GenerateName: "test-bucket-",
|
GenerateName: "test-bucket-",
|
||||||
Generation: 1,
|
Generation: 1,
|
||||||
Namespace: "default",
|
|
||||||
},
|
},
|
||||||
Spec: sourcev1.BucketSpec{
|
Spec: sourcev1.BucketSpec{
|
||||||
Timeout: &metav1.Duration{Duration: timeout},
|
Timeout: &metav1.Duration{Duration: timeout},
|
||||||
|
|
@ -1751,191 +1751,6 @@ func TestBucketReconciler_notify(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBucketReconciler_getProxyURL(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
bucket *sourcev1.Bucket
|
|
||||||
objects []client.Object
|
|
||||||
expectedURL string
|
|
||||||
expectedErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty proxySecretRef",
|
|
||||||
bucket: &sourcev1.Bucket{
|
|
||||||
Spec: sourcev1.BucketSpec{
|
|
||||||
ProxySecretRef: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-existing proxySecretRef",
|
|
||||||
bucket: &sourcev1.Bucket{
|
|
||||||
Spec: sourcev1.BucketSpec{
|
|
||||||
ProxySecretRef: &meta.LocalObjectReference{
|
|
||||||
Name: "non-existing",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedErr: "failed to get secret '/non-existing': secrets \"non-existing\" not found",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing address in proxySecretRef",
|
|
||||||
bucket: &sourcev1.Bucket{
|
|
||||||
Spec: sourcev1.BucketSpec{
|
|
||||||
ProxySecretRef: &meta.LocalObjectReference{
|
|
||||||
Name: "dummy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
objects: []client.Object{
|
|
||||||
&corev1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "dummy",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedErr: "invalid proxy secret '/dummy': key 'address' is missing",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid address in proxySecretRef",
|
|
||||||
bucket: &sourcev1.Bucket{
|
|
||||||
Spec: sourcev1.BucketSpec{
|
|
||||||
ProxySecretRef: &meta.LocalObjectReference{
|
|
||||||
Name: "dummy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
objects: []client.Object{
|
|
||||||
&corev1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "dummy",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"address": {0x7f},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedErr: "failed to parse proxy address '\x7f': parse \"\\x7f\": net/url: invalid control character in URL",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no user, no password",
|
|
||||||
bucket: &sourcev1.Bucket{
|
|
||||||
Spec: sourcev1.BucketSpec{
|
|
||||||
ProxySecretRef: &meta.LocalObjectReference{
|
|
||||||
Name: "dummy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
objects: []client.Object{
|
|
||||||
&corev1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "dummy",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"address": []byte("http://proxy.example.com"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedURL: "http://proxy.example.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "user, no password",
|
|
||||||
bucket: &sourcev1.Bucket{
|
|
||||||
Spec: sourcev1.BucketSpec{
|
|
||||||
ProxySecretRef: &meta.LocalObjectReference{
|
|
||||||
Name: "dummy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
objects: []client.Object{
|
|
||||||
&corev1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "dummy",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"address": []byte("http://proxy.example.com"),
|
|
||||||
"username": []byte("user"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedURL: "http://user:@proxy.example.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no user, password",
|
|
||||||
bucket: &sourcev1.Bucket{
|
|
||||||
Spec: sourcev1.BucketSpec{
|
|
||||||
ProxySecretRef: &meta.LocalObjectReference{
|
|
||||||
Name: "dummy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
objects: []client.Object{
|
|
||||||
&corev1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "dummy",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"address": []byte("http://proxy.example.com"),
|
|
||||||
"password": []byte("password"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedURL: "http://:password@proxy.example.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "user, password",
|
|
||||||
bucket: &sourcev1.Bucket{
|
|
||||||
Spec: sourcev1.BucketSpec{
|
|
||||||
ProxySecretRef: &meta.LocalObjectReference{
|
|
||||||
Name: "dummy",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
objects: []client.Object{
|
|
||||||
&corev1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "dummy",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"address": []byte("http://proxy.example.com"),
|
|
||||||
"username": []byte("user"),
|
|
||||||
"password": []byte("password"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedURL: "http://user:password@proxy.example.com",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
g := NewWithT(t)
|
|
||||||
|
|
||||||
c := fakeclient.NewClientBuilder().
|
|
||||||
WithScheme(testEnv.Scheme()).
|
|
||||||
WithObjects(tt.objects...).
|
|
||||||
Build()
|
|
||||||
|
|
||||||
r := &BucketReconciler{
|
|
||||||
Client: c,
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := r.getProxyURL(ctx, tt.bucket)
|
|
||||||
if tt.expectedErr == "" {
|
|
||||||
g.Expect(err).To(BeNil())
|
|
||||||
} else {
|
|
||||||
g.Expect(err.Error()).To(ContainSubstring(tt.expectedErr))
|
|
||||||
}
|
|
||||||
if tt.expectedURL == "" {
|
|
||||||
g.Expect(u).To(BeNil())
|
|
||||||
} else {
|
|
||||||
g.Expect(u.String()).To(Equal(tt.expectedURL))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBucketReconciler_APIServerValidation_STS(t *testing.T) {
|
func TestBucketReconciler_APIServerValidation_STS(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
|
|
@ -1035,12 +1035,12 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
want: sreconcile.ResultEmpty,
|
want: sreconcile.ResultEmpty,
|
||||||
wantErr: &serror.Generic{Err: errors.New("failed to get authentication secret: secrets \"invalid\" not found")},
|
wantErr: &serror.Generic{Err: errors.New("failed to get authentication secret '/invalid': secrets \"invalid\" not found")},
|
||||||
assertFunc: func(g *WithT, obj *sourcev1.HelmChart, build chart.Build) {
|
assertFunc: func(g *WithT, obj *sourcev1.HelmChart, build chart.Build) {
|
||||||
g.Expect(build.Complete()).To(BeFalse())
|
g.Expect(build.Complete()).To(BeFalse())
|
||||||
|
|
||||||
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
|
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get authentication secret: secrets \"invalid\" not found"),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get authentication secret '/invalid': secrets \"invalid\" not found"),
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1304,12 +1304,12 @@ func TestHelmChartReconciler_buildFromOCIHelmRepository(t *testing.T) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
want: sreconcile.ResultEmpty,
|
want: sreconcile.ResultEmpty,
|
||||||
wantErr: &serror.Generic{Err: errors.New("failed to get authentication secret: secrets \"invalid\" not found")},
|
wantErr: &serror.Generic{Err: errors.New("failed to get authentication secret '/invalid': secrets \"invalid\" not found")},
|
||||||
assertFunc: func(g *WithT, obj *sourcev1.HelmChart, build chart.Build) {
|
assertFunc: func(g *WithT, obj *sourcev1.HelmChart, build chart.Build) {
|
||||||
g.Expect(build.Complete()).To(BeFalse())
|
g.Expect(build.Complete()).To(BeFalse())
|
||||||
|
|
||||||
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
|
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get authentication secret: secrets \"invalid\" not found"),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get authentication secret '/invalid': secrets \"invalid\" not found"),
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -936,7 +936,7 @@ func (r *OCIRepositoryReconciler) keychain(ctx context.Context, obj *sourcev1.OC
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.AuthenticationFailedReason,
|
r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.AuthenticationFailedReason,
|
||||||
"auth secret '%s' not found", obj.Spec.SecretRef.Name)
|
"auth secret '%s' not found", obj.Spec.SecretRef.Name)
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to get secret '%s': %w", secretRef, err)
|
||||||
}
|
}
|
||||||
imagePullSecrets = append(imagePullSecrets, imagePullSecret)
|
imagePullSecrets = append(imagePullSecrets, imagePullSecret)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,8 @@ func configureAuthentication(ctx context.Context, c client.Client, obj *sourcev1
|
||||||
if obj.Spec.CertSecretRef != nil {
|
if obj.Spec.CertSecretRef != nil {
|
||||||
secret, err := fetchSecret(ctx, c, obj.Spec.CertSecretRef.Name, obj.GetNamespace())
|
secret, err := fetchSecret(ctx, c, obj.Spec.CertSecretRef.Name, obj.GetNamespace())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, nil, fmt.Errorf("failed to get TLS authentication secret: %w", err)
|
secretRef := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.Spec.CertSecretRef.Name}
|
||||||
|
return false, nil, nil, fmt.Errorf("failed to get TLS authentication secret '%s': %w", secretRef, err)
|
||||||
}
|
}
|
||||||
certSecret = secret
|
certSecret = secret
|
||||||
|
|
||||||
|
|
@ -138,7 +139,8 @@ func configureAuthentication(ctx context.Context, c client.Client, obj *sourcev1
|
||||||
if obj.Spec.SecretRef != nil {
|
if obj.Spec.SecretRef != nil {
|
||||||
secret, err := fetchSecret(ctx, c, obj.Spec.SecretRef.Name, obj.GetNamespace())
|
secret, err := fetchSecret(ctx, c, obj.Spec.SecretRef.Name, obj.GetNamespace())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, nil, fmt.Errorf("failed to get authentication secret: %w", err)
|
secretRef := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.Spec.SecretRef.Name}
|
||||||
|
return false, nil, nil, fmt.Errorf("failed to get authentication secret '%s': %w", secretRef, err)
|
||||||
}
|
}
|
||||||
authSecret = secret
|
authSecret = secret
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2023 The Flux authors
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
|
||||||
neturl "net/url"
|
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
const CACrtKey = "ca.crt"
|
|
||||||
|
|
||||||
// TLSBytes contains the bytes of the TLS files.
|
|
||||||
type TLSBytes struct {
|
|
||||||
// CertBytes is the bytes of the certificate file.
|
|
||||||
CertBytes []byte
|
|
||||||
// KeyBytes is the bytes of the key file.
|
|
||||||
KeyBytes []byte
|
|
||||||
// CABytes is the bytes of the CA file.
|
|
||||||
CABytes []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// KubeTLSClientConfigFromSecret returns a TLS client config as a `tls.Config`
|
|
||||||
// object and in its bytes representation. The secret is expected to have the
|
|
||||||
// following keys:
|
|
||||||
// - tls.key, for the private key
|
|
||||||
// - tls.crt, for the certificate
|
|
||||||
// - ca.crt, for the CA certificate
|
|
||||||
//
|
|
||||||
// Secrets with no certificate, private key, AND CA cert are ignored. If only a
|
|
||||||
// certificate OR private key is found, an error is returned. The Secret type
|
|
||||||
// can be blank, Opaque or kubernetes.io/tls.
|
|
||||||
func KubeTLSClientConfigFromSecret(secret corev1.Secret, url string) (*tls.Config, *TLSBytes, error) {
|
|
||||||
return tlsClientConfigFromSecret(secret, url, true, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLSClientConfigFromSecret returns a TLS client config as a `tls.Config`
|
|
||||||
// object and in its bytes representation. The secret is expected to have the
|
|
||||||
// following keys:
|
|
||||||
// - keyFile, for the private key
|
|
||||||
// - certFile, for the certificate
|
|
||||||
// - caFile, for the CA certificate
|
|
||||||
//
|
|
||||||
// Secrets with no certificate, private key, AND CA cert are ignored. If only a
|
|
||||||
// certificate OR private key is found, an error is returned. The Secret type
|
|
||||||
// can be blank, Opaque or kubernetes.io/tls.
|
|
||||||
func TLSClientConfigFromSecret(secret corev1.Secret, url string) (*tls.Config, *TLSBytes, error) {
|
|
||||||
return tlsClientConfigFromSecret(secret, url, false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LegacyTLSClientConfigFromSecret returns a TLS client config as a `tls.Config`
|
|
||||||
// object and in its bytes representation. The secret is expected to have the
|
|
||||||
// following keys:
|
|
||||||
// - keyFile, for the private key
|
|
||||||
// - certFile, for the certificate
|
|
||||||
// - caFile, for the CA certificate
|
|
||||||
//
|
|
||||||
// Secrets with no certificate, private key, AND CA cert are ignored. If only a
|
|
||||||
// certificate OR private key is found, an error is returned.
|
|
||||||
func LegacyTLSClientConfigFromSecret(secret corev1.Secret, url string) (*tls.Config, *TLSBytes, error) {
|
|
||||||
return tlsClientConfigFromSecret(secret, url, false, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// tlsClientConfigFromSecret attempts to construct and return a TLS client
|
|
||||||
// config from the given Secret. If the Secret does not contain any TLS
|
|
||||||
// data, it returns nil.
|
|
||||||
//
|
|
||||||
// kubernetesTLSKeys is a boolean indicating whether to check the Secret
|
|
||||||
// for keys expected to be present in a Kubernetes TLS Secret. Based on its
|
|
||||||
// value, the Secret is checked for the following keys:
|
|
||||||
// - tls.key/keyFile for the private key
|
|
||||||
// - tls.crt/certFile for the certificate
|
|
||||||
// - ca.crt/caFile for the CA certificate
|
|
||||||
// The keys should adhere to a single convention, i.e. a Secret with tls.key
|
|
||||||
// and certFile is invalid.
|
|
||||||
//
|
|
||||||
// checkType is a boolean indicating whether to check the Secret type. If true
|
|
||||||
// and the Secret's type is not blank, Opaque or kubernetes.io/tls, then an
|
|
||||||
// error is returned.
|
|
||||||
func tlsClientConfigFromSecret(secret corev1.Secret, url string, kubernetesTLSKeys bool, checkType bool) (*tls.Config, *TLSBytes, error) {
|
|
||||||
if checkType {
|
|
||||||
// Only Secrets of type Opaque and TLS are allowed. We also allow Secrets with a blank
|
|
||||||
// type, to avoid having to specify the type of the Secret for every test case.
|
|
||||||
// Since a real Kubernetes Secret is of type Opaque by default, its safe to allow this.
|
|
||||||
switch secret.Type {
|
|
||||||
case corev1.SecretTypeOpaque, corev1.SecretTypeTLS, "":
|
|
||||||
default:
|
|
||||||
return nil, nil, fmt.Errorf("cannot use secret '%s' to construct TLS config: invalid secret type: '%s'", secret.Name, secret.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var certBytes, keyBytes, caBytes []byte
|
|
||||||
if kubernetesTLSKeys {
|
|
||||||
certBytes, keyBytes, caBytes = secret.Data[corev1.TLSCertKey], secret.Data[corev1.TLSPrivateKeyKey], secret.Data[CACrtKey]
|
|
||||||
} else {
|
|
||||||
certBytes, keyBytes, caBytes = secret.Data["certFile"], secret.Data["keyFile"], secret.Data["caFile"]
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case len(certBytes)+len(keyBytes)+len(caBytes) == 0:
|
|
||||||
return nil, nil, nil
|
|
||||||
case (len(certBytes) > 0 && len(keyBytes) == 0) || (len(keyBytes) > 0 && len(certBytes) == 0):
|
|
||||||
return nil, nil, fmt.Errorf("invalid '%s' secret data: both certificate and private key need to be provided",
|
|
||||||
secret.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConf := &tls.Config{
|
|
||||||
MinVersion: tls.VersionTLS12,
|
|
||||||
}
|
|
||||||
if len(certBytes) > 0 && len(keyBytes) > 0 {
|
|
||||||
cert, err := tls.X509KeyPair(certBytes, keyBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
tlsConf.Certificates = append(tlsConf.Certificates, cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(caBytes) > 0 {
|
|
||||||
cp, err := x509.SystemCertPool()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("cannot retrieve system certificate pool: %w", err)
|
|
||||||
}
|
|
||||||
if !cp.AppendCertsFromPEM(caBytes) {
|
|
||||||
return nil, nil, fmt.Errorf("cannot append certificate into certificate pool: invalid CA certificate")
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConf.RootCAs = cp
|
|
||||||
}
|
|
||||||
|
|
||||||
if url != "" {
|
|
||||||
u, err := neturl.Parse(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("cannot parse repository URL: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConf.ServerName = u.Hostname()
|
|
||||||
}
|
|
||||||
|
|
||||||
return tlsConf, &TLSBytes{
|
|
||||||
CertBytes: certBytes,
|
|
||||||
KeyBytes: keyBytes,
|
|
||||||
CABytes: caBytes,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2023 The Flux authors
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_tlsClientConfigFromSecret(t *testing.T) {
|
|
||||||
kubernetesTlsSecretFixture := validTlsSecret(t, true)
|
|
||||||
tlsSecretFixture := validTlsSecret(t, false)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
secret corev1.Secret
|
|
||||||
modify func(secret *corev1.Secret)
|
|
||||||
tlsKeys bool
|
|
||||||
checkType bool
|
|
||||||
url string
|
|
||||||
wantErr bool
|
|
||||||
wantNil bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "tls.crt, tls.key and ca.crt",
|
|
||||||
secret: kubernetesTlsSecretFixture,
|
|
||||||
modify: nil,
|
|
||||||
tlsKeys: true,
|
|
||||||
url: "https://example.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "certFile, keyFile and caFile",
|
|
||||||
secret: tlsSecretFixture,
|
|
||||||
modify: nil,
|
|
||||||
tlsKeys: false,
|
|
||||||
url: "https://example.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "without tls.crt",
|
|
||||||
secret: kubernetesTlsSecretFixture,
|
|
||||||
modify: func(s *corev1.Secret) { delete(s.Data, "tls.crt") },
|
|
||||||
tlsKeys: true,
|
|
||||||
wantErr: true,
|
|
||||||
wantNil: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "without tls.key",
|
|
||||||
secret: kubernetesTlsSecretFixture,
|
|
||||||
modify: func(s *corev1.Secret) { delete(s.Data, "tls.key") },
|
|
||||||
tlsKeys: true,
|
|
||||||
wantErr: true,
|
|
||||||
wantNil: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "without ca.crt",
|
|
||||||
secret: kubernetesTlsSecretFixture,
|
|
||||||
modify: func(s *corev1.Secret) { delete(s.Data, "ca.crt") },
|
|
||||||
tlsKeys: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty secret",
|
|
||||||
secret: corev1.Secret{},
|
|
||||||
tlsKeys: true,
|
|
||||||
wantNil: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "docker config secret with type checking enabled",
|
|
||||||
secret: tlsSecretFixture,
|
|
||||||
modify: func(secret *corev1.Secret) { secret.Type = corev1.SecretTypeDockerConfigJson },
|
|
||||||
tlsKeys: false,
|
|
||||||
checkType: true,
|
|
||||||
wantErr: true,
|
|
||||||
wantNil: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "docker config secret with type checking disabled",
|
|
||||||
secret: tlsSecretFixture,
|
|
||||||
modify: func(secret *corev1.Secret) { secret.Type = corev1.SecretTypeDockerConfigJson },
|
|
||||||
tlsKeys: false,
|
|
||||||
url: "https://example.com",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
g := NewWithT(t)
|
|
||||||
secret := tt.secret.DeepCopy()
|
|
||||||
if tt.modify != nil {
|
|
||||||
tt.modify(secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig, _, err := tlsClientConfigFromSecret(*secret, tt.url, tt.tlsKeys, tt.checkType)
|
|
||||||
g.Expect(err != nil).To(Equal(tt.wantErr), fmt.Sprintf("expected error: %v, got: %v", tt.wantErr, err))
|
|
||||||
g.Expect(tlsConfig == nil).To(Equal(tt.wantNil))
|
|
||||||
if tt.url != "" {
|
|
||||||
u, _ := url.Parse(tt.url)
|
|
||||||
g.Expect(u.Hostname()).To(Equal(tlsConfig.ServerName))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validTlsSecret creates a secret containing key pair and CA certificate that are
|
|
||||||
// valid from a syntax (minimum requirements) perspective.
|
|
||||||
func validTlsSecret(t *testing.T, kubernetesTlsKeys bool) corev1.Secret {
|
|
||||||
t.Helper()
|
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Private key cannot be created.", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
certTemplate := x509.Certificate{
|
|
||||||
SerialNumber: big.NewInt(1337),
|
|
||||||
}
|
|
||||||
cert, err := x509.CreateCertificate(rand.Reader, &certTemplate, &certTemplate, &key.PublicKey, key)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Certificate cannot be created.", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
ca := &x509.Certificate{
|
|
||||||
SerialNumber: big.NewInt(7331),
|
|
||||||
IsCA: true,
|
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
|
||||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
||||||
}
|
|
||||||
|
|
||||||
caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("CA private key cannot be created.", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("CA certificate cannot be created.", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
keyPem := pem.EncodeToMemory(&pem.Block{
|
|
||||||
Type: "RSA PRIVATE KEY",
|
|
||||||
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
|
||||||
})
|
|
||||||
|
|
||||||
certPem := pem.EncodeToMemory(&pem.Block{
|
|
||||||
Type: "CERTIFICATE",
|
|
||||||
Bytes: cert,
|
|
||||||
})
|
|
||||||
|
|
||||||
caPem := pem.EncodeToMemory(&pem.Block{
|
|
||||||
Type: "CERTIFICATE",
|
|
||||||
Bytes: caBytes,
|
|
||||||
})
|
|
||||||
|
|
||||||
crtKey := corev1.TLSCertKey
|
|
||||||
pkKey := corev1.TLSPrivateKeyKey
|
|
||||||
caKey := CACrtKey
|
|
||||||
if !kubernetesTlsKeys {
|
|
||||||
crtKey = "certFile"
|
|
||||||
pkKey = "keyFile"
|
|
||||||
caKey = "caFile"
|
|
||||||
}
|
|
||||||
return corev1.Secret{
|
|
||||||
Data: map[string][]byte{
|
|
||||||
crtKey: []byte(certPem),
|
|
||||||
pkKey: []byte(keyPem),
|
|
||||||
caKey: []byte(caPem),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue