diff --git a/go.mod b/go.mod index 82990c75c..de71877fb 100644 --- a/go.mod +++ b/go.mod @@ -22,13 +22,14 @@ require ( github.com/fluxcd/cli-utils v0.36.0-flux.7 github.com/fluxcd/pkg/apis/event v0.9.0 github.com/fluxcd/pkg/apis/meta v1.5.0 + github.com/fluxcd/pkg/cache v0.0.1 github.com/fluxcd/pkg/git v0.19.0 github.com/fluxcd/pkg/git/gogit v0.19.0 github.com/fluxcd/pkg/gittestserver v0.12.0 github.com/fluxcd/pkg/helmtestserver v0.18.0 github.com/fluxcd/pkg/lockedfile v0.3.0 github.com/fluxcd/pkg/masktoken v0.4.0 - github.com/fluxcd/pkg/oci v0.37.1 + github.com/fluxcd/pkg/oci v0.38.0 github.com/fluxcd/pkg/runtime v0.47.1 github.com/fluxcd/pkg/sourceignore v0.7.0 github.com/fluxcd/pkg/ssh v0.13.0 @@ -314,7 +315,6 @@ require ( github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/viper v1.18.2 // indirect github.com/spiffe/go-spiffe/v2 v2.2.0 // indirect - github.com/stretchr/objx v0.5.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/thales-e-security/pool v0.0.2 // indirect diff --git a/go.sum b/go.sum index 8083e29f5..9cac0071e 100644 --- a/go.sum +++ b/go.sum @@ -342,6 +342,8 @@ github.com/fluxcd/pkg/apis/event v0.9.0 h1:iKxU+3v/3bAuC1C1iXg1mjbIiaEQet7WETh8l github.com/fluxcd/pkg/apis/event v0.9.0/go.mod h1:5LjcTeppPMEyOgtTbIP7q2GbVwIRUfujIxynIjHBV/k= github.com/fluxcd/pkg/apis/meta v1.5.0 h1:/G82d2Az5D9op3F+wJUpD8jw/eTV0suM6P7+cSURoUM= github.com/fluxcd/pkg/apis/meta v1.5.0/go.mod h1:Y3u7JomuuKtr5fvP1Iji2/50FdRe5GcBug2jawNVkdM= +github.com/fluxcd/pkg/cache v0.0.1 h1:aeDQm4D37btj6I01p6ZKW6JNOZm3CIYN5PaVzyhHr38= +github.com/fluxcd/pkg/cache v0.0.1/go.mod h1:R3TJIK9XaohHNc3BeqfZX/UivMrx8Xz6ihGoVAjh75k= github.com/fluxcd/pkg/git v0.19.0 h1:zIv+GAT0ieIUpnGBVi3Bhax/qq4Rr28BW7Jv4DTt6zE= github.com/fluxcd/pkg/git v0.19.0/go.mod h1:wkqUOSrTjtsVVk/gC6/7RxVpi9GcqAA+7O5HVJF5S14= github.com/fluxcd/pkg/git/gogit v0.19.0 h1:SdoNAmC/HTPXniQjp609X59rCsBiA+Sdq1Hv8SnYC6I= @@ -354,8 +356,8 @@ github.com/fluxcd/pkg/lockedfile v0.3.0 h1:tZkBAffcxyt4zMigHIKc54cKgN5I/kFF005gy github.com/fluxcd/pkg/lockedfile v0.3.0/go.mod h1:5iCYXAs953LlXZq7nTId9ZSGnHVvTfZ0mDmrDE49upk= github.com/fluxcd/pkg/masktoken v0.4.0 h1:pRItymXzW8dhT9Fd4XfnbrgKeySPeeLCrr6W1pgrUbM= github.com/fluxcd/pkg/masktoken v0.4.0/go.mod h1:MP1nCsr2tJbH8hnhZP4+7TfTR0ggrKOJgi9Bo7Mj/6M= -github.com/fluxcd/pkg/oci v0.37.1 h1:p4rfCHZlBWL+Q5Xey51iiBRmoje0IevCBT0/r8iae3M= -github.com/fluxcd/pkg/oci v0.37.1/go.mod h1:LrVuX6VACenJ5ycQJxec+I7YJegCsE4nzRUV+6RuxcY= +github.com/fluxcd/pkg/oci v0.38.0 h1:a9pCdqiUPZ7YOnYDXVXCxELBU0r6xbDnGv4C6YUz7vU= +github.com/fluxcd/pkg/oci v0.38.0/go.mod h1:mYVSxnpVutRmWu6mpwxm7hXFn6qdhLEjspL04ej/WZU= github.com/fluxcd/pkg/runtime v0.47.1 h1:Q1tAFsp92uurWyoEe52AmMC4k+6DYTPBrUQDs+nz/9c= github.com/fluxcd/pkg/runtime v0.47.1/go.mod h1:97a+PqpWMgQsoqh91uH3EQz+/DC7Uxc8xcu/rDHFC5c= github.com/fluxcd/pkg/sourceignore v0.7.0 h1:qQrB2o543wA1o4vgR62ufwkAaDp8+f8Wdj1HKDlmDrU= @@ -911,8 +913,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= -github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/internal/controller/helmchart_controller.go b/internal/controller/helmchart_controller.go index 614b43b53..8b34859e8 100644 --- a/internal/controller/helmchart_controller.go +++ b/internal/controller/helmchart_controller.go @@ -138,8 +138,9 @@ type HelmChartReconciler struct { Getters helmgetter.Providers ControllerName string - Cache *cache.Cache - TTL time.Duration + OIDCAuthenticator *soci.OIDCAuthenticator + Cache *cache.Cache + TTL time.Duration *cache.CacheRecorder patchOptions []patch.Option @@ -527,7 +528,8 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * return chartRepoConfigErrorReturn(err, obj) } - clientOpts, certsTmpDir, err := getter.GetClientOpts(ctxTimeout, r.Client, repo, normalizedURL) + clientOpts, certsTmpDir, err := getter.GetClientOptsWithOIDCAuth(ctxTimeout, + r.Client, r.OIDCAuthenticator, repo, normalizedURL) if err != nil && !errors.Is(err, getter.ErrDeprecatedTLSConfig) { e := serror.NewGeneric( err, @@ -1012,8 +1014,9 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont } obj = &sourcev1.HelmRepository{ Spec: sourcev1.HelmRepositorySpec{ - URL: url, - Timeout: &metav1.Duration{Duration: 60 * time.Second}, + URL: url, + Timeout: &metav1.Duration{Duration: 60 * time.Second}, + Provider: sourcev1beta2.GenericOCIProvider, }, } } @@ -1022,7 +1025,8 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont ctxTimeout, cancel := context.WithTimeout(ctx, obj.GetTimeout()) defer cancel() - clientOpts, certsTmpDir, err := getter.GetClientOpts(ctxTimeout, r.Client, obj, normalizedURL) + clientOpts, certsTmpDir, err := getter.GetClientOptsWithOIDCAuth(ctxTimeout, r.Client, + r.OIDCAuthenticator, obj, normalizedURL) if err != nil && !errors.Is(err, getter.ErrDeprecatedTLSConfig) { return nil, err } diff --git a/internal/controller/helmchart_controller_test.go b/internal/controller/helmchart_controller_test.go index 39f9991f1..173011808 100644 --- a/internal/controller/helmchart_controller_test.go +++ b/internal/controller/helmchart_controller_test.go @@ -1129,8 +1129,9 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) { GenerateName: "helmrepository-", }, Spec: sourcev1.HelmRepositorySpec{ - URL: server.URL(), - Timeout: &metav1.Duration{Duration: timeout}, + URL: server.URL(), + Timeout: &metav1.Duration{Duration: timeout}, + Provider: sourcev1beta2.GenericOCIProvider, }, Status: sourcev1.HelmRepositoryStatus{ Artifact: &sourcev1.Artifact{ @@ -2647,11 +2648,14 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) { }, } + authenticator, er := oci.NewOIDCAuthenticator(oci.WithCacheCapacity(1)) + g.Expect(er).NotTo(HaveOccurred()) r := &HelmChartReconciler{ Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Getters: testGetters, RegistryClientGenerator: registry.ClientGenerator, + OIDCAuthenticator: authenticator, patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } diff --git a/internal/controller/helmrepository_controller_test.go b/internal/controller/helmrepository_controller_test.go index 9724baf65..2e7ad5372 100644 --- a/internal/controller/helmrepository_controller_test.go +++ b/internal/controller/helmrepository_controller_test.go @@ -50,6 +50,7 @@ import ( "github.com/fluxcd/pkg/runtime/patch" sourcev1 "github.com/fluxcd/source-controller/api/v1" + sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" "github.com/fluxcd/source-controller/internal/cache" intdigest "github.com/fluxcd/source-controller/internal/digest" "github.com/fluxcd/source-controller/internal/helm/getter" @@ -819,6 +820,7 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) { Spec: sourcev1.HelmRepositorySpec{ Interval: metav1.Duration{Duration: interval}, Timeout: &metav1.Duration{Duration: timeout}, + Provider: sourcev1beta2.GenericOCIProvider, }, } diff --git a/internal/controller/ocirepository_controller.go b/internal/controller/ocirepository_controller.go index 3de4faaa7..49014e703 100644 --- a/internal/controller/ocirepository_controller.go +++ b/internal/controller/ocirepository_controller.go @@ -141,6 +141,7 @@ type OCIRepositoryReconciler struct { Storage *Storage ControllerName string requeueDependency time.Duration + OIDCAuthenticator *soci.OIDCAuthenticator patchOptions []patch.Option } @@ -355,7 +356,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch if _, ok := keychain.(soci.Anonymous); obj.Spec.Provider != ociv1.GenericOCIProvider && ok { var authErr error - auth, authErr = soci.OIDCAuth(ctxTimeout, obj.Spec.URL, obj.Spec.Provider) + auth, authErr = r.OIDCAuthenticator.Authorization(ctxTimeout, obj.Spec.URL, obj.Spec.Provider) if authErr != nil && !errors.Is(authErr, oci.ErrUnconfiguredProvider) { e := serror.NewGeneric( fmt.Errorf("failed to get credential from %s: %w", obj.Spec.Provider, authErr), diff --git a/internal/controller/ocirepository_controller_test.go b/internal/controller/ocirepository_controller_test.go index 0e9f89885..089849efe 100644 --- a/internal/controller/ocirepository_controller_test.go +++ b/internal/controller/ocirepository_controller_test.go @@ -69,8 +69,10 @@ import ( sourcev1 "github.com/fluxcd/source-controller/api/v1" ociv1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" intdigest "github.com/fluxcd/source-controller/internal/digest" serror "github.com/fluxcd/source-controller/internal/error" + soci "github.com/fluxcd/source-controller/internal/oci" snotation "github.com/fluxcd/source-controller/internal/oci/notation" sreconcile "github.com/fluxcd/source-controller/internal/reconcile" ) @@ -795,11 +797,15 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) { obj.Spec.Insecure = true } + authenticator, er := soci.NewOIDCAuthenticator(soci.WithCacheCapacity(1)) + g.Expect(er).NotTo(HaveOccurred()) + r := &OCIRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + OIDCAuthenticator: authenticator, + patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), } opts := makeRemoteOptions(ctx, makeTransport(tt.insecure), authn.DefaultKeychain, nil) @@ -1147,6 +1153,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) { Interval: metav1.Duration{Duration: interval}, Timeout: &metav1.Duration{Duration: timeout}, Insecure: true, + Provider: sourcev1beta2.GenericOCIProvider, }, } @@ -1398,6 +1405,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignatureNotation(t *testi }, Interval: metav1.Duration{Duration: interval}, Timeout: &metav1.Duration{Duration: timeout}, + Provider: sourcev1beta2.GenericOCIProvider, }, } @@ -1718,6 +1726,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceTrustPolicyNotation(t *tes }, Interval: metav1.Duration{Duration: interval}, Timeout: &metav1.Duration{Duration: timeout}, + Provider: sourcev1beta2.GenericOCIProvider, }, } @@ -2042,6 +2051,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignatureCosign(t *testing }, Interval: metav1.Duration{Duration: interval}, Timeout: &metav1.Duration{Duration: timeout}, + Provider: sourcev1beta2.GenericOCIProvider, }, } @@ -2265,6 +2275,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature_keyless(t *testi }, Interval: metav1.Duration{Duration: interval}, Timeout: &metav1.Duration{Duration: timeout}, + Provider: sourcev1beta2.GenericOCIProvider, Reference: tt.reference, }, } @@ -2448,6 +2459,7 @@ func TestOCIRepository_reconcileSource_noop(t *testing.T) { Reference: &ociv1.OCIRepositoryRef{Tag: "6.1.5"}, Interval: metav1.Duration{Duration: interval}, Timeout: &metav1.Duration{Duration: timeout}, + Provider: sourcev1beta2.GenericOCIProvider, Insecure: true, }, } diff --git a/internal/helm/getter/client_opts.go b/internal/helm/getter/client_opts.go index c305b7385..4716b86fc 100644 --- a/internal/helm/getter/client_opts.go +++ b/internal/helm/getter/client_opts.go @@ -64,13 +64,17 @@ func (o ClientOpts) MustLoginToRegistry() bool { return len(o.RegLoginOpts) > 0 && o.RegLoginOpts[0] != nil } -// GetClientOpts uses the provided HelmRepository object and a normalized +// GetClientOptsWithOIDCAuth uses the provided HelmRepository object and a normalized // URL to construct a HelmClientOpts object. If obj is an OCI HelmRepository, // then the returned options object will also contain the required registry // auth mechanisms. // A temporary directory is created to store the certs files if needed and its path is returned along with the options object. It is the // caller's responsibility to clean up the directory. -func GetClientOpts(ctx context.Context, c client.Client, obj *sourcev1.HelmRepository, url string) (*ClientOpts, string, error) { +func GetClientOptsWithOIDCAuth(ctx context.Context, c client.Client, oidcAuthenticator *soci.OIDCAuthenticator, + obj *sourcev1.HelmRepository, url string) (*ClientOpts, string, error) { + if obj.Spec.Provider != sourcev1beta2.GenericOCIProvider && oidcAuthenticator == nil { + return nil, "", fmt.Errorf("OIDC authenticator is not configured") + } hrOpts := &ClientOpts{ GetterOpts: []helmgetter.Option{ helmgetter.WithURL(url), @@ -137,7 +141,7 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *sourcev1.HelmRepos } } } else if obj.Spec.Provider != sourcev1beta2.GenericOCIProvider && obj.Spec.Type == sourcev1.HelmRepositoryTypeOCI && ociRepo { - authenticator, authErr := soci.OIDCAuth(ctx, obj.Spec.URL, obj.Spec.Provider) + authenticator, authErr := oidcAuthenticator.Authorization(ctx, obj.Spec.URL, obj.Spec.Provider) if authErr != nil && !errors.Is(authErr, oci.ErrUnconfiguredProvider) { return nil, "", fmt.Errorf("failed to get credential from '%s': %w", obj.Spec.Provider, authErr) } @@ -179,6 +183,16 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *sourcev1.HelmRepos return hrOpts, dir, err } +// GetClientOpts uses the provided HelmRepository object and a normalized +// URL to construct a HelmClientOpts object. If obj is an OCI HelmRepository, +// then the returned options object will also contain the required registry +// auth mechanisms. +// A temporary directory is created to store the certs files if needed and its path is returned along with the options object. It is the +// caller's responsibility to clean up the directory. +func GetClientOpts(ctx context.Context, c client.Client, obj *sourcev1.HelmRepository, url string) (*ClientOpts, string, error) { + return GetClientOptsWithOIDCAuth(ctx, c, nil, obj, url) +} + func fetchSecret(ctx context.Context, c client.Client, name, namespace string) (*corev1.Secret, error) { key := types.NamespacedName{ Namespace: namespace, diff --git a/internal/helm/getter/client_opts_test.go b/internal/helm/getter/client_opts_test.go index b8bf15f28..e59a03acc 100644 --- a/internal/helm/getter/client_opts_test.go +++ b/internal/helm/getter/client_opts_test.go @@ -30,6 +30,7 @@ import ( fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" helmv1 "github.com/fluxcd/source-controller/api/v1" + sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" ) func TestGetClientOpts(t *testing.T) { @@ -143,6 +144,7 @@ func TestGetClientOpts(t *testing.T) { Duration: time.Second, }, Insecure: tt.insecure, + Provider: sourcev1beta2.GenericOCIProvider, }, } if tt.oci { @@ -249,7 +251,8 @@ func TestGetClientOpts_registryTLSLoginOption(t *testing.T) { Timeout: &metav1.Duration{ Duration: time.Second, }, - Type: helmv1.HelmRepositoryTypeOCI, + Provider: sourcev1beta2.GenericOCIProvider, + Type: helmv1.HelmRepositoryTypeOCI, }, } diff --git a/internal/oci/auth.go b/internal/oci/auth.go index 7b3eab896..9e9b2edc0 100644 --- a/internal/oci/auth.go +++ b/internal/oci/auth.go @@ -20,7 +20,9 @@ import ( "context" "fmt" "strings" + "time" + "github.com/fluxcd/pkg/cache" "github.com/fluxcd/pkg/oci/auth/login" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" @@ -28,6 +30,19 @@ import ( sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" ) +const ( + // We want to cache the authenticators for the 3rd party providers + // There are at least 3 providers (aws, azure, gcp), but there could be more + // e.g. alibaba, ibm, etc. But realistically, we can expect the number of + // providers to be less than 10. + DefaultAuthCacheCapacity = 10 + // The cache cleanup interval, to remove expired entries + // 1 minute is a reasonable interval for authentication tokens. + // We don't want to be aggressive with the cleanup, as the tokens + // are valid for a longer period of time usually. + defaultAuthCacheInterval = time.Minute +) + // Anonymous is an authn.AuthConfig that always returns an anonymous // authenticator. It is useful for registries that do not require authentication // or when the credentials are not known. @@ -39,15 +54,61 @@ func (a Anonymous) Resolve(_ authn.Resource) (authn.Authenticator, error) { return authn.Anonymous, nil } -// OIDCAuth generates the OIDC credential authenticator based on the specified cloud provider. -func OIDCAuth(ctx context.Context, url, provider string) (authn.Authenticator, error) { +// OIDCAuthenticatorOptionFunc is a functional option for the OIDCAuthenticator. +type OIDCAuthenticatorOptionFunc func(opts *oidcAuthenticatorOptions) + +type oidcAuthenticatorOptions struct { + capacity int +} + +// WithCacheCapacity sets the capacity of the cache. +func WithCacheCapacity(capacity int) OIDCAuthenticatorOptionFunc { + return func(opts *oidcAuthenticatorOptions) { + opts.capacity = capacity + } +} + +// OIDCAuthenticator holds a manager for the OIDC authenticators. +// It caches the authenticators to avoid re-authenticating for the same URL. +type OIDCAuthenticator struct { + manager *login.Manager + cache cache.Expirable[cache.StoreObject[authn.Authenticator]] +} + +// NewOIDCAuthenticator returns a new OIDCAuthenticator. +// The capacity is the number of authenticators to cache. +// If the capacity is less than or equal to 0, the cache is disabled. +func NewOIDCAuthenticator(opts ...OIDCAuthenticatorOptionFunc) (*OIDCAuthenticator, error) { + o := &oidcAuthenticatorOptions{} + for _, opt := range opts { + opt(o) + } + + var ( + c cache.Expirable[cache.StoreObject[authn.Authenticator]] + err error + ) + if o.capacity > 0 { + c, err = cache.New(o.capacity, cache.StoreObjectKeyFunc, + cache.WithCleanupInterval[cache.StoreObject[authn.Authenticator]](defaultAuthCacheInterval)) + if err != nil { + return nil, fmt.Errorf("failed to create cache: %w", err) + } + } + + manager := login.NewManager() + return &OIDCAuthenticator{cache: c, manager: manager}, nil +} + +// Authorization returns an authenticator for the OIDC credentials. +func (o *OIDCAuthenticator) Authorization(ctx context.Context, url, provider string) (authn.Authenticator, error) { u := strings.TrimPrefix(url, sourcev1.OCIRepositoryPrefix) ref, err := name.ParseReference(u) if err != nil { return nil, fmt.Errorf("failed to parse URL '%s': %w", u, err) } - opts := login.ProviderOptions{} + opts := login.ProviderOptions{Cache: o.cache} switch provider { case sourcev1.AmazonOCIProvider: opts.AwsAutoLogin = true @@ -57,5 +118,26 @@ func OIDCAuth(ctx context.Context, url, provider string) (authn.Authenticator, e opts.GcpAutoLogin = true } - return login.NewManager().Login(ctx, u, ref, opts) + return o.manager.Login(ctx, u, ref, opts) } + +// OIDCAuth generates the OIDC credential authenticator based on the specified cloud provider. +// func OIDCAuth(ctx context.Context, url, provider string) (authn.Authenticator, error) { +// u := strings.TrimPrefix(url, sourcev1.OCIRepositoryPrefix) +// ref, err := name.ParseReference(u) +// if err != nil { +// return nil, fmt.Errorf("failed to parse URL '%s': %w", u, err) +// } + +// opts := login.ProviderOptions{} +// switch provider { +// case sourcev1.AmazonOCIProvider: +// opts.AwsAutoLogin = true +// case sourcev1.AzureOCIProvider: +// opts.AzureAutoLogin = true +// case sourcev1.GoogleOCIProvider: +// opts.GcpAutoLogin = true +// } + +// return login.NewManager().Login(ctx, u, ref, opts) +// } diff --git a/main.go b/main.go index a0abb7c8c..95b20a312 100644 --- a/main.go +++ b/main.go @@ -50,7 +50,7 @@ import ( "github.com/fluxcd/pkg/runtime/pprof" "github.com/fluxcd/pkg/runtime/probes" - "github.com/fluxcd/source-controller/api/v1" + v1 "github.com/fluxcd/source-controller/api/v1" "github.com/fluxcd/source-controller/api/v1beta2" // +kubebuilder:scaffold:imports @@ -61,6 +61,7 @@ import ( "github.com/fluxcd/source-controller/internal/features" "github.com/fluxcd/source-controller/internal/helm" "github.com/fluxcd/source-controller/internal/helm/registry" + "github.com/fluxcd/source-controller/internal/oci" ) const controllerName = "source-controller" @@ -187,6 +188,8 @@ func main() { mustSetupHelmLimits(helmIndexLimit, helmChartLimit, helmChartFileLimit) helmIndexCache, helmIndexCacheItemTTL := mustInitHelmCache(helmCacheMaxSize, helmCacheTTL, helmCachePurgeInterval) + authenticator := mustInitOIDCAuthenticator() + ctx := ctrl.SetupSignalHandler() if err := (&controller.GitRepositoryReconciler{ @@ -228,6 +231,7 @@ func main() { EventRecorder: eventRecorder, Metrics: metrics, ControllerName: controllerName, + OIDCAuthenticator: authenticator, Cache: helmIndexCache, TTL: helmIndexCacheItemTTL, CacheRecorder: cacheRecorder, @@ -252,11 +256,12 @@ func main() { } if err := (&controller.OCIRepositoryReconciler{ - Client: mgr.GetClient(), - Storage: storage, - EventRecorder: eventRecorder, - ControllerName: controllerName, - Metrics: metrics, + Client: mgr.GetClient(), + Storage: storage, + EventRecorder: eventRecorder, + ControllerName: controllerName, + Metrics: metrics, + OIDCAuthenticator: authenticator, }).SetupWithManagerAndOptions(mgr, controller.OCIRepositoryReconcilerOptions{ RateLimiter: helper.GetRateLimiter(rateLimiterOptions), }); err != nil { @@ -406,6 +411,20 @@ func mustInitHelmCache(maxSize int, itemTTL, purgeInterval string) (*cache.Cache return cache.New(maxSize, interval), ttl } +func mustInitOIDCAuthenticator() *oci.OIDCAuthenticator { + capacity := oci.DefaultAuthCacheCapacity + enabled, found := os.LookupEnv("LOGIN_CACHE_DISABLED") + if found && enabled == "true" { + capacity = -1 + } + authenticator, err := oci.NewOIDCAuthenticator(oci.WithCacheCapacity(capacity)) + if err != nil { + setupLog.Error(err, "unable to initialise OIDC authenticator") + os.Exit(1) + } + return authenticator +} + func mustInitStorage(path string, storageAdvAddr string, artifactRetentionTTL time.Duration, artifactRetentionRecords int, artifactDigestAlgo string) *controller.Storage { if storageAdvAddr == "" { storageAdvAddr = determineAdvStorageAddr(storageAdvAddr)