Skip to content

Commit 1f5fa0b

Browse files
committed
[RFC-0010] Introduce object-level workload identity for container registry APIs
Signed-off-by: Matheus Pimenta <[email protected]>
1 parent 9f36f29 commit 1f5fa0b

File tree

8 files changed

+48
-41
lines changed

8 files changed

+48
-41
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ require (
2424
github.com/fluxcd/cli-utils v0.36.0-flux.13
2525
github.com/fluxcd/pkg/apis/event v0.17.0
2626
github.com/fluxcd/pkg/apis/meta v1.11.0
27-
github.com/fluxcd/pkg/auth v0.11.0
27+
github.com/fluxcd/pkg/auth v0.11.1-0.20250504212336-3c3c3cab892e
2828
github.com/fluxcd/pkg/cache v0.9.0
2929
github.com/fluxcd/pkg/git v0.28.0
3030
github.com/fluxcd/pkg/git/gogit v0.30.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,8 @@ github.com/fluxcd/pkg/apis/event v0.17.0 h1:foEINE++pCJlWVhWjYDXfkVmGKu8mQ4BDBlb
374374
github.com/fluxcd/pkg/apis/event v0.17.0/go.mod h1:0fLhLFiHlRTDKPDXdRnv+tS7mCMIQ0fJxnEfmvGM/5A=
375375
github.com/fluxcd/pkg/apis/meta v1.11.0 h1:h8q95k6ZEK1HCfsLkt8Np3i6ktb6ZzcWJ6hg++oc9w0=
376376
github.com/fluxcd/pkg/apis/meta v1.11.0/go.mod h1:+son1Va60x2eiDcTwd7lcctbI6C+K3gM7R+ULmEq1SI=
377-
github.com/fluxcd/pkg/auth v0.11.0 h1:1BC6fQ71lCLFKz7juGlvWq9ysR2HVl5JPOWoxy4RMWE=
378-
github.com/fluxcd/pkg/auth v0.11.0/go.mod h1:BJVrbanLH0AoUBzOH7u016D21Zl3dvEd0AnAWVOo5Vs=
377+
github.com/fluxcd/pkg/auth v0.11.1-0.20250504212336-3c3c3cab892e h1:bYYHjibVjIJLKtTfYAijvfq1TZM50ZA16XuSfEurbrc=
378+
github.com/fluxcd/pkg/auth v0.11.1-0.20250504212336-3c3c3cab892e/go.mod h1:gQD2VT5OhIR1E8ZTEsTaho3bDQZidr9P10smH/awcew=
379379
github.com/fluxcd/pkg/cache v0.9.0 h1:EGKfOLMG3fOwWnH/4Axl5xd425mxoQbZzlZoLfd8PDk=
380380
github.com/fluxcd/pkg/cache v0.9.0/go.mod h1:jMwabjWfsC5lW8hE7NM3wtGNwSJ38Javx6EKbEi7INU=
381381
github.com/fluxcd/pkg/git v0.28.0 h1:by7XTOvj4ZUPH1alYMJtDCVryhHue+UfjhrnPuJt5vA=

internal/controller/gitrepository_controller.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -132,19 +132,17 @@ type GitRepositoryReconciler struct {
132132

133133
Storage *Storage
134134
ControllerName string
135+
TokenCache *cache.TokenCache
135136

136137
requeueDependency time.Duration
137138
features map[string]bool
138139

139140
patchOptions []patch.Option
140-
141-
tokenCache *cache.TokenCache
142141
}
143142

144143
type GitRepositoryReconcilerOptions struct {
145144
DependencyRequeueInterval time.Duration
146145
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
147-
TokenCache *cache.TokenCache
148146
}
149147

150148
// gitRepositoryReconcileFunc is the function type for all the
@@ -164,8 +162,6 @@ func (r *GitRepositoryReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, o
164162
r.features = features.FeatureGates()
165163
}
166164

167-
r.tokenCache = opts.TokenCache
168-
169165
return ctrl.NewControllerManagedBy(mgr).
170166
For(&sourcev1.GitRepository{}, builder.WithPredicates(
171167
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}),
@@ -689,14 +685,14 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
689685

690686
var authOpts []auth.Option
691687

692-
if r.tokenCache != nil {
688+
if r.TokenCache != nil {
693689
involvedObject := cache.InvolvedObject{
694690
Kind: sourcev1.GitRepositoryKind,
695691
Name: obj.GetName(),
696692
Namespace: obj.GetNamespace(),
697693
Operation: cache.OperationReconcile,
698694
}
699-
authOpts = append(authOpts, auth.WithCache(*r.tokenCache, involvedObject))
695+
authOpts = append(authOpts, auth.WithCache(*r.TokenCache, involvedObject))
700696
}
701697

702698
if proxyURL != nil {
@@ -726,7 +722,7 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
726722
GitHubOpts: []github.OptFunc{
727723
github.WithAppData(authData),
728724
github.WithProxyURL(proxyURL),
729-
github.WithCache(r.tokenCache, sourcev1.GitRepositoryKind,
725+
github.WithCache(r.TokenCache, sourcev1.GitRepositoryKind,
730726
obj.GetName(), obj.GetNamespace(), cache.OperationReconcile),
731727
},
732728
}
@@ -1150,7 +1146,7 @@ func (r *GitRepositoryReconciler) reconcileDelete(ctx context.Context, obj *sour
11501146
controllerutil.RemoveFinalizer(obj, sourcev1.SourceFinalizer)
11511147

11521148
// Cleanup caches.
1153-
r.tokenCache.DeleteEventsForObject(sourcev1.GitRepositoryKind,
1149+
r.TokenCache.DeleteEventsForObject(sourcev1.GitRepositoryKind,
11541150
obj.GetName(), obj.GetNamespace(), cache.OperationReconcile)
11551151

11561152
// Stop reconciliation as the object is being deleted

internal/controller/helmchart_controller.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ import (
7070
"github.com/fluxcd/source-controller/internal/helm/chart"
7171
"github.com/fluxcd/source-controller/internal/helm/getter"
7272
"github.com/fluxcd/source-controller/internal/helm/repository"
73-
"github.com/fluxcd/source-controller/internal/oci"
7473
soci "github.com/fluxcd/source-controller/internal/oci"
7574
scosign "github.com/fluxcd/source-controller/internal/oci/cosign"
7675
"github.com/fluxcd/source-controller/internal/oci/notation"
@@ -1255,7 +1254,7 @@ func observeChartBuild(ctx context.Context, sp *patch.SerialPatcher, pOpts []pat
12551254
if build.Complete() {
12561255
conditions.Delete(obj, sourcev1.FetchFailedCondition)
12571256
conditions.Delete(obj, sourcev1.BuildFailedCondition)
1258-
if build.VerifiedResult == oci.VerificationResultSuccess {
1257+
if build.VerifiedResult == soci.VerificationResultSuccess {
12591258
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of version %s", build.Version)
12601259
}
12611260
}

internal/controller/ocirepository_controller.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ import (
5151

5252
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
5353
"github.com/fluxcd/pkg/apis/meta"
54+
"github.com/fluxcd/pkg/auth"
55+
"github.com/fluxcd/pkg/cache"
5456
"github.com/fluxcd/pkg/oci"
5557
"github.com/fluxcd/pkg/runtime/conditions"
5658
helper "github.com/fluxcd/pkg/runtime/controller"
@@ -141,6 +143,7 @@ type OCIRepositoryReconciler struct {
141143

142144
Storage *Storage
143145
ControllerName string
146+
TokenCache *cache.TokenCache
144147
requeueDependency time.Duration
145148

146149
patchOptions []patch.Option
@@ -328,7 +331,7 @@ func (r *OCIRepositoryReconciler) reconcile(ctx context.Context, sp *patch.Seria
328331
// If this fails, it records v1beta2.FetchFailedCondition=True on the object and returns early.
329332
func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch.SerialPatcher,
330333
obj *ociv1.OCIRepository, metadata *sourcev1.Artifact, dir string) (sreconcile.Result, error) {
331-
var auth authn.Authenticator
334+
var authenticator authn.Authenticator
332335

333336
ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
334337
defer cancel()
@@ -363,8 +366,28 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
363366
}
364367

365368
if _, ok := keychain.(soci.Anonymous); obj.Spec.Provider != ociv1.GenericOCIProvider && ok {
369+
var opts []auth.Option
370+
if obj.Spec.ServiceAccountName != "" {
371+
serviceAccount := client.ObjectKey{
372+
Name: obj.Spec.ServiceAccountName,
373+
Namespace: obj.GetNamespace(),
374+
}
375+
opts = append(opts, auth.WithServiceAccount(serviceAccount, r.Client))
376+
}
377+
if r.TokenCache != nil {
378+
involvedObject := cache.InvolvedObject{
379+
Kind: ociv1.OCIRepositoryKind,
380+
Name: obj.GetName(),
381+
Namespace: obj.GetNamespace(),
382+
Operation: cache.OperationReconcile,
383+
}
384+
opts = append(opts, auth.WithCache(*r.TokenCache, involvedObject))
385+
}
386+
if proxyURL != nil {
387+
opts = append(opts, auth.WithProxyURL(*proxyURL))
388+
}
366389
var authErr error
367-
auth, authErr = soci.OIDCAuth(ctxTimeout, obj.Spec.URL, obj.Spec.Provider, proxyURL)
390+
authenticator, authErr = soci.OIDCAuth(ctxTimeout, obj.Spec.URL, obj.Spec.Provider, opts...)
368391
if authErr != nil && !errors.Is(authErr, oci.ErrUnconfiguredProvider) {
369392
e := serror.NewGeneric(
370393
fmt.Errorf("failed to get credential from %s: %w", obj.Spec.Provider, authErr),
@@ -386,7 +409,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
386409
return sreconcile.ResultEmpty, e
387410
}
388411

389-
opts := makeRemoteOptions(ctx, transport, keychain, auth)
412+
opts := makeRemoteOptions(ctx, transport, keychain, authenticator)
390413

391414
// Determine which artifact revision to pull
392415
ref, err := r.getArtifactRef(obj, opts)
@@ -446,7 +469,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
446469
conditions.GetObservedGeneration(obj, sourcev1.SourceVerifiedCondition) != obj.Generation ||
447470
conditions.IsFalse(obj, sourcev1.SourceVerifiedCondition) {
448471

449-
result, err := r.verifySignature(ctx, obj, ref, keychain, auth, transport, opts...)
472+
result, err := r.verifySignature(ctx, obj, ref, keychain, authenticator, transport, opts...)
450473
if err != nil {
451474
provider := obj.Spec.Verify.Provider
452475
if obj.Spec.Verify.SecretRef == nil && obj.Spec.Verify.Provider == "cosign" {
@@ -1225,6 +1248,10 @@ func (r *OCIRepositoryReconciler) reconcileDelete(ctx context.Context, obj *ociv
12251248
// Remove our finalizer from the list
12261249
controllerutil.RemoveFinalizer(obj, sourcev1.SourceFinalizer)
12271250

1251+
// Cleanup caches.
1252+
r.TokenCache.DeleteEventsForObject(ociv1.OCIRepositoryKind,
1253+
obj.GetName(), obj.GetNamespace(), cache.OperationReconcile)
1254+
12281255
// Stop reconciliation as the object is being deleted
12291256
return sreconcile.ResultEmpty, nil
12301257
}

internal/helm/getter/client_opts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *sourcev1.HelmRepos
137137
}
138138
}
139139
} else if obj.Spec.Provider != sourcev1beta2.GenericOCIProvider && obj.Spec.Type == sourcev1.HelmRepositoryTypeOCI && ociRepo {
140-
authenticator, authErr := soci.OIDCAuth(ctx, obj.Spec.URL, obj.Spec.Provider, nil)
140+
authenticator, authErr := soci.OIDCAuth(ctx, obj.Spec.URL, obj.Spec.Provider)
141141
if authErr != nil && !errors.Is(authErr, oci.ErrUnconfiguredProvider) {
142142
return nil, "", fmt.Errorf("failed to get credential from '%s': %w", obj.Spec.Provider, authErr)
143143
}

internal/oci/auth.go

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@ package oci
1818

1919
import (
2020
"context"
21-
"fmt"
22-
"net/url"
2321
"strings"
2422

25-
"github.com/fluxcd/pkg/oci/auth/login"
2623
"github.com/google/go-containerregistry/pkg/authn"
27-
"github.com/google/go-containerregistry/pkg/name"
24+
25+
"github.com/fluxcd/pkg/auth"
26+
authutils "github.com/fluxcd/pkg/auth/utils"
2827

2928
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
3029
)
@@ -41,22 +40,7 @@ func (a Anonymous) Resolve(_ authn.Resource) (authn.Authenticator, error) {
4140
}
4241

4342
// OIDCAuth generates the OIDC credential authenticator based on the specified cloud provider.
44-
func OIDCAuth(ctx context.Context, url, provider string, proxyURL *url.URL) (authn.Authenticator, error) {
43+
func OIDCAuth(ctx context.Context, url, provider string, opts ...auth.Option) (authn.Authenticator, error) {
4544
u := strings.TrimPrefix(url, sourcev1.OCIRepositoryPrefix)
46-
ref, err := name.ParseReference(u)
47-
if err != nil {
48-
return nil, fmt.Errorf("failed to parse URL '%s': %w", u, err)
49-
}
50-
51-
opts := login.ProviderOptions{}
52-
switch provider {
53-
case sourcev1.AmazonOCIProvider:
54-
opts.AwsAutoLogin = true
55-
case sourcev1.AzureOCIProvider:
56-
opts.AzureAutoLogin = true
57-
case sourcev1.GoogleOCIProvider:
58-
opts.GcpAutoLogin = true
59-
}
60-
61-
return login.NewManager(login.WithProxyURL(proxyURL)).Login(ctx, u, ref, opts)
45+
return authutils.GetArtifactRegistryCredentials(ctx, provider, u, opts...)
6246
}

main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,10 @@ func main() {
216216
Metrics: metrics,
217217
Storage: storage,
218218
ControllerName: controllerName,
219+
TokenCache: tokenCache,
219220
}).SetupWithManagerAndOptions(mgr, controller.GitRepositoryReconcilerOptions{
220221
DependencyRequeueInterval: requeueDependency,
221222
RateLimiter: helper.GetRateLimiter(rateLimiterOptions),
222-
TokenCache: tokenCache,
223223
}); err != nil {
224224
setupLog.Error(err, "unable to create controller", "controller", v1.GitRepositoryKind)
225225
os.Exit(1)
@@ -278,6 +278,7 @@ func main() {
278278
Storage: storage,
279279
EventRecorder: eventRecorder,
280280
ControllerName: controllerName,
281+
TokenCache: tokenCache,
281282
Metrics: metrics,
282283
}).SetupWithManagerAndOptions(mgr, controller.OCIRepositoryReconcilerOptions{
283284
RateLimiter: helper.GetRateLimiter(rateLimiterOptions),

0 commit comments

Comments
 (0)