diff --git a/Makefile b/Makefile index 18af2ee1..b390a96a 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,7 @@ dev-deploy: manifests # Generate manifests e.g. CRD, RBAC etc. manifests: controller-gen + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role paths="./..." output:crd:artifacts:config="config/crd/bases" cd api; $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role paths="./..." output:crd:artifacts:config="../config/crd/bases" # Generate API reference documentation diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index be8ab82c..04e1e279 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -1,21 +1,9 @@ - --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - creationTimestamp: null name: manager-role rules: -- apiGroups: - - "" - resources: - - namespaces - - secrets - - serviceaccounts - verbs: - - get - - list - - watch - apiGroups: - "" resources: @@ -24,28 +12,25 @@ rules: - create - patch - apiGroups: - - image.toolkit.fluxcd.io + - "" resources: - - imagepolicies + - namespaces + - secrets + - serviceaccounts verbs: - - create - - delete - get - list - - patch - - update - watch - apiGroups: - - image.toolkit.fluxcd.io + - "" resources: - - imagepolicies/status + - serviceaccounts/token verbs: - - get - - patch - - update + - create - apiGroups: - image.toolkit.fluxcd.io resources: + - imagepolicies - imagerepositories verbs: - create @@ -58,6 +43,7 @@ rules: - apiGroups: - image.toolkit.fluxcd.io resources: + - imagepolicies/status - imagerepositories/status verbs: - get diff --git a/docs/spec/v1beta2/imagerepositories.md b/docs/spec/v1beta2/imagerepositories.md index 372e79aa..fd6e3147 100644 --- a/docs/spec/v1beta2/imagerepositories.md +++ b/docs/spec/v1beta2/imagerepositories.md @@ -208,7 +208,7 @@ metadata: namespace: default spec: interval: 5m0s - url: example.com + image: example.com certSecretRef: name: example-tls --- @@ -252,7 +252,7 @@ metadata: namespace: default spec: interval: 5m0s - url: example.com + image: example.com proxySecretRef: name: http-proxy --- diff --git a/go.mod b/go.mod index 06d39e0a..18b5fe23 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,8 @@ require ( github.com/fluxcd/pkg/apis/acl v0.7.0 github.com/fluxcd/pkg/apis/event v0.17.0 github.com/fluxcd/pkg/apis/meta v1.11.0 - github.com/fluxcd/pkg/oci v0.47.0 + github.com/fluxcd/pkg/auth v0.12.0 + github.com/fluxcd/pkg/cache v0.9.0 github.com/fluxcd/pkg/runtime v0.59.0 github.com/fluxcd/pkg/version v0.7.0 github.com/google/go-containerregistry v0.20.3 @@ -132,7 +133,9 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.63.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cobra v1.9.1 // indirect github.com/vbatts/tar-split v0.12.1 // indirect diff --git a/go.sum b/go.sum index 7921668b..65a476c1 100644 --- a/go.sum +++ b/go.sum @@ -107,6 +107,8 @@ github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRcc github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk= +github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -161,8 +163,10 @@ github.com/fluxcd/pkg/apis/event v0.17.0 h1:foEINE++pCJlWVhWjYDXfkVmGKu8mQ4BDBlb github.com/fluxcd/pkg/apis/event v0.17.0/go.mod h1:0fLhLFiHlRTDKPDXdRnv+tS7mCMIQ0fJxnEfmvGM/5A= github.com/fluxcd/pkg/apis/meta v1.11.0 h1:h8q95k6ZEK1HCfsLkt8Np3i6ktb6ZzcWJ6hg++oc9w0= github.com/fluxcd/pkg/apis/meta v1.11.0/go.mod h1:+son1Va60x2eiDcTwd7lcctbI6C+K3gM7R+ULmEq1SI= -github.com/fluxcd/pkg/oci v0.47.0 h1:eQ7syqy91Xcfd7Sgf64v5n+dfRAju/OBiXuOhZsgQAg= -github.com/fluxcd/pkg/oci v0.47.0/go.mod h1:XBnI8+T6YFnIW4uEFojg7iIgHjKH7LXMpZARXJ9qmZk= +github.com/fluxcd/pkg/auth v0.12.0 h1:35o0ziYMLZVgJwNvJBGsv/wd903B2fMagcrnm1ptUjc= +github.com/fluxcd/pkg/auth v0.12.0/go.mod h1:gQD2VT5OhIR1E8ZTEsTaho3bDQZidr9P10smH/awcew= +github.com/fluxcd/pkg/cache v0.9.0 h1:EGKfOLMG3fOwWnH/4Axl5xd425mxoQbZzlZoLfd8PDk= +github.com/fluxcd/pkg/cache v0.9.0/go.mod h1:jMwabjWfsC5lW8hE7NM3wtGNwSJ38Javx6EKbEi7INU= github.com/fluxcd/pkg/runtime v0.59.0 h1:3OrFkMJB39NcQ2vhhoxqls59sQVSn8U+thhyLbsQoA4= github.com/fluxcd/pkg/runtime v0.59.0/go.mod h1:MFbfyNyyoYRgPxpdwC9/dCOkzo7Yxhu/cQ9NKyhvqc0= github.com/fluxcd/pkg/version v0.7.0 h1:jZT5I6WFy1KlM40nHCSqlHmjC1VT1/DfmbAdOkIVVJc= @@ -176,6 +180,8 @@ github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vt github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -357,13 +363,13 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -569,6 +575,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/controller/imagepolicy_controller.go b/internal/controller/imagepolicy_controller.go index 3d6c4649..e7404162 100644 --- a/internal/controller/imagepolicy_controller.go +++ b/internal/controller/imagepolicy_controller.go @@ -99,6 +99,7 @@ const imageRepoKey = ".spec.imageRepository" // +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imagerepositories,verbs=get;list;watch // +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch +// +kubebuilder:rbac:groups="",resources=serviceaccounts/token,verbs=create // ImagePolicyReconciler reconciles a ImagePolicy object type ImagePolicyReconciler struct { diff --git a/internal/controller/imagerepository_controller.go b/internal/controller/imagerepository_controller.go index 6d46da8b..d5c90800 100644 --- a/internal/controller/imagerepository_controller.go +++ b/internal/controller/imagerepository_controller.go @@ -48,8 +48,9 @@ import ( eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" "github.com/fluxcd/pkg/apis/meta" - "github.com/fluxcd/pkg/oci" - "github.com/fluxcd/pkg/oci/auth/login" + "github.com/fluxcd/pkg/auth" + authutils "github.com/fluxcd/pkg/auth/utils" + "github.com/fluxcd/pkg/cache" "github.com/fluxcd/pkg/runtime/conditions" helper "github.com/fluxcd/pkg/runtime/controller" "github.com/fluxcd/pkg/runtime/patch" @@ -111,11 +112,12 @@ type ImageRepositoryReconciler struct { helper.Metrics ControllerName string + TokenCache *cache.TokenCache Database interface { DatabaseWriter DatabaseReader } - DeprecatedLoginOpts login.ProviderOptions + DeprecatedLoginOpts []auth.Provider patchOptions []patch.Option } @@ -350,7 +352,7 @@ func (r *ImageRepositoryReconciler) setAuthOptions(ctx context.Context, obj *ima // Configure authentication strategy to access the registry. var options []remote.Option var authSecret corev1.Secret - var auth authn.Authenticator + var authenticator authn.Authenticator var authErr error if obj.Spec.SecretRef != nil { @@ -360,37 +362,49 @@ func (r *ImageRepositoryReconciler) setAuthOptions(ctx context.Context, obj *ima }, &authSecret); err != nil { return nil, err } - auth, authErr = secret.AuthFromSecret(authSecret, ref) + authenticator, authErr = secret.AuthFromSecret(authSecret, ref) } else { // Build login provider options and use it to attempt registry login. - opts := login.ProviderOptions{} - switch obj.GetProvider() { - case "aws": - opts.AwsAutoLogin = true - case "azure": - opts.AzureAutoLogin = true - case "gcp": - opts.GcpAutoLogin = true - default: - opts = r.DeprecatedLoginOpts - } - var managerOpts []login.Option + var opts []auth.Option if proxyURL != nil { - managerOpts = append(managerOpts, login.WithProxyURL(proxyURL)) + opts = append(opts, auth.WithProxyURL(*proxyURL)) + } + switch provider := obj.GetProvider(); provider { + case "aws", "azure", "gcp": + // Support new features (service account and cache) only for non-deprecated code paths. + if obj.Spec.ServiceAccountName != "" { + serviceAccount := client.ObjectKey{ + Name: obj.Spec.ServiceAccountName, + Namespace: obj.GetNamespace(), + } + opts = append(opts, auth.WithServiceAccount(serviceAccount, r.Client)) + } + if r.TokenCache != nil { + involvedObject := cache.InvolvedObject{ + Kind: imagev1.ImageRepositoryKind, + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + Operation: cache.OperationReconcile, + } + opts = append(opts, auth.WithCache(*r.TokenCache, involvedObject)) + } + authenticator, authErr = authutils.GetArtifactRegistryCredentials(ctx, provider, obj.Spec.Image, opts...) + default: + // Handle deprecated auto-login controller flags. + for _, provider := range r.DeprecatedLoginOpts { + if _, err := provider.ParseArtifactRepository(obj.Spec.Image); err == nil { + authenticator, authErr = authutils.GetArtifactRegistryCredentials(ctx, + provider.GetName(), obj.Spec.Image, opts...) + break + } + } } - manager := login.NewManager(managerOpts...) - auth, authErr = manager.Login(ctx, obj.Spec.Image, ref, opts) } if authErr != nil { - // If it's not unconfigured provider error, abort reconciliation. - // Continue reconciliation if it's unconfigured providers for scanning - // public repositories. - if !errors.Is(authErr, oci.ErrUnconfiguredProvider) { - return nil, authErr - } + return nil, authErr } - if auth != nil { - options = append(options, remote.WithAuth(auth)) + if authenticator != nil { + options = append(options, remote.WithAuth(authenticator)) } // Load any provided certificate. @@ -437,7 +451,7 @@ func (r *ImageRepositoryReconciler) setAuthOptions(ctx context.Context, obj *ima options = append(options, remote.WithTransport(tr)) } - if obj.Spec.ServiceAccountName != "" { + if authenticator == nil && obj.Spec.ServiceAccountName != "" { serviceAccount := corev1.ServiceAccount{} // Lookup service account if err := r.Get(ctx, types.NamespacedName{ @@ -619,6 +633,10 @@ func (r *ImageRepositoryReconciler) reconcileDelete(ctx context.Context, obj *im // Remove our finalizer from the list. controllerutil.RemoveFinalizer(obj, imagev1.ImageFinalizer) + // Cleanup caches. + r.TokenCache.DeleteEventsForObject(imagev1.ImageRepositoryKind, + obj.GetName(), obj.GetNamespace(), cache.OperationReconcile) + // Stop reconciliation as the object is being deleted. return ctrl.Result{}, nil } diff --git a/main.go b/main.go index da5a1044..007e5a5e 100644 --- a/main.go +++ b/main.go @@ -33,9 +33,14 @@ import ( ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/config" + ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" - "github.com/fluxcd/pkg/oci/auth/login" + "github.com/fluxcd/pkg/auth" + "github.com/fluxcd/pkg/auth/aws" + "github.com/fluxcd/pkg/auth/azure" + "github.com/fluxcd/pkg/auth/gcp" + pkgcache "github.com/fluxcd/pkg/cache" "github.com/fluxcd/pkg/runtime/acl" "github.com/fluxcd/pkg/runtime/client" helper "github.com/fluxcd/pkg/runtime/controller" @@ -70,6 +75,10 @@ func init() { } func main() { + const ( + tokenCacheDefaultMaxSize = 100 + ) + var ( metricsAddr string eventsAddr string @@ -87,6 +96,7 @@ func main() { aclOptions acl.Options rateLimiterOptions helper.RateLimiterOptions featureGates feathelper.FeatureGates + tokenCacheOptions pkgcache.TokenFlags ) flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") @@ -108,6 +118,7 @@ func main() { rateLimiterOptions.BindFlags(flag.CommandLine) featureGates.BindFlags(flag.CommandLine) watchOptions.BindFlags(flag.CommandLine) + tokenCacheOptions.BindFlags(flag.CommandLine, tokenCacheDefaultMaxSize) flag.Parse() @@ -215,17 +226,38 @@ func main() { metricsH := helper.NewMetrics(mgr, metrics.MustMakeRecorder(), imagev1.ImageFinalizer) + var tokenCache *pkgcache.TokenCache + if tokenCacheOptions.MaxSize > 0 { + var err error + tokenCache, err = pkgcache.NewTokenCache(tokenCacheOptions.MaxSize, + pkgcache.WithMaxDuration(tokenCacheOptions.MaxDuration), + pkgcache.WithMetricsRegisterer(ctrlmetrics.Registry), + pkgcache.WithMetricsPrefix("gotk_token_")) + if err != nil { + setupLog.Error(err, "unable to create token cache") + os.Exit(1) + } + } + + var deprecatedLoginOpts []auth.Provider + if awsAutoLogin { + deprecatedLoginOpts = append(deprecatedLoginOpts, aws.Provider{}) + } + if azureAutoLogin { + deprecatedLoginOpts = append(deprecatedLoginOpts, azure.Provider{}) + } + if gcpAutoLogin { + deprecatedLoginOpts = append(deprecatedLoginOpts, gcp.Provider{}) + } + if err := (&controller.ImageRepositoryReconciler{ - Client: mgr.GetClient(), - EventRecorder: eventRecorder, - Metrics: metricsH, - Database: db, - ControllerName: controllerName, - DeprecatedLoginOpts: login.ProviderOptions{ - AwsAutoLogin: awsAutoLogin, - AzureAutoLogin: azureAutoLogin, - GcpAutoLogin: gcpAutoLogin, - }, + Client: mgr.GetClient(), + EventRecorder: eventRecorder, + Metrics: metricsH, + Database: db, + ControllerName: controllerName, + TokenCache: tokenCache, + DeprecatedLoginOpts: deprecatedLoginOpts, }).SetupWithManager(mgr, controller.ImageRepositoryReconcilerOptions{ RateLimiter: helper.GetRateLimiter(rateLimiterOptions), }); err != nil {