diff --git a/artifact/go.mod b/artifact/go.mod index 0fdf0f911..aa99b33f7 100644 --- a/artifact/go.mod +++ b/artifact/go.mod @@ -15,7 +15,7 @@ require ( github.com/cyphar/filepath-securejoin v0.6.1 github.com/fluxcd/pkg/apis/meta v1.25.0 github.com/fluxcd/pkg/lockedfile v0.7.0 - github.com/fluxcd/pkg/oci v0.59.0 + github.com/fluxcd/pkg/oci v0.60.0 github.com/fluxcd/pkg/sourceignore v0.16.0 github.com/fluxcd/pkg/tar v0.17.0 github.com/go-git/go-git/v5 v5.16.4 diff --git a/auth/aws/provider.go b/auth/aws/provider.go index 9cec5c6b3..34bfe9cfd 100644 --- a/auth/aws/provider.go +++ b/auth/aws/provider.go @@ -164,7 +164,7 @@ func (p Provider) NewTokenForServiceAccount(ctx context.Context, oidcToken strin return creds, nil } -// GetAccessTokenOptionsForArtifactRepository implements auth.Provider. +// GetAccessTokenOptionsForArtifactRepository implements auth.ArtifactRegistryCredentialsProvider. func (p Provider) GetAccessTokenOptionsForArtifactRepository(artifactRepository string) ([]auth.Option, error) { // AWS requires a region for getting access credentials. To avoid requiring // two regions to be passed in the Flux APIs we leverage the region present @@ -191,7 +191,7 @@ const publicECR = "public.ecr.aws" var registryRegex = regexp.MustCompile(registryPattern) -// ParseArtifactRepository implements auth.Provider. +// ParseArtifactRepository implements auth.ArtifactRegistryCredentialsProvider. // ParseArtifactRepository returns the ECR region, unless the registry // is public.ecr.aws, in which case it returns public.ecr.aws. func (Provider) ParseArtifactRepository(artifactRepository string) (string, error) { @@ -223,7 +223,7 @@ func getECRRegionFromRegistryInput(registryInput string) string { return registryInput } -// NewArtifactRegistryCredentials implements auth.Provider. +// NewArtifactRegistryCredentials implements auth.ArtifactRegistryCredentialsProvider. func (p Provider) NewArtifactRegistryCredentials(ctx context.Context, registryInput string, accessToken auth.Token, opts ...auth.Option) (*auth.ArtifactRegistryCredentials, error) { @@ -284,15 +284,15 @@ func (p Provider) NewArtifactRegistryCredentials(ctx context.Context, registryIn return nil, fmt.Errorf("invalid authorization token format") } return &auth.ArtifactRegistryCredentials{ - Authenticator: authn.FromConfig(authn.AuthConfig{ + Authenticator: &authn.Basic{ Username: s[0], Password: s[1], - }), + }, ExpiresAt: expiresAt, }, nil } -// GetAccessTokenOptionsForCluster implements auth.Provider. +// GetAccessTokenOptionsForCluster implements auth.RESTConfigProvider. func (Provider) GetAccessTokenOptionsForCluster(opts ...auth.Option) ([][]auth.Option, error) { var o auth.Options o.Apply(opts...) @@ -304,7 +304,7 @@ func (Provider) GetAccessTokenOptionsForCluster(opts ...auth.Option) ([][]auth.O return [][]auth.Option{{auth.WithSTSRegion(region)}}, nil } -// NewRESTConfig implements auth.Provider. +// NewRESTConfig implements auth.RESTConfigProvider. // // Reference: // https://docs.aws.amazon.com/eks/latest/best-practices/identity-and-access-management.html#_controlling_access_to_eks_clusters diff --git a/auth/aws/provider_test.go b/auth/aws/provider_test.go index 92b928f0e..f551e215e 100644 --- a/auth/aws/provider_test.go +++ b/auth/aws/provider_test.go @@ -267,10 +267,10 @@ func TestProvider_NewArtifactRegistryCredentials(t *testing.T) { context.Background(), provider, tt.artifactRepository, opts...) g.Expect(err).NotTo(HaveOccurred()) g.Expect(creds).To(Equal(&auth.ArtifactRegistryCredentials{ - Authenticator: authn.FromConfig(authn.AuthConfig{ + Authenticator: &authn.Basic{ Username: "username", Password: "password", - }), + }, })) }) } diff --git a/auth/azure/provider.go b/auth/azure/provider.go index 84fe495eb..03ab3ef23 100644 --- a/auth/azure/provider.go +++ b/auth/azure/provider.go @@ -125,7 +125,7 @@ func (p Provider) NewTokenForServiceAccount(ctx context.Context, oidcToken strin return &Token{token}, nil } -// GetAccessTokenOptionsForArtifactRepository implements auth.Provider. +// GetAccessTokenOptionsForArtifactRepository implements auth.ArtifactRegistryCredentialsProvider. func (p Provider) GetAccessTokenOptionsForArtifactRepository(artifactRepository string) ([]auth.Option, error) { // Azure requires scopes for getting access tokens. Here we compute // the scope for ACR, which is based on the registry host. @@ -160,7 +160,7 @@ const registryPattern = `^.+\.(azurecr\.io|azurecr\.cn|azurecr\.de|azurecr\.us)$ var registryRegex = regexp.MustCompile(registryPattern) -// ParseArtifactRepository implements auth.Provider. +// ParseArtifactRepository implements auth.ArtifactRegistryCredentialsProvider. // ParseArtifactRepository returns the ACR registry host. func (Provider) ParseArtifactRepository(artifactRepository string) (string, error) { registry, err := auth.GetRegistryFromArtifactRepository(artifactRepository) @@ -191,7 +191,7 @@ func (Provider) ParseArtifactRepository(artifactRepository string) (string, erro registry, registryPattern) } -// NewArtifactRegistryCredentials implements auth.Provider. +// NewArtifactRegistryCredentials implements auth.ArtifactRegistryCredentialsProvider. func (p Provider) NewArtifactRegistryCredentials(ctx context.Context, registry string, accessToken auth.Token, opts ...auth.Option) (*auth.ArtifactRegistryCredentials, error) { @@ -234,16 +234,16 @@ func (p Provider) NewArtifactRegistryCredentials(ctx context.Context, registry s // Return the credentials. return &auth.ArtifactRegistryCredentials{ - Authenticator: authn.FromConfig(authn.AuthConfig{ + Authenticator: &authn.Basic{ // https://docs.microsoft.com/en-us/azure/container-registry/container-registry-authentication?tabs=azure-cli#az-acr-login-with---expose-token Username: "00000000-0000-0000-0000-000000000000", Password: token, - }), + }, ExpiresAt: expiry.Time, }, nil } -// GetAccessTokenOptionsForCluster implements auth.Provider. +// GetAccessTokenOptionsForCluster implements auth.RESTConfigProvider. func (Provider) GetAccessTokenOptionsForCluster(opts ...auth.Option) ([][]auth.Option, error) { var o auth.Options o.Apply(opts...) @@ -278,7 +278,7 @@ func (Provider) GetAccessTokenOptionsForCluster(opts ...auth.Option) ([][]auth.O return atOpts, nil } -// NewRESTConfig implements auth.Provider. +// NewRESTConfig implements auth.RESTConfigProvider. func (p Provider) NewRESTConfig(ctx context.Context, accessTokens []auth.Token, opts ...auth.Option) (*auth.RESTConfig, error) { diff --git a/auth/azure/provider_test.go b/auth/azure/provider_test.go index ea0909f80..3aca16177 100644 --- a/auth/azure/provider_test.go +++ b/auth/azure/provider_test.go @@ -225,10 +225,10 @@ func TestProvider_NewArtifactRegistryCredentials(t *testing.T) { creds, err := auth.GetArtifactRegistryCredentials(context.Background(), provider, artifactRepository, opts...) g.Expect(err).NotTo(HaveOccurred()) g.Expect(creds).To(Equal(&auth.ArtifactRegistryCredentials{ - Authenticator: authn.FromConfig(authn.AuthConfig{ + Authenticator: &authn.Basic{ Username: "00000000-0000-0000-0000-000000000000", Password: refreshToken, - }), + }, ExpiresAt: time.Unix(exp, 0), })) }) diff --git a/auth/doc.go b/auth/doc.go index ca31016ce..e6f20ae73 100644 --- a/auth/doc.go +++ b/auth/doc.go @@ -14,5 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -// auth is a package for handling secret-less authentication with cloud providers. +// auth is a package for handling short-lived credentials. +// Flux APIs using this package will never pass in contents +// of a Secret specified directly in the API object under +// reconciliation. Instead, options to generate short-lived +// credentials on-the-fly shall be provided. The package +// supports caching of generated credentials to avoid +// rate-limiting by external services that are part of +// the credential generation process. package auth diff --git a/auth/gcp/provider.go b/auth/gcp/provider.go index 71dc5bbc2..d3fc4657a 100644 --- a/auth/gcp/provider.go +++ b/auth/gcp/provider.go @@ -157,7 +157,7 @@ func (p Provider) NewTokenForServiceAccount(ctx context.Context, oidcToken strin return &Token{*token}, nil } -// GetAccessTokenOptionsForArtifactRepository implements auth.Provider. +// GetAccessTokenOptionsForArtifactRepository implements auth.ArtifactRegistryCredentialsProvider. func (Provider) GetAccessTokenOptionsForArtifactRepository(string) ([]auth.Option, error) { // GCP does not require any special options to retrieve access tokens. return nil, nil @@ -167,8 +167,8 @@ const registryPattern = `^(((.+\.)?gcr\.io)|(.+-docker\.pkg\.dev))$` var registryRegex = regexp.MustCompile(registryPattern) -// ParseArtifactRepository implements auth.Provider. -func (Provider) ParseArtifactRepository(artifactRepository string) (string, error) { +// ParseArtifactRepository implements auth.ArtifactRegistryCredentialsProvider. +func (p Provider) ParseArtifactRepository(artifactRepository string) (string, error) { registry, err := auth.GetRegistryFromArtifactRepository(artifactRepository) if err != nil { return "", err @@ -181,31 +181,31 @@ func (Provider) ParseArtifactRepository(artifactRepository string) (string, erro // The artifact repository is irrelevant for issuing GCP registry credentials, // just return the provider name for inclusion in the cache key. - return ProviderName, nil + return p.GetName(), nil } -// NewArtifactRegistryCredentials implements auth.Provider. +// NewArtifactRegistryCredentials implements auth.ArtifactRegistryCredentialsProvider. func (Provider) NewArtifactRegistryCredentials(_ context.Context, _ string, accessToken auth.Token, _ ...auth.Option) (*auth.ArtifactRegistryCredentials, error) { t := accessToken.(*Token) return &auth.ArtifactRegistryCredentials{ - Authenticator: authn.FromConfig(authn.AuthConfig{ + Authenticator: &authn.Basic{ Username: "oauth2accesstoken", Password: t.AccessToken, - }), + }, ExpiresAt: t.Expiry, }, nil } -// GetAccessTokenOptionsForCluster implements auth.Provider. +// GetAccessTokenOptionsForCluster implements auth.RESTConfigProvider. func (Provider) GetAccessTokenOptionsForCluster(opts ...auth.Option) ([][]auth.Option, error) { // A single token is needed. No options. return [][]auth.Option{{}}, nil } -// NewRESTConfig implements auth.Provider. +// NewRESTConfig implements auth.RESTConfigProvider. func (p Provider) NewRESTConfig(ctx context.Context, accessTokens []auth.Token, opts ...auth.Option) (*auth.RESTConfig, error) { diff --git a/auth/gcp/provider_test.go b/auth/gcp/provider_test.go index 82f203ed5..4713c1a47 100644 --- a/auth/gcp/provider_test.go +++ b/auth/gcp/provider_test.go @@ -274,14 +274,12 @@ func TestProvider_NewArtifactRegistryCredentials(t *testing.T) { creds, err := auth.GetArtifactRegistryCredentials(context.Background(), provider, "gcr.io", auth.WithProxyURL(url.URL{Scheme: "http", Host: "proxy.example.com"})) g.Expect(err).NotTo(HaveOccurred()) - g.Expect(creds).NotTo(BeNil()) - g.Expect(creds.ExpiresAt).To(Equal(exp)) - g.Expect(creds.Authenticator).NotTo(BeNil()) - authConf, err := creds.Authenticator.Authorization() - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(authConf).To(Equal(&authn.AuthConfig{ - Username: "oauth2accesstoken", - Password: "access-token", + g.Expect(creds).To(Equal(&auth.ArtifactRegistryCredentials{ + Authenticator: &authn.Basic{ + Username: "oauth2accesstoken", + Password: "access-token", + }, + ExpiresAt: exp, })) } diff --git a/auth/generic/implementation.go b/auth/serviceaccounttoken/implementation.go similarity index 96% rename from auth/generic/implementation.go rename to auth/serviceaccounttoken/implementation.go index aad77a1be..be6b18f1e 100644 --- a/auth/generic/implementation.go +++ b/auth/serviceaccounttoken/implementation.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package generic +package serviceaccounttoken import "os" diff --git a/auth/generic/implementation_test.go b/auth/serviceaccounttoken/implementation_test.go similarity index 96% rename from auth/generic/implementation_test.go rename to auth/serviceaccounttoken/implementation_test.go index bf893ec94..79488e945 100644 --- a/auth/generic/implementation_test.go +++ b/auth/serviceaccounttoken/implementation_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package generic_test +package serviceaccounttoken_test import ( "testing" diff --git a/auth/generic/provider.go b/auth/serviceaccounttoken/provider.go similarity index 78% rename from auth/generic/provider.go rename to auth/serviceaccounttoken/provider.go index d6b67f4eb..f9af53b96 100644 --- a/auth/generic/provider.go +++ b/auth/serviceaccounttoken/provider.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package generic +package serviceaccounttoken import ( "context" @@ -24,6 +24,7 @@ import ( "time" "github.com/golang-jwt/jwt/v5" + "github.com/google/go-containerregistry/pkg/authn" authnv1 "k8s.io/api/authentication/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -32,15 +33,21 @@ import ( "github.com/fluxcd/pkg/auth" ) -// ProviderName is the name of the generic authentication provider. +// ProviderName is the name of the provider implemented by this package. +// Only the Kustomization and HelmRelease APIs refer to this package as +// a provider for historical reasons. New APIs should refer to it as the +// ServiceAccountToken credential provider (see CredentialName). const ProviderName = "generic" +// CredentialName is the name of the credential type implemented by this package. +const CredentialName = "ServiceAccountToken" + // Provider implements the auth.Provider interface for generic authentication. type Provider struct{ Implementation } // GetName implements auth.RESTConfigProvider. func (p Provider) GetName() string { - return ProviderName + return CredentialName } // NewControllerToken implements auth.RESTConfigProvider. @@ -132,6 +139,31 @@ func (Provider) NewTokenForServiceAccount(ctx context.Context, oidcToken string, }, nil } +// GetAccessTokenOptionsForArtifactRepository implements auth.ArtifactRegistryCredentialsProvider. +func (Provider) GetAccessTokenOptionsForArtifactRepository(string) ([]auth.Option, error) { + // No special options are needed to get an access token for artifact registry. + return nil, nil +} + +// ParseArtifactRepository implements auth.ArtifactRegistryCredentialsProvider. +func (p Provider) ParseArtifactRepository(artifactRepository string) (string, error) { + // The artifact repository is irrelevant for issuing the ServiceAccount token, + // just return the provider name for inclusion in the cache key. + return p.GetName(), nil +} + +// NewArtifactRegistryCredentials implements auth.ArtifactRegistryCredentialsProvider. +func (p Provider) NewArtifactRegistryCredentials(ctx context.Context, registryInput string, + accessToken auth.Token, opts ...auth.Option) (*auth.ArtifactRegistryCredentials, error) { + + token := accessToken.(*Token) + + return &auth.ArtifactRegistryCredentials{ + Authenticator: &authn.Bearer{Token: token.Token}, + ExpiresAt: token.ExpiresAt, + }, nil +} + // GetAccessTokenOptionsForCluster implements auth.RESTConfigProvider. func (Provider) GetAccessTokenOptionsForCluster(opts ...auth.Option) ([][]auth.Option, error) { diff --git a/auth/generic/provider_test.go b/auth/serviceaccounttoken/provider_test.go similarity index 83% rename from auth/generic/provider_test.go rename to auth/serviceaccounttoken/provider_test.go index d56e38551..847c2f2b8 100644 --- a/auth/generic/provider_test.go +++ b/auth/serviceaccounttoken/provider_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package generic_test +package serviceaccounttoken_test import ( "context" @@ -30,14 +30,14 @@ import ( "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/auth" - "github.com/fluxcd/pkg/auth/generic" + "github.com/fluxcd/pkg/auth/serviceaccounttoken" "github.com/fluxcd/pkg/auth/utils" ) func TestProvider_NewControllerToken(t *testing.T) { t.Run("no client", func(t *testing.T) { g := NewWithT(t) - token, err := generic.Provider{}.NewControllerToken(context.Background()) + token, err := serviceaccounttoken.Provider{}.NewControllerToken(context.Background()) g.Expect(err).To(HaveOccurred()) g.Expect(err.Error()).To(Equal("client is required to create a controller token")) g.Expect(token).To(BeNil()) @@ -66,11 +66,11 @@ func TestProvider_NewControllerToken(t *testing.T) { t: t, b: []byte("eyJhbGciOiJSUzI1NiIsImtpZCI6IkU2cUVmaVJ0QUY2OWhoNThZWU1QUmhPc1F1b1N5XzJuT1ZfRWF3TVRETlkifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzUyMjkwMDE1LCJpYXQiOjE3NTIyODY0MTUsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiMzEwMTgxZGItZDc3MC00MGE5LTg5MDEtN2M1NTQzOTBjZDhjIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImNvbnRyb2xsZXIiLCJ1aWQiOiJjMTUzNWEyNi01NDY5LTRmYzAtOGRiMi1kZWFhMGRlNDRmZjUifX0sIm5iZiI6MTc1MjI4NjQxNSwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6Y29udHJvbGxlciJ9.k-jt09bIwrGUNbSATEwaHHaaoym7NjcdStXcM0RYXZbL_PXCwP-TZPgBb2FzCq6V79E_q-NtZrY3RyvyAynUezXr6IPVkGne201uvOAjaibLvDxLzvbA5jWlZ0bHuLCfOxlC7GYSWjsglyH_ufulb6vxoMhY0rmiQzBbDHfB3EWM79-udcqLrxBsGgxjDnW4BXMIgSpuvipNA1GaMkpQb5AaY7Ns4zd0FftOimQmmvnwz8oDrGrCf2kmw91r0sAovva5B2BoJKlZwYGwO93zwTwK1qOMPLN2QHCUNBEY4K-QQlgz0oMUYR-YRpPJr7akjTQ6hm9zrTD90Tm0Jbqw7g\n"), } - token, err := auth.GetAccessToken(ctx, generic.Provider{m}, + token, err := auth.GetAccessToken(ctx, serviceaccounttoken.Provider{m}, auth.WithClient(envClient), auth.WithAudiences("audience1", "audience2")) g.Expect(err).NotTo(HaveOccurred()) - genericToken := token.(*generic.Token) + genericToken := token.(*serviceaccounttoken.Token) g.Expect(genericToken).NotTo(BeNil()) // Validate token. @@ -116,13 +116,13 @@ func TestProvider_NewTokenForServiceAccount(t *testing.T) { g.Expect(err).NotTo(HaveOccurred()) // Create token. - token, err := auth.GetAccessToken(ctx, generic.Provider{}, + token, err := auth.GetAccessToken(ctx, serviceaccounttoken.Provider{}, auth.WithClient(envClient), auth.WithServiceAccountName(serviceAccount.Name), auth.WithServiceAccountNamespace(serviceAccount.Namespace), auth.WithAudiences("audience1", "audience2")) g.Expect(err).NotTo(HaveOccurred()) - genericToken := token.(*generic.Token) + genericToken := token.(*serviceaccounttoken.Token) g.Expect(genericToken).NotTo(BeNil()) // Validate token. @@ -145,9 +145,14 @@ func TestProvider_NewTokenForServiceAccount(t *testing.T) { g.Expect(time.Until(genericToken.ExpiresAt)).To(BeNumerically("~", time.Hour, 10*time.Second)) } +func TestProvider_GetName(t *testing.T) { + g := NewWithT(t) + g.Expect(serviceaccounttoken.Provider{}.GetName()).To(Equal(serviceaccounttoken.CredentialName)) +} + func TestProvider_GetIdentity(t *testing.T) { g := NewWithT(t) - id, err := generic.Provider{}.GetIdentity(corev1.ServiceAccount{ + id, err := serviceaccounttoken.Provider{}.GetIdentity(corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant", Namespace: "default", @@ -217,7 +222,7 @@ func TestProvider_NewRESTConfig(t *testing.T) { opts = append(opts, auth.WithClusterAddress(tt.clusterAddress)) } - conf, err := auth.GetRESTConfig(ctx, generic.Provider{m}, opts...) + conf, err := auth.GetRESTConfig(ctx, serviceaccounttoken.Provider{m}, opts...) if tt.err != "" { g.Expect(err).To(HaveOccurred()) @@ -290,7 +295,7 @@ func TestProvider_NewRESTConfig_EndToEnd(t *testing.T) { Namespace: namespace, }, Data: map[string]string{ - meta.KubeConfigKeyProvider: generic.ProviderName, + meta.KubeConfigKeyProvider: serviceaccounttoken.ProviderName, meta.KubeConfigKeyAddress: envConfig.Host, meta.KubeConfigKeyCACert: string(envConfig.CAData), meta.KubeConfigKeyServiceAccountName: saName, @@ -327,7 +332,7 @@ func TestProvider_NewRESTConfig_EndToEnd(t *testing.T) { func TestProvider_GetAccessTokenOptionsForCluster(t *testing.T) { t.Run("without audiences", func(t *testing.T) { g := NewWithT(t) - opts, err := generic.Provider{}.GetAccessTokenOptionsForCluster( + opts, err := serviceaccounttoken.Provider{}.GetAccessTokenOptionsForCluster( auth.WithClusterAddress("https://example.com")) g.Expect(err).NotTo(HaveOccurred()) g.Expect(opts).To(HaveLen(1)) @@ -339,7 +344,7 @@ func TestProvider_GetAccessTokenOptionsForCluster(t *testing.T) { t.Run("with audiences", func(t *testing.T) { g := NewWithT(t) - opts, err := generic.Provider{}.GetAccessTokenOptionsForCluster( + opts, err := serviceaccounttoken.Provider{}.GetAccessTokenOptionsForCluster( auth.WithClusterAddress("https://example.com"), auth.WithAudiences("audience1", "audience2")) g.Expect(err).NotTo(HaveOccurred()) @@ -350,3 +355,47 @@ func TestProvider_GetAccessTokenOptionsForCluster(t *testing.T) { g.Expect(o.Audiences).To(ConsistOf("audience1", "audience2")) }) } + +func TestProvider_GetAccessTokenOptionsForArtifactRepository(t *testing.T) { + g := NewWithT(t) + opts, err := serviceaccounttoken.Provider{}.GetAccessTokenOptionsForArtifactRepository("any-registry.io") + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(opts).To(BeNil()) +} + +func TestProvider_ParseArtifactRepository(t *testing.T) { + p := serviceaccounttoken.Provider{} + for _, repo := range []string{ + "any-registry.io", + "ghcr.io/owner/repo", + "docker.io/library/nginx", + } { + t.Run(repo, func(t *testing.T) { + g := NewWithT(t) + parsed, err := p.ParseArtifactRepository(repo) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(parsed).To(Equal(serviceaccounttoken.CredentialName)) + }) + } +} + +func TestProvider_NewArtifactRegistryCredentials(t *testing.T) { + g := NewWithT(t) + + expiresAt := time.Now().Add(time.Hour) + token := &serviceaccounttoken.Token{ + Token: "test-token", + ExpiresAt: expiresAt, + } + + creds, err := serviceaccounttoken.Provider{}.NewArtifactRegistryCredentials( + context.Background(), "any-registry.io", token) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(creds).NotTo(BeNil()) + g.Expect(creds.ExpiresAt).To(Equal(expiresAt)) + + // Verify the authenticator returns the correct authorization header. + authConfig, err := creds.Authenticator.Authorization() + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(authConfig.RegistryToken).To(Equal("test-token")) +} diff --git a/auth/generic/suite_test.go b/auth/serviceaccounttoken/suite_test.go similarity index 98% rename from auth/generic/suite_test.go rename to auth/serviceaccounttoken/suite_test.go index f9a7546b2..7d3963d1d 100644 --- a/auth/generic/suite_test.go +++ b/auth/serviceaccounttoken/suite_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package generic_test +package serviceaccounttoken_test import ( "context" diff --git a/auth/generic/token.go b/auth/serviceaccounttoken/token.go similarity index 96% rename from auth/generic/token.go rename to auth/serviceaccounttoken/token.go index ecfb5d5cb..26b47dd8d 100644 --- a/auth/generic/token.go +++ b/auth/serviceaccounttoken/token.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package generic +package serviceaccounttoken import ( "time" diff --git a/auth/utils/provider.go b/auth/utils/provider.go index 9049c8e19..94e21e354 100644 --- a/auth/utils/provider.go +++ b/auth/utils/provider.go @@ -22,7 +22,7 @@ import ( "github.com/fluxcd/pkg/auth/aws" "github.com/fluxcd/pkg/auth/azure" "github.com/fluxcd/pkg/auth/gcp" - "github.com/fluxcd/pkg/auth/generic" + "github.com/fluxcd/pkg/auth/serviceaccounttoken" ) // ProviderByName looks up the implemented providers by name and type. @@ -37,8 +37,8 @@ func ProviderByName[T any](name string) (T, error) { p = azure.Provider{} case gcp.ProviderName: p = gcp.Provider{} - case generic.ProviderName: - p = generic.Provider{} + case serviceaccounttoken.ProviderName, serviceaccounttoken.CredentialName: + p = serviceaccounttoken.Provider{} default: return zero, fmt.Errorf("provider '%s' not implemented", name) } diff --git a/auth/utils/provider_test.go b/auth/utils/provider_test.go index f1069722d..f4a57d995 100644 --- a/auth/utils/provider_test.go +++ b/auth/utils/provider_test.go @@ -25,7 +25,7 @@ import ( "github.com/fluxcd/pkg/auth/aws" "github.com/fluxcd/pkg/auth/azure" "github.com/fluxcd/pkg/auth/gcp" - "github.com/fluxcd/pkg/auth/generic" + "github.com/fluxcd/pkg/auth/serviceaccounttoken" authutils "github.com/fluxcd/pkg/auth/utils" ) @@ -48,8 +48,12 @@ func TestProviderByName(t *testing.T) { provider: gcp.Provider{}, }, { - name: generic.ProviderName, - provider: generic.Provider{}, + name: serviceaccounttoken.ProviderName, + provider: serviceaccounttoken.Provider{}, + }, + { + name: serviceaccounttoken.CredentialName, + provider: serviceaccounttoken.Provider{}, }, } { t.Run(tt.name, func(t *testing.T) { @@ -78,6 +82,14 @@ func TestProviderByName(t *testing.T) { name: gcp.ProviderName, provider: gcp.Provider{}, }, + { + name: serviceaccounttoken.ProviderName, + provider: serviceaccounttoken.Provider{}, + }, + { + name: serviceaccounttoken.CredentialName, + provider: serviceaccounttoken.Provider{}, + }, } { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) @@ -86,14 +98,6 @@ func TestProviderByName(t *testing.T) { g.Expect(p).To(Equal(tt.provider)) }) } - - t.Run("generic provider", func(t *testing.T) { - g := NewWithT(t) - p, err := authutils.ProviderByName[auth.ArtifactRegistryCredentialsProvider](generic.ProviderName) - g.Expect(err).To(HaveOccurred()) - g.Expect(err.Error()).To(ContainSubstring("does not implement the expected interface")) - g.Expect(p).To(BeNil()) - }) }) t.Run("restconfig providers", func(t *testing.T) { @@ -114,8 +118,12 @@ func TestProviderByName(t *testing.T) { provider: gcp.Provider{}, }, { - name: generic.ProviderName, - provider: generic.Provider{}, + name: serviceaccounttoken.ProviderName, + provider: serviceaccounttoken.Provider{}, + }, + { + name: serviceaccounttoken.CredentialName, + provider: serviceaccounttoken.Provider{}, }, } { t.Run(tt.name, func(t *testing.T) { @@ -147,8 +155,12 @@ func TestProviderByName(t *testing.T) { provider: gcp.Provider{}, }, { - name: generic.ProviderName, - provider: generic.Provider{}, + name: serviceaccounttoken.ProviderName, + provider: serviceaccounttoken.Provider{}, + }, + { + name: serviceaccounttoken.CredentialName, + provider: serviceaccounttoken.Provider{}, }, } { t.Run(tt.name, func(t *testing.T) { diff --git a/oci/login.go b/oci/login.go index a601004c5..3edcd6082 100644 --- a/oci/login.go +++ b/oci/login.go @@ -39,19 +39,14 @@ func (c *Client) LoginWithCredentials(credentials string) error { // GetAuthFromCredentials returns an authn.Authenticator for the static credentials, accepts a single token // or a user:password format. func GetAuthFromCredentials(credentials string) (authn.Authenticator, error) { - var authConfig authn.AuthConfig - if credentials == "" { return nil, errors.New("credentials cannot be empty") } - parts := strings.SplitN(credentials, ":", 2) - - if len(parts) == 1 { - authConfig = authn.AuthConfig{RegistryToken: parts[0]} - } else { - authConfig = authn.AuthConfig{Username: parts[0], Password: parts[1]} + switch parts := strings.SplitN(credentials, ":", 2); { + case len(parts) == 1: + return &authn.Bearer{Token: parts[0]}, nil + default: + return &authn.Basic{Username: parts[0], Password: parts[1]}, nil } - - return authn.FromConfig(authConfig), nil } diff --git a/tests/integration/go.mod b/tests/integration/go.mod index f4e6f5e33..db1d15e13 100644 --- a/tests/integration/go.mod +++ b/tests/integration/go.mod @@ -17,10 +17,10 @@ require ( github.com/elazarl/goproxy v1.8.0 github.com/fluxcd/cli-utils v0.37.1-flux.1 github.com/fluxcd/pkg/apis/meta v1.25.0 - github.com/fluxcd/pkg/auth v0.36.0 + github.com/fluxcd/pkg/auth v0.37.0 github.com/fluxcd/pkg/cache v0.13.0 github.com/fluxcd/pkg/git v0.41.0 - github.com/fluxcd/pkg/runtime v0.96.0 + github.com/fluxcd/pkg/runtime v0.97.0 github.com/fluxcd/test-infra/tftestenv v0.0.0-20250626232827-e0ca9c3f8d7b github.com/go-git/go-git/v5 v5.16.4 github.com/google/go-containerregistry v0.20.7