Implement core pkg/runtime/secrets migration

Updates pkg/runtime from v0.60.0 to v0.63.0 and migrates core secret handling
components to use the consolidated pkg/runtime/secrets package.

This includes basic auth, TLS config, and OCI registry authentication for
Bucket and OCIRepository controllers. Removes internal wrapper functions
in favor of direct pkg/runtime/secrets usage.

Signed-off-by: cappyzawa <cappyzawa@gmail.com>
This commit is contained in:
cappyzawa 2025-07-04 01:48:56 +09:00
parent 8f77ed4981
commit 3cd259eaf2
No known key found for this signature in database
10 changed files with 94 additions and 212 deletions

2
go.mod
View File

@ -37,7 +37,7 @@ require (
github.com/fluxcd/pkg/lockedfile v0.6.0
github.com/fluxcd/pkg/masktoken v0.7.0
github.com/fluxcd/pkg/oci v0.49.0
github.com/fluxcd/pkg/runtime v0.60.0
github.com/fluxcd/pkg/runtime v0.63.0
github.com/fluxcd/pkg/sourceignore v0.12.0
github.com/fluxcd/pkg/ssh v0.19.0
github.com/fluxcd/pkg/tar v0.12.0

4
go.sum
View File

@ -391,8 +391,8 @@ github.com/fluxcd/pkg/masktoken v0.7.0 h1:pitmyOg2pUVdW+nn2Lk/xqm2TaA08uxvOC0ns3
github.com/fluxcd/pkg/masktoken v0.7.0/go.mod h1:Lc1uoDjO1GY6+YdkK+ZqqBIBWquyV58nlSJ5S1N1IYU=
github.com/fluxcd/pkg/oci v0.49.0 h1:L8/dmNSIzqu6X8vzIkPLrW8NAF7Et/SnOuI8WJkXeq8=
github.com/fluxcd/pkg/oci v0.49.0/go.mod h1:iZkF4bQTpc6YOU5IJWMBp0Q8voGm7bkMYiAarJ9407U=
github.com/fluxcd/pkg/runtime v0.60.0 h1:d++EkV3FlycB+bzakB5NumwY4J8xts8i7lbvD6jBLeU=
github.com/fluxcd/pkg/runtime v0.60.0/go.mod h1:UeU0/eZLErYC/1bTmgzBfNXhiHy9fuQzjfLK0HxRgxY=
github.com/fluxcd/pkg/runtime v0.63.0 h1:55J7ascGmXyTXWGwhD21N9fU7jC1l5rhdzjgNXs6aZg=
github.com/fluxcd/pkg/runtime v0.63.0/go.mod h1:7pxGvaU0Yy1cDIUhiHAHhCx2yCLnkcVsplbYZG6j4JY=
github.com/fluxcd/pkg/sourceignore v0.12.0 h1:jCIe6d50rQ3wdXPF0+PhhqN0XrTRIq3upMomPelI8Mw=
github.com/fluxcd/pkg/sourceignore v0.12.0/go.mod h1:dc0zvkuXM5OgL/b3IkrVuwvPjj1zJn4NBUMH45uJ4Y0=
github.com/fluxcd/pkg/ssh v0.19.0 h1:njSwNJQZ+3TGhBXshU/2TbqvooMbf6lQzFn7w6vuaKI=

View File

@ -50,6 +50,7 @@ import (
"github.com/fluxcd/pkg/runtime/patch"
"github.com/fluxcd/pkg/runtime/predicates"
rreconcile "github.com/fluxcd/pkg/runtime/reconcile"
"github.com/fluxcd/pkg/runtime/secrets"
"github.com/fluxcd/pkg/sourceignore"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
@ -58,7 +59,6 @@ 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"
@ -480,11 +480,14 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
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 {
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
return sreconcile.ResultEmpty, e
var tlsConfig *stdtls.Config
if obj.Spec.CertSecretRef != nil {
tlsConfig, err = r.getTLSConfig(ctx, obj.Spec.CertSecretRef, obj.GetNamespace(), obj.Spec.Endpoint)
if err != nil {
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
return sreconcile.ResultEmpty, e
}
}
stsSecret, err := r.getSTSSecret(ctx, obj)
if err != nil {
@ -757,17 +760,19 @@ func (r *BucketReconciler) getSecret(ctx context.Context, secretRef *meta.LocalO
// 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)
tlsConfig, err := secrets.TLSConfigFromSecret(ctx, r.Client, secretRef.Name, namespace)
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")
}
u, err := url.Parse(endpoint)
if err != nil {
return nil, fmt.Errorf("cannot parse endpoint URL: %w", err)
}
tlsConfig.ServerName = u.Hostname()
tlsConfig.MinVersion = stdtls.VersionTLS12
return tlsConfig, nil
}
@ -812,6 +817,9 @@ func (r *BucketReconciler) getSTSTLSConfig(ctx context.Context, obj *sourcev1.Bu
if obj.Spec.STS == nil {
return nil, nil
}
if obj.Spec.STS.CertSecretRef == nil {
return nil, nil
}
return r.getTLSConfig(ctx, obj.Spec.STS.CertSecretRef, obj.GetNamespace(), obj.Spec.STS.Endpoint)
}

View File

@ -481,7 +481,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
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(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret 'default/dummy': secrets \"dummy\" not found"),
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
},
@ -491,7 +491,8 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
bucketName: "dummy",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "dummy",
Name: "dummy",
Namespace: "default",
},
},
beforeFunc: func(obj *sourcev1.Bucket) {
@ -522,7 +523,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
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(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to create TLS config: secret 'default/dummy' not found"),
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
},
@ -532,7 +533,8 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
bucketName: "dummy",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "dummy",
Name: "dummy",
Namespace: "default",
},
},
beforeFunc: func(obj *sourcev1.Bucket) {
@ -547,7 +549,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
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"),
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to create TLS config: secret 'default/dummy' must contain either 'ca.crt' or both 'tls.crt' and 'tls.key'"),
},
},
{
@ -563,7 +565,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
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(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret 'default/dummy': secrets \"dummy\" not found"),
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
},
@ -573,7 +575,8 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
bucketName: "dummy",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "dummy",
Name: "dummy",
Namespace: "default",
},
},
beforeFunc: func(obj *sourcev1.Bucket) {
@ -588,7 +591,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
assertConditions: []metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
*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, "invalid proxy secret 'default/dummy': key 'address' is missing"),
},
},
{
@ -604,7 +607,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
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(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret 'default/dummy': secrets \"dummy\" not found"),
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
},
@ -614,7 +617,8 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
bucketName: "dummy",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "dummy",
Name: "dummy",
Namespace: "default",
},
},
beforeFunc: func(obj *sourcev1.Bucket) {
@ -648,7 +652,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
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(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to create TLS config: secret 'default/dummy' not found"),
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
},
@ -658,7 +662,8 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
bucketName: "dummy",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "dummy",
Name: "dummy",
Namespace: "default",
},
},
beforeFunc: func(obj *sourcev1.Bucket) {
@ -676,7 +681,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
assertConditions: []metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
*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: failed to create TLS config: secret 'default/dummy' must contain either 'ca.crt' or both 'tls.crt' and 'tls.key'"),
},
},
{
@ -921,6 +926,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-bucket-",
Generation: 1,
Namespace: "default",
},
Spec: sourcev1.BucketSpec{
Timeout: &metav1.Duration{Duration: timeout},
@ -1030,7 +1036,7 @@ func TestBucketReconciler_reconcileSource_gcs(t *testing.T) {
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(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret 'default/dummy': secrets \"dummy\" not found"),
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
},
@ -1040,7 +1046,8 @@ func TestBucketReconciler_reconcileSource_gcs(t *testing.T) {
bucketName: "dummy",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "dummy",
Name: "dummy",
Namespace: "default",
},
},
beforeFunc: func(obj *sourcev1.Bucket) {
@ -1073,7 +1080,7 @@ func TestBucketReconciler_reconcileSource_gcs(t *testing.T) {
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(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret 'default/dummy': secrets \"dummy\" not found"),
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
},
@ -1083,7 +1090,8 @@ func TestBucketReconciler_reconcileSource_gcs(t *testing.T) {
bucketName: "dummy",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "dummy",
Name: "dummy",
Namespace: "default",
},
},
beforeFunc: func(obj *sourcev1.Bucket) {
@ -1097,7 +1105,7 @@ func TestBucketReconciler_reconcileSource_gcs(t *testing.T) {
wantErr: true,
assertIndex: index.NewDigester(),
assertConditions: []metav1.Condition{
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "invalid proxy secret '/dummy': key 'address' is missing"),
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "invalid proxy secret 'default/dummy': key 'address' is missing"),
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
},

View File

@ -48,11 +48,11 @@ import (
"github.com/fluxcd/pkg/runtime/conditions"
conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check"
"github.com/fluxcd/pkg/runtime/patch"
"github.com/fluxcd/pkg/runtime/secrets"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
"github.com/fluxcd/source-controller/internal/cache"
intdigest "github.com/fluxcd/source-controller/internal/digest"
"github.com/fluxcd/source-controller/internal/helm/getter"
"github.com/fluxcd/source-controller/internal/helm/repository"
intpredicates "github.com/fluxcd/source-controller/internal/predicates"
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
@ -881,16 +881,14 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
var tlsConf *tls.Config
validSecret := true
if secret != nil {
// Extract the client options from secret, ignoring any invalid
// value. validSecret is used to determine if the index digest
// should be calculated below.
var gOpts []helmgetter.Option
var serr error
gOpts, serr = getter.GetterOptionsFromSecret(*secret)
// Extract the client option from secret. validSecret is used to
// determine if the index digest should be calculated below.
username, password, serr := secrets.BasicAuthFromSecret(ctx, k8sClient, secret.Name, secret.Namespace)
if serr != nil {
validSecret = false
} else {
getterOpts = append(getterOpts, helmgetter.WithBasicAuth(username, password))
}
getterOpts = append(getterOpts, gOpts...)
repoURL := server.URL()
if tt.url != "" {
repoURL = tt.url

View File

@ -60,6 +60,7 @@ import (
"github.com/fluxcd/pkg/runtime/patch"
"github.com/fluxcd/pkg/runtime/predicates"
rreconcile "github.com/fluxcd/pkg/runtime/reconcile"
"github.com/fluxcd/pkg/runtime/secrets"
"github.com/fluxcd/pkg/sourceignore"
"github.com/fluxcd/pkg/tar"
"github.com/fluxcd/pkg/version"
@ -77,7 +78,6 @@ import (
"github.com/fluxcd/source-controller/internal/oci/notation"
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/internal/util"
)
@ -995,30 +995,11 @@ func (r *OCIRepositoryReconciler) getTLSConfig(ctx context.Context, obj *sourcev
return nil, nil
}
certSecretName := types.NamespacedName{
Namespace: obj.Namespace,
Name: obj.Spec.CertSecretRef.Name,
}
var certSecret corev1.Secret
if err := r.Get(ctx, certSecretName, &certSecret); err != nil {
return nil, err
}
tlsConfig, _, err := tls.KubeTLSClientConfigFromSecret(certSecret, "")
tlsConfig, err := secrets.TLSConfigFromSecret(ctx, r.Client, obj.Spec.CertSecretRef.Name, obj.Namespace)
if err != nil {
return nil, err
}
if tlsConfig == nil {
tlsConfig, _, err = tls.TLSClientConfigFromSecret(certSecret, "")
if err != nil {
return nil, err
}
if tlsConfig != nil {
ctrl.LoggerFrom(ctx).
Info("warning: specifying TLS auth data via `certFile`/`keyFile`/`caFile` is deprecated, please use `tls.crt`/`tls.key`/`ca.crt` instead")
}
}
tlsConfig.MinVersion = cryptotls.VersionTLS12
return tlsConfig, nil
}

View File

@ -24,6 +24,7 @@ import (
"os"
"path"
"github.com/fluxcd/pkg/runtime/secrets"
"github.com/google/go-containerregistry/pkg/authn"
helmgetter "helm.sh/helm/v3/pkg/getter"
helmreg "helm.sh/helm/v3/pkg/registry"
@ -109,11 +110,11 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *sourcev1.HelmRepos
}
// Construct actual Helm client options.
opts, err := GetterOptionsFromSecret(*authSecret)
username, password, err := secrets.BasicAuthFromSecret(ctx, c, obj.Spec.SecretRef.Name, obj.GetNamespace())
if err != nil {
return nil, "", fmt.Errorf("failed to configure Helm client: %w", err)
}
hrOpts.GetterOpts = append(hrOpts.GetterOpts, opts...)
hrOpts.GetterOpts = append(hrOpts.GetterOpts, helmgetter.WithBasicAuth(username, password))
// If the TLS config is nil, i.e. one couldn't be constructed using
// `.spec.certSecretRef`, then try to use `.spec.secretRef`.
@ -129,7 +130,7 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *sourcev1.HelmRepos
}
if ociRepo {
hrOpts.Keychain, err = registry.LoginOptionFromSecret(url, *authSecret)
hrOpts.Keychain, err = registry.LoginOptionFromSecretRef(ctx, c, url, obj.Spec.SecretRef.Name, obj.GetNamespace())
if err != nil {
return nil, "", fmt.Errorf("failed to configure login options: %w", err)
}

View File

@ -1,54 +0,0 @@
/*
Copyright 2020 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 getter
import (
"fmt"
"helm.sh/helm/v3/pkg/getter"
corev1 "k8s.io/api/core/v1"
)
// GetterOptionsFromSecret constructs a getter.Option slice for the given secret.
// It returns the slice, or an error.
func GetterOptionsFromSecret(secret corev1.Secret) ([]getter.Option, error) {
var opts []getter.Option
basicAuth, err := basicAuthFromSecret(secret)
if err != nil {
return opts, err
}
if basicAuth != nil {
opts = append(opts, basicAuth)
}
return opts, nil
}
// basicAuthFromSecret attempts to construct a basic auth getter.Option for the
// given v1.Secret and returns the result.
//
// Secrets with no username AND password are ignored, if only one is defined it
// returns an error.
func basicAuthFromSecret(secret corev1.Secret) (getter.Option, error) {
username, password := string(secret.Data["username"]), string(secret.Data["password"])
switch {
case username == "" && password == "":
return nil, nil
case username == "" || password == "":
return nil, fmt.Errorf("invalid '%s' secret data: required fields 'username' and 'password'", secret.Name)
}
return getter.WithBasicAuth(username, password), nil
}

View File

@ -1,93 +0,0 @@
/*
Copyright 2020 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 getter
import (
"testing"
corev1 "k8s.io/api/core/v1"
)
var (
basicAuthSecretFixture = corev1.Secret{
Data: map[string][]byte{
"username": []byte("user"),
"password": []byte("password"),
},
}
)
func TestGetterOptionsFromSecret(t *testing.T) {
tests := []struct {
name string
secrets []corev1.Secret
}{
{"basic auth", []corev1.Secret{basicAuthSecretFixture}},
{"empty", []corev1.Secret{}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
secret := corev1.Secret{Data: map[string][]byte{}}
for _, s := range tt.secrets {
for k, v := range s.Data {
secret.Data[k] = v
}
}
got, err := GetterOptionsFromSecret(secret)
if err != nil {
t.Errorf("ClientOptionsFromSecret() error = %v", err)
return
}
if len(got) != len(tt.secrets) {
t.Errorf("ClientOptionsFromSecret() options = %v, expected = %v", got, len(tt.secrets))
}
})
}
}
func Test_basicAuthFromSecret(t *testing.T) {
tests := []struct {
name string
secret corev1.Secret
modify func(secret *corev1.Secret)
wantErr bool
wantNil bool
}{
{"username and password", basicAuthSecretFixture, nil, false, false},
{"without username", basicAuthSecretFixture, func(s *corev1.Secret) { delete(s.Data, "username") }, true, true},
{"without password", basicAuthSecretFixture, func(s *corev1.Secret) { delete(s.Data, "password") }, true, true},
{"empty", corev1.Secret{}, nil, false, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
secret := tt.secret.DeepCopy()
if tt.modify != nil {
tt.modify(secret)
}
got, err := basicAuthFromSecret(*secret)
if (err != nil) != tt.wantErr {
t.Errorf("BasicAuthFromSecret() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantNil && got != nil {
t.Error("BasicAuthFromSecret() != nil")
return
}
})
}
}

View File

@ -18,16 +18,20 @@ package registry
import (
"bytes"
"context"
"errors"
"fmt"
"net/url"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/credentials"
"github.com/fluxcd/pkg/runtime/secrets"
"github.com/fluxcd/source-controller/internal/helm/common"
"github.com/fluxcd/source-controller/internal/oci"
"github.com/google/go-containerregistry/pkg/authn"
"helm.sh/helm/v3/pkg/registry"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// helper is a subset of the Docker credential helper credentials.Helper interface used by NewKeychainFromHelper.
@ -86,6 +90,35 @@ func LoginOptionFromSecret(registryURL string, secret corev1.Secret) (authn.Keyc
return authn.NewKeychainFromHelper(helper{registry: parsedURL.Host, username: username, password: password}), nil
}
// LoginOptionFromSecretRef derives authentication data using runtime/secrets.
func LoginOptionFromSecretRef(ctx context.Context, c client.Client, registryURL, secretName, namespace string) (authn.Keychain, error) {
parsedURL, err := url.Parse(registryURL)
if err != nil {
return nil, fmt.Errorf("unable to parse registry URL '%s' while reconciling Secret '%s/%s': %w",
registryURL, namespace, secretName, err)
}
secretKey := client.ObjectKey{Name: secretName, Namespace: namespace}
var secret corev1.Secret
if err := c.Get(ctx, secretKey, &secret); err != nil {
return nil, fmt.Errorf("failed to get secret '%s/%s': %w", namespace, secretName, err)
}
if secret.Type == corev1.SecretTypeDockerConfigJson {
return LoginOptionFromSecret(registryURL, secret)
}
username, password, err := secrets.BasicAuthFromSecret(ctx, c, secretName, namespace)
if err != nil {
if errors.Is(err, secrets.ErrKeyNotFound) {
return oci.Anonymous{}, nil
}
return nil, err
}
return authn.NewKeychainFromHelper(helper{registry: parsedURL.Host, username: username, password: password}), nil
}
// KeyChainAdaptHelper returns an ORAS credentials callback configured with the authorization data
// from the given authn keychain. This allows for example to make use of credential helpers from
// cloud providers.