Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions auth/access_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"fmt"
"strings"

authnv1 "k8s.io/api/authentication/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/fluxcd/pkg/cache"
)
Expand Down Expand Up @@ -60,16 +60,12 @@ func GetAccessToken(ctx context.Context, provider Provider, opts ...Option) (Tok
if saInfo.useServiceAccount {
newAccessToken = func() (Token, error) {
// Issue Kubernetes OIDC token for the service account.
tokenReq := &authnv1.TokenRequest{
Spec: authnv1.TokenRequestSpec{
Audiences: saInfo.audiences,
},
}
if err := o.Client.SubResource("token").Create(ctx, saInfo.obj, tokenReq); err != nil {
saKey := client.ObjectKeyFromObject(saInfo.obj)
oidcToken, err := CreateServiceAccountToken(ctx, o.Client, saKey, saInfo.audiences...)
if err != nil {
return nil, fmt.Errorf("failed to create kubernetes token for service account '%s/%s': %w",
saInfo.obj.Namespace, saInfo.obj.Name, err)
}
oidcToken := tokenReq.Status.Token

// Exchange the Kubernetes OIDC token for a provider access token.
token, err := provider.NewTokenForServiceAccount(ctx, oidcToken, *saInfo.obj, opts...)
Expand Down
14 changes: 7 additions & 7 deletions auth/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func (p Provider) NewTokenForIdentity(ctx context.Context, token auth.Token,
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
Expand All @@ -260,7 +260,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) {
Expand Down Expand Up @@ -292,7 +292,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) {

Expand Down Expand Up @@ -353,15 +353,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...)
Expand All @@ -373,7 +373,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
Expand Down
4 changes: 2 additions & 2 deletions auth/aws/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,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",
}),
},
}))
})
}
Expand Down
14 changes: 7 additions & 7 deletions auth/azure/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {

Expand Down Expand Up @@ -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...)
Expand Down Expand Up @@ -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) {

Expand Down
4 changes: 2 additions & 2 deletions auth/azure/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}))
})
Expand Down
9 changes: 8 additions & 1 deletion auth/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 9 additions & 9 deletions auth/gcp/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ func (p Provider) NewTokenForIdentity(ctx context.Context, token auth.Token,
return &Token{*tok}, 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
Expand All @@ -221,8 +221,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
Expand All @@ -235,31 +235,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) {

Expand Down
14 changes: 6 additions & 8 deletions auth/gcp/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,14 +275,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,
}))
}

Expand Down
54 changes: 54 additions & 0 deletions auth/pod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
Copyright 2026 The Flux authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package auth

import (
"fmt"
"strings"

"github.com/golang-jwt/jwt/v5"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// FindPodServiceAccount attempts to determine the service account
// associated with the current pod by reading and parsing the
// service account token mounted in the pod.
func FindPodServiceAccount(readFile func(name string) ([]byte, error)) (*client.ObjectKey, error) {
// tokenFile is the well-known path for the service account token mounted
// in pods by Kubernetes.
const tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
b, err := readFile(tokenFile)
if err != nil {
return nil, fmt.Errorf("failed to read service account token file %s: %w", tokenFile, err)
}
tok, _, err := jwt.NewParser().ParseUnverified(string(b), jwt.MapClaims{})
if err != nil {
return nil, fmt.Errorf("failed to parse service account token: %w", err)
}
sub, err := tok.Claims.GetSubject()
if err != nil {
return nil, fmt.Errorf("failed to get subject from service account token: %w", err)
}
parts := strings.Split(sub, ":")
if len(parts) != 4 {
return nil, fmt.Errorf("invalid subject format in service account token: %s", sub)
}
return &client.ObjectKey{
Namespace: parts[2],
Name: parts[3],
}, nil
}
Loading
Loading