From bf712cc59aa984a8901c08f687a6e637e692f3c9 Mon Sep 17 00:00:00 2001 From: Stephen Cahill Date: Tue, 13 Jan 2026 17:56:35 -0500 Subject: [PATCH] allow k8snoclient for fetcher auth --- cmd/crossplane/core/core.go | 19 +++++++++----- internal/xpkg/fetch.go | 52 ++++++++++++++++++++++++------------- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/cmd/crossplane/core/core.go b/cmd/crossplane/core/core.go index 9c234f5a51a..b858fe30005 100644 --- a/cmd/crossplane/core/core.go +++ b/cmd/crossplane/core/core.go @@ -92,11 +92,12 @@ func (c *Command) Run() error { type startCommand struct { Profile string `help:"Serve runtime profiling data via HTTP at /debug/pprof." placeholder:"host:port"` - Namespace string `default:"crossplane-system" env:"POD_NAMESPACE" help:"Namespace used to unpack and run packages." short:"n"` - ServiceAccount string `default:"crossplane" env:"POD_SERVICE_ACCOUNT" help:"Name of the Crossplane Service Account."` - LeaderElection bool `default:"false" env:"LEADER_ELECTION" help:"Use leader election for the controller manager." short:"l"` - CABundlePath string `env:"CA_BUNDLE_PATH" help:"Additional CA bundle to use when fetching packages from registry."` - UserAgent string `default:"${default_user_agent}" env:"USER_AGENT" help:"The User-Agent header that will be set on all package requests."` + Namespace string `default:"crossplane-system" env:"POD_NAMESPACE" help:"Namespace used to unpack and run packages." short:"n"` + ServiceAccount string `default:"crossplane" env:"POD_SERVICE_ACCOUNT" help:"Name of the Crossplane Service Account."` + LeaderElection bool `default:"false" env:"LEADER_ELECTION" help:"Use leader election for the controller manager." short:"l"` + CABundlePath string `env:"CA_BUNDLE_PATH" help:"Additional CA bundle to use when fetching packages from registry."` + UserAgent string `default:"${default_user_agent}" env:"USER_AGENT" help:"The User-Agent header that will be set on all package requests."` + RegistryAuthCloudNative bool `default:"false" env:"REGISTRY_AUTH_CLOUD_NATIVE" help:"Use cloud-native authentication (IMDS/workload identity) for registry access instead of Kubernetes ImagePullSecrets. Enables GCR/AR, ACR, ECR authentication via node/workload-identity."` XpkgCacheDir string `aliases:"cache-dir" default:"/cache/xpkg" env:"XPKG_CACHE_DIR,CACHE_DIR" help:"Directory used for caching package images." short:"c"` @@ -499,12 +500,18 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli log.Info("Package Runtime for Provider: " + string(pr.For(pkgv1.ProviderKind))) log.Info("Package Runtime for Function: " + string(pr.For(pkgv1.FunctionKind))) + fetcherOpts := []xpkg.FetcherOpt{xpkg.WithUserAgent(c.UserAgent)} + if c.RegistryAuthCloudNative { + fetcherOpts = append(fetcherOpts, xpkg.WithCloudNativeAuth(true)) + log.Info("Using cloud-native authentication for registry access (IMDS/workload identity)") + } + po := pkgcontroller.Options{ Options: o, Cache: xpkg.NewFsPackageCache(c.XpkgCacheDir, afero.NewOsFs()), Namespace: c.Namespace, ServiceAccount: c.ServiceAccount, - FetcherOptions: []xpkg.FetcherOpt{xpkg.WithUserAgent(c.UserAgent)}, + FetcherOptions: fetcherOpts, PackageRuntime: pr, MaxConcurrentPackageEstablishers: c.MaxConcurrentPackageEstablishers, } diff --git a/internal/xpkg/fetch.go b/internal/xpkg/fetch.go index 494d5c2fe85..90f2da889d0 100644 --- a/internal/xpkg/fetch.go +++ b/internal/xpkg/fetch.go @@ -23,6 +23,7 @@ import ( "io" "net/http" + "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/authn/k8schain" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -52,11 +53,12 @@ type Fetcher interface { // K8sFetcher uses kubernetes credentials to fetch package images. type K8sFetcher struct { - client kubernetes.Interface - namespace string - serviceAccount string - transport http.RoundTripper - userAgent string + client kubernetes.Interface + namespace string + serviceAccount string + transport http.RoundTripper + userAgent string + useCloudNativeAuth bool } // FetcherOpt can be used to add optional parameters to NewK8sFetcher. @@ -107,6 +109,17 @@ func WithServiceAccount(sa string) FetcherOpt { } } +// WithCloudNativeAuth is a FetcherOpt that enables cloud-native authentication +// (via IMDS/workload identity) without requiring Kubernetes API access to pull +// secrets. This uses k8schain.NewNoClient which provides GCR/AR, ACR, and ECR +// authentication via node/workload-identity style auth. +func WithCloudNativeAuth(enabled bool) FetcherOpt { + return func(k *K8sFetcher) error { + k.useCloudNativeAuth = enabled + return nil + } +} + // NewK8sFetcher creates a new K8sFetcher. func NewK8sFetcher(client kubernetes.Interface, opts ...FetcherOpt) (*K8sFetcher, error) { dt, ok := remote.DefaultTransport.(*http.Transport) @@ -128,13 +141,24 @@ func NewK8sFetcher(client kubernetes.Interface, opts ...FetcherOpt) (*K8sFetcher return k, nil } -// Fetch fetches a package image. -func (i *K8sFetcher) Fetch(ctx context.Context, ref name.Reference, secrets ...string) (v1.Image, error) { - auth, err := k8schain.New(ctx, i.client, k8schain.Options{ +// keychain returns the appropriate keychain based on the fetcher configuration. +// If useCloudNativeAuth is enabled, it uses k8schain.NewNoClient for cloud-native +// authentication (GCR/AR, ACR, ECR via IMDS/workload identity). +// Otherwise, it uses k8schain.New which reads ImagePullSecrets from Kubernetes. +func (i *K8sFetcher) keychain(ctx context.Context, secrets ...string) (authn.Keychain, error) { + if i.useCloudNativeAuth { + return k8schain.NewNoClient(ctx) + } + return k8schain.New(ctx, i.client, k8schain.Options{ Namespace: i.namespace, ServiceAccountName: i.serviceAccount, ImagePullSecrets: secrets, }) +} + +// Fetch fetches a package image. +func (i *K8sFetcher) Fetch(ctx context.Context, ref name.Reference, secrets ...string) (v1.Image, error) { + auth, err := i.keychain(ctx, secrets...) if err != nil { return nil, err } @@ -149,11 +173,7 @@ func (i *K8sFetcher) Fetch(ctx context.Context, ref name.Reference, secrets ...s // Head fetches a package descriptor. func (i *K8sFetcher) Head(ctx context.Context, ref name.Reference, secrets ...string) (*v1.Descriptor, error) { - auth, err := k8schain.New(ctx, i.client, k8schain.Options{ - Namespace: i.namespace, - ServiceAccountName: i.serviceAccount, - ImagePullSecrets: secrets, - }) + auth, err := i.keychain(ctx, secrets...) if err != nil { return nil, err } @@ -183,11 +203,7 @@ func (i *K8sFetcher) Head(ctx context.Context, ref name.Reference, secrets ...st // Tags fetches a package's tags. func (i *K8sFetcher) Tags(ctx context.Context, ref name.Reference, secrets ...string) ([]string, error) { - auth, err := k8schain.New(ctx, i.client, k8schain.Options{ - Namespace: i.namespace, - ServiceAccountName: i.serviceAccount, - ImagePullSecrets: secrets, - }) + auth, err := i.keychain(ctx, secrets...) if err != nil { return nil, err }