From f9fc32702da5ebd811130a3cfa7aeb4e711aecae Mon Sep 17 00:00:00 2001 From: Dipti Pai Date: Fri, 15 Aug 2025 12:41:20 -0700 Subject: [PATCH] [RFC-0010] Add multi-tenant workload identity support for ImageUpdateAutomation with Azure GitRepository Signed-off-by: Dipti Pai --- go.mod | 2 ++ go.sum | 4 ++-- .../controller/imageupdateautomation_controller.go | 10 +++++++++- internal/source/git.go | 8 ++++++++ internal/source/git_test.go | 9 +++++++++ internal/source/source.go | 3 +++ 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c82e53f..7980e4d 100644 --- a/go.mod +++ b/go.mod @@ -198,3 +198,5 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect sigs.k8s.io/yaml v1.5.0 // indirect ) + +replace github.com/fluxcd/source-controller/api => github.com/dipti-pai/source-controller/api v0.0.0-20250815055530-ae30fe521c22 diff --git a/go.sum b/go.sum index 631452e..5adf215 100644 --- a/go.sum +++ b/go.sum @@ -102,6 +102,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dipti-pai/source-controller/api v0.0.0-20250815055530-ae30fe521c22 h1:cQx98gGW+xPQHh/rxa16qTbEUKjYwtA4xKX6/Nw/0Cg= +github.com/dipti-pai/source-controller/api v0.0.0-20250815055530-ae30fe521c22/go.mod h1:MGk/yprMK9SjHbzAT89PUsGV8WnUmpbCiqxbW+K/e+w= github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A= github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= @@ -150,8 +152,6 @@ github.com/fluxcd/pkg/ssh v0.20.0 h1:Ak0laIYIc/L8lEfqls/LDWRW8wYPESGaravQsCRGLb8 github.com/fluxcd/pkg/ssh v0.20.0/go.mod h1:sRfAAkxx1GwCGjYirKPnTKdNkNrJRo9kqzWLVFXKv7E= github.com/fluxcd/pkg/version v0.9.0 h1:pQBHMt9TbnnTUzj3EoMhRi5JUkNBqrTBSAaoLG1ovUA= github.com/fluxcd/pkg/version v0.9.0/go.mod h1:JU6/UwNbGeMm4gqeyUn/dxl+qwLTi2+X10xpfgWdt9I= -github.com/fluxcd/source-controller/api v1.6.1 h1:ZPTA9lNzBYHmwHfFX978qb8xVkdnQZHF1ggo6BoFm4w= -github.com/fluxcd/source-controller/api v1.6.1/go.mod h1:ZJcAi0nemsnBxjVgmJl0WQzNvB0rMETxQMTdoFosmMw= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= diff --git a/internal/controller/imageupdateautomation_controller.go b/internal/controller/imageupdateautomation_controller.go index fc72529..e42eb78 100644 --- a/internal/controller/imageupdateautomation_controller.go +++ b/internal/controller/imageupdateautomation_controller.go @@ -43,6 +43,7 @@ import ( aclapi "github.com/fluxcd/pkg/apis/acl" eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/auth" "github.com/fluxcd/pkg/cache" "github.com/fluxcd/pkg/git" "github.com/fluxcd/pkg/runtime/acl" @@ -372,6 +373,13 @@ func (r *ImageUpdateAutomationReconciler) reconcile(ctx context.Context, sp *pat result, retErr = ctrl.Result{}, nil return } + if errors.Is(err, source.ErrFeatureGateNotEnabled) { + const gate = auth.FeatureGateObjectLevelWorkloadIdentity + const msgFmt = "to use spec.serviceAccountName for provider authentication please enable the %s feature gate in the controller" + conditions.MarkStalled(obj, meta.FeatureGateDisabledReason, msgFmt, gate) + result, retErr = ctrl.Result{}, nil + return + } e := fmt.Errorf("failed configuring source manager: %w", err) conditions.MarkFalse(obj, meta.ReadyCondition, imagev1.SourceManagerFailedReason, "%s", e) result, retErr = ctrl.Result{}, e @@ -383,7 +391,7 @@ func (r *ImageUpdateAutomationReconciler) reconcile(ctx context.Context, sp *pat } }() // Update any stale Ready=False condition from SourceManager failure. - if conditions.HasAnyReason(obj, meta.ReadyCondition, aclapi.AccessDeniedCondition, imagev1.InvalidSourceConfigReason, imagev1.SourceManagerFailedReason) { + if conditions.HasAnyReason(obj, meta.ReadyCondition, aclapi.AccessDeniedCondition, imagev1.InvalidSourceConfigReason, imagev1.SourceManagerFailedReason, meta.FeatureGateDisabledReason) { conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress") } diff --git a/internal/source/git.go b/internal/source/git.go index 995a0ae..249f7bf 100644 --- a/internal/source/git.go +++ b/internal/source/git.go @@ -209,6 +209,14 @@ func getAuthOpts(ctx context.Context, c client.Client, repo *sourcev1.GitReposit auth.WithServiceAccountNamespace(srcOpts.objNamespace), } + if repo.Spec.ServiceAccountName != "" { + // Check object-level workload identity feature gate. + if !auth.IsObjectLevelWorkloadIdentityEnabled() { + return nil, ErrFeatureGateNotEnabled + } + opts = append(opts, auth.WithServiceAccountName(repo.Spec.ServiceAccountName)) + } + if srcOpts.tokenCache != nil { involvedObject := cache.InvolvedObject{ Kind: imagev1.ImageUpdateAutomationKind, diff --git a/internal/source/git_test.go b/internal/source/git_test.go index ccda779..8f1d8ca 100644 --- a/internal/source/git_test.go +++ b/internal/source/git_test.go @@ -155,6 +155,15 @@ func Test_getAuthOpts_providerAuth(t *testing.T) { }, wantErr: "ManagedIdentityCredential", }, + { + name: "azure provider with service account and feature gate for object-level identity disabled", + url: "https://dev.azure.com/foo/bar/_git/baz", + beforeFunc: func(obj *sourcev1.GitRepository) { + obj.Spec.Provider = sourcev1.GitProviderAzure + obj.Spec.ServiceAccountName = "azure-sa" + }, + wantErr: ErrFeatureGateNotEnabled.Error(), + }, { name: "github provider with no secret ref", url: "https://github.com/org/repo.git", diff --git a/internal/source/source.go b/internal/source/source.go index 4465db3..6b8aaf5 100644 --- a/internal/source/source.go +++ b/internal/source/source.go @@ -48,6 +48,9 @@ import ( // ErrInvalidSourceConfiguration is an error for invalid source configuration. var ErrInvalidSourceConfiguration = errors.New("invalid source configuration") +// ErrFeatureGateNotEnabled is an error for when a required feature gate is not enabled. +var ErrFeatureGateNotEnabled = errors.New("required feature gate not enabled") + // RemovedTemplateFieldError represents an error when a removed template field is used. type RemovedTemplateFieldError struct { Field string