Skip to content

Commit e253855

Browse files
authored
Merge pull request #1790 from fluxcd/rfc-0010-oci
[RFC-0010] Introduce object-level workload identity for OCIRepository
2 parents 9f36f29 + e128d3b commit e253855

10 files changed

+72
-59
lines changed

config/rbac/role.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ rules:
1919
- get
2020
- list
2121
- watch
22+
- apiGroups:
23+
- ""
24+
resources:
25+
- serviceaccounts/token
26+
verbs:
27+
- create
2228
- apiGroups:
2329
- source.toolkit.fluxcd.io
2430
resources:

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ 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.12.0
2828
github.com/fluxcd/pkg/cache v0.9.0
29-
github.com/fluxcd/pkg/git v0.28.0
30-
github.com/fluxcd/pkg/git/gogit v0.30.0
29+
github.com/fluxcd/pkg/git v0.29.0
30+
github.com/fluxcd/pkg/git/gogit v0.31.0
3131
github.com/fluxcd/pkg/gittestserver v0.17.0
3232
github.com/fluxcd/pkg/helmtestserver v0.24.0
3333
github.com/fluxcd/pkg/lockedfile v0.6.0
3434
github.com/fluxcd/pkg/masktoken v0.7.0
35-
github.com/fluxcd/pkg/oci v0.47.0
35+
github.com/fluxcd/pkg/oci v0.48.0
3636
github.com/fluxcd/pkg/runtime v0.59.0
3737
github.com/fluxcd/pkg/sourceignore v0.12.0
3838
github.com/fluxcd/pkg/ssh v0.18.0

go.sum

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -374,14 +374,14 @@ 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.12.0 h1:35o0ziYMLZVgJwNvJBGsv/wd903B2fMagcrnm1ptUjc=
378+
github.com/fluxcd/pkg/auth v0.12.0/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=
381-
github.com/fluxcd/pkg/git v0.28.0 h1:by7XTOvj4ZUPH1alYMJtDCVryhHue+UfjhrnPuJt5vA=
382-
github.com/fluxcd/pkg/git v0.28.0/go.mod h1:VPv6O3mYnYvn79LOdWAFCl4fE8o651cxW/p/yxBoq2g=
383-
github.com/fluxcd/pkg/git/gogit v0.30.0 h1:tdKRT4EDV8Cc2tBX+bg4H4gdcND7M4OEl6DQy1jSJmo=
384-
github.com/fluxcd/pkg/git/gogit v0.30.0/go.mod h1:UCm/fOBuvX43BNz7Rc61Sukp2gBG/qxlOASaBkwMFvc=
381+
github.com/fluxcd/pkg/git v0.29.0 h1:MHQ4F53e6Xt8a/POkd/fiChgysnd/XqiuK7vOWXAXLk=
382+
github.com/fluxcd/pkg/git v0.29.0/go.mod h1:Ygn+LfrK6Ok+85uiq6s3NWG5LcHS4KY7mzES2JDJsGY=
383+
github.com/fluxcd/pkg/git/gogit v0.31.0 h1:A56cmtgJBkWAj+gXSOdhPMQVTx0VF91S0PUaqpMXN4g=
384+
github.com/fluxcd/pkg/git/gogit v0.31.0/go.mod h1:ya8z22xTvAAdW12HycxKYv4S+G+lqu5Kx/LyO/jWz8Y=
385385
github.com/fluxcd/pkg/gittestserver v0.17.0 h1:JlBvWZQTDOI+np5Z+084m3DkeAH1hMusEybyRUDF63k=
386386
github.com/fluxcd/pkg/gittestserver v0.17.0/go.mod h1:E/40EmLoXcMqd6gLuLDC9F6KJxqHVGbBBeMNKk5XdxU=
387387
github.com/fluxcd/pkg/helmtestserver v0.24.0 h1:9sSfRG17GnDIup4sI8V+fdvKROtunU4JyIo34uvXq3Q=
@@ -390,8 +390,8 @@ github.com/fluxcd/pkg/lockedfile v0.6.0 h1:64RRMiPv3ZK9Y4sjI8c78kZAdfEo+Sjr2iP8a
390390
github.com/fluxcd/pkg/lockedfile v0.6.0/go.mod h1:gpdUVm7+05NIT1ZvzuNnHfnT81OhZtIySlxxkZ68pXk=
391391
github.com/fluxcd/pkg/masktoken v0.7.0 h1:pitmyOg2pUVdW+nn2Lk/xqm2TaA08uxvOC0ns3sz6bM=
392392
github.com/fluxcd/pkg/masktoken v0.7.0/go.mod h1:Lc1uoDjO1GY6+YdkK+ZqqBIBWquyV58nlSJ5S1N1IYU=
393-
github.com/fluxcd/pkg/oci v0.47.0 h1:eQ7syqy91Xcfd7Sgf64v5n+dfRAju/OBiXuOhZsgQAg=
394-
github.com/fluxcd/pkg/oci v0.47.0/go.mod h1:XBnI8+T6YFnIW4uEFojg7iIgHjKH7LXMpZARXJ9qmZk=
393+
github.com/fluxcd/pkg/oci v0.48.0 h1:iSK4JDM0nx9plSlOGx2aI4td6aQdV/awrfXK/bzI35I=
394+
github.com/fluxcd/pkg/oci v0.48.0/go.mod h1:rnUC8EOpzQp4rugpmopYFMnG3+CR1wqEV3356gHUtSY=
395395
github.com/fluxcd/pkg/runtime v0.59.0 h1:3OrFkMJB39NcQ2vhhoxqls59sQVSn8U+thhyLbsQoA4=
396396
github.com/fluxcd/pkg/runtime v0.59.0/go.mod h1:MFbfyNyyoYRgPxpdwC9/dCOkzo7Yxhu/cQ9NKyhvqc0=
397397
github.com/fluxcd/pkg/sourceignore v0.12.0 h1:jCIe6d50rQ3wdXPF0+PhhqN0XrTRIq3upMomPelI8Mw=

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: 33 additions & 5 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
@@ -175,6 +178,7 @@ func (r *OCIRepositoryReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, o
175178
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=ocirepositories/status,verbs=get;update;patch
176179
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=ocirepositories/finalizers,verbs=get;create;update;patch;delete
177180
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
181+
// +kubebuilder:rbac:groups="",resources=serviceaccounts/token,verbs=create
178182

179183
func (r *OCIRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
180184
start := time.Now()
@@ -328,7 +332,7 @@ func (r *OCIRepositoryReconciler) reconcile(ctx context.Context, sp *patch.Seria
328332
// If this fails, it records v1beta2.FetchFailedCondition=True on the object and returns early.
329333
func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch.SerialPatcher,
330334
obj *ociv1.OCIRepository, metadata *sourcev1.Artifact, dir string) (sreconcile.Result, error) {
331-
var auth authn.Authenticator
335+
var authenticator authn.Authenticator
332336

333337
ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
334338
defer cancel()
@@ -363,9 +367,29 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
363367
}
364368

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

389-
opts := makeRemoteOptions(ctx, transport, keychain, auth)
413+
opts := makeRemoteOptions(ctx, transport, keychain, authenticator)
390414

391415
// Determine which artifact revision to pull
392416
ref, err := r.getArtifactRef(obj, opts)
@@ -446,7 +470,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
446470
conditions.GetObservedGeneration(obj, sourcev1.SourceVerifiedCondition) != obj.Generation ||
447471
conditions.IsFalse(obj, sourcev1.SourceVerifiedCondition) {
448472

449-
result, err := r.verifySignature(ctx, obj, ref, keychain, auth, transport, opts...)
473+
result, err := r.verifySignature(ctx, obj, ref, keychain, authenticator, transport, opts...)
450474
if err != nil {
451475
provider := obj.Spec.Verify.Provider
452476
if obj.Spec.Verify.SecretRef == nil && obj.Spec.Verify.Provider == "cosign" {
@@ -1225,6 +1249,10 @@ func (r *OCIRepositoryReconciler) reconcileDelete(ctx context.Context, obj *ociv
12251249
// Remove our finalizer from the list
12261250
controllerutil.RemoveFinalizer(obj, sourcev1.SourceFinalizer)
12271251

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

internal/controller/ocirepository_controller_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -872,9 +872,9 @@ func TestOCIRepository_CertSecret(t *testing.T) {
872872

873873
tlsSecretClientCert := corev1.Secret{
874874
Data: map[string][]byte{
875-
oci.CACert: tlsCA,
876-
oci.ClientCert: clientPublicKey,
877-
oci.ClientKey: clientPrivateKey,
875+
"caFile": tlsCA,
876+
"certFile": clientPublicKey,
877+
"keyFile": clientPrivateKey,
878878
},
879879
}
880880

@@ -907,9 +907,9 @@ func TestOCIRepository_CertSecret(t *testing.T) {
907907
digest: pi.digest,
908908
certSecret: &corev1.Secret{
909909
Data: map[string][]byte{
910-
oci.CACert: tlsCA,
911-
oci.ClientCert: clientPublicKey,
912-
oci.ClientKey: []byte("invalid-key"),
910+
"caFile": tlsCA,
911+
"certFile": clientPublicKey,
912+
"keyFile": []byte("invalid-key"),
913913
},
914914
},
915915
expectreadyconition: false,

internal/helm/getter/client_opts.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"os"
2525
"path"
2626

27-
"github.com/fluxcd/pkg/oci"
2827
"github.com/google/go-containerregistry/pkg/authn"
2928
helmgetter "helm.sh/helm/v3/pkg/getter"
3029
helmreg "helm.sh/helm/v3/pkg/registry"
@@ -137,8 +136,8 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *sourcev1.HelmRepos
137136
}
138137
}
139138
} 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)
141-
if authErr != nil && !errors.Is(authErr, oci.ErrUnconfiguredProvider) {
139+
authenticator, authErr := soci.OIDCAuth(ctx, obj.Spec.URL, obj.Spec.Provider)
140+
if authErr != nil {
142141
return nil, "", fmt.Errorf("failed to get credential from '%s': %w", obj.Spec.Provider, authErr)
143142
}
144143
if authenticator != nil {

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)