@@ -19,14 +19,18 @@ package aws
1919import (
2020 "context"
2121 "encoding/base64"
22+ "errors"
2223 "fmt"
2324 "net/http"
25+ "os"
26+ "regexp"
2427 "strings"
2528 "time"
2629
2730 "github.com/aws/aws-sdk-go-v2/aws"
2831 "github.com/aws/aws-sdk-go-v2/config"
2932 "github.com/aws/aws-sdk-go-v2/service/sts"
33+ "github.com/google/go-containerregistry/pkg/authn"
3034 corev1 "k8s.io/api/core/v1"
3135
3236 "github.com/fluxcd/pkg/auth"
@@ -43,16 +47,43 @@ func (Provider) GetName() string {
4347 return ProviderName
4448}
4549
46- // NewDefaultToken implements auth.Provider.
47- func (p Provider ) NewDefaultToken (ctx context.Context , opts ... auth.Option ) (auth.Token , error ) {
50+ // NewControllerToken implements auth.Provider.
51+ func (p Provider ) NewControllerToken (ctx context.Context , opts ... auth.Option ) (auth.Token , error ) {
4852 var o auth.Options
4953 o .Apply (opts ... )
5054
5155 var awsOpts []func (* config.LoadOptions ) error
5256
53- stsRegion , err := getSTSRegion ()
54- if err != nil {
55- return nil , err
57+ stsRegion := o .STSRegion
58+ if stsRegion == "" {
59+ // A region is required. Try to get it somewhere else.
60+ switch {
61+ // For artifact repositories we can take advantage of the fact that ECR
62+ // repositories have a region we can use.
63+ // **Important**: This code path is required for supporting EKS Node Identity
64+ // for artifact repositories! This is because the environment variable
65+ // AWS_REGION is set automatically for IRSA or EKS Pod Identity, but
66+ // not for Node Identity.
67+ // We strive to support Node Identity for container registry-based APIs because
68+ // EKS users also use Node Identity for container images, so this allows a
69+ // simpler/consistent user experience.
70+ case o .ArtifactRepository != "" :
71+ // We can safely ignore the error here, auth.GetToken() has already called
72+ // ParseArtifactRepository() and validated the repository at this point.
73+ ecrRegion , _ := p .ParseArtifactRepository (o .ArtifactRepository )
74+ stsRegion = ecrRegion
75+ // EKS sets this environment variable automatically if the controller pod is
76+ // properly configured with IRSA or EKS Pod Identity, so we can rely on this
77+ // and communicate this to users since this is controller-level configuration.
78+ default :
79+ stsRegion = os .Getenv ("AWS_REGION" )
80+ if stsRegion == "" {
81+ return nil , errors .New ("AWS_REGION environment variable is not set in the Flux controller. " +
82+ "if you have properly configured IAM Roles for Service Accounts (IRSA) or EKS Pod Identity, " +
83+ "please delete/replace the controller pod so the EKS admission controllers can inject this " +
84+ "environment variable, or set it manually if the cluster is not EKS" )
85+ }
86+ }
5687 }
5788 awsOpts = append (awsOpts , config .WithRegion (stsRegion ))
5889
@@ -100,9 +131,27 @@ func (p Provider) NewTokenForServiceAccount(ctx context.Context, oidcToken strin
100131 var o auth.Options
101132 o .Apply (opts ... )
102133
103- stsRegion , err := getSTSRegion ()
104- if err != nil {
105- return nil , err
134+ stsRegion := o .STSRegion
135+ if stsRegion == "" {
136+ // A region is required. Try to get it somewhere else.
137+ switch {
138+ // For artifact repositories we can take advantage of the fact that ECR
139+ // repositories have a region we can use.
140+ case o .ArtifactRepository != "" :
141+ // We can safely ignore the error here, auth.GetToken() has already called
142+ // ParseArtifactRepository() and validated the repository at this point.
143+ ecrRegion , _ := p .ParseArtifactRepository (o .ArtifactRepository )
144+ stsRegion = ecrRegion
145+ // In this case we can't rely on IRSA or EKS Pod Identity for the controller
146+ // pod because this is object-level configuration, so we show a different
147+ // error message.
148+ // In this error message we assume an API that has a region field, e.g. the
149+ // Bucket API. APIs that can extract the region from the ARN (e.g. KMS) will
150+ // never reach this code path.
151+ default :
152+ return nil , errors .New ("an AWS region is required for authenticating with a service account. " +
153+ "please configure one in the object spec" )
154+ }
106155 }
107156
108157 roleARN , err := getRoleARN (serviceAccount )
@@ -151,31 +200,41 @@ func (p Provider) NewTokenForServiceAccount(ctx context.Context, oidcToken strin
151200 return token , nil
152201}
153202
154- // GetArtifactCacheKey implements auth.Provider.
155- func (Provider ) GetArtifactCacheKey (artifactRepository string ) string {
156- if _ , ecrRegion , ok := ParseRegistry (artifactRepository ); ok {
157- return ecrRegion
203+ // This regex is sourced from the AWS ECR Credential Helper (https://github.com/awslabs/amazon-ecr-credential-helper).
204+ // It covers both public AWS partitions like amazonaws.com, China partitions like amazonaws.com.cn, and non-public partitions.
205+ const registryPattern = `([0-9+]*).dkr.ecr(?:-fips)?\.([^/.]*)\.(amazonaws\.com[.cn]*|sc2s\.sgov\.gov|c2s\.ic\.gov|cloud\.adc-e\.uk|csp\.hci\.ic\.gov)`
206+
207+ var registryRegex = regexp .MustCompile (registryPattern )
208+
209+ // ParseArtifactRepository implements auth.Provider.
210+ // ParseArtifactRepository returns the ECR region.
211+ func (Provider ) ParseArtifactRepository (artifactRepository string ) (string , error ) {
212+ registry , err := auth .GetRegistryFromArtifactRepository (artifactRepository )
213+ if err != nil {
214+ return "" , err
215+ }
216+
217+ parts := registryRegex .FindAllStringSubmatch (registry , - 1 )
218+ if len (parts ) < 1 || len (parts [0 ]) < 3 {
219+ return "" , fmt .Errorf ("invalid AWS registry: '%s'. must match %s" ,
220+ registry , registryPattern )
158221 }
159- return ""
222+
223+ // For issuing AWS registry credentials the ECR region is required.
224+ ecrRegion := parts [0 ][2 ]
225+ return ecrRegion , nil
160226}
161227
162- // NewArtifactRegistryToken implements auth.Provider.
163- func (p Provider ) NewArtifactRegistryToken (ctx context.Context , artifactRepository string ,
164- accessToken auth.Token , opts ... auth.Option ) (auth.Token , error ) {
228+ // NewArtifactRegistryCredentials implements auth.Provider.
229+ func (p Provider ) NewArtifactRegistryCredentials (ctx context.Context , ecrRegion string ,
230+ accessToken auth.Token , opts ... auth.Option ) (* auth.ArtifactRegistryCredentials , error ) {
165231
166232 var o auth.Options
167233 o .Apply (opts ... )
168234
169- _ , ecrRegion , ok := ParseRegistry (artifactRepository )
170- if ! ok {
171- return nil , fmt .Errorf ("invalid ecr repository: '%s'" , artifactRepository )
172- }
173-
174- credsProvider := accessToken .(* Token ).CredentialsProvider ()
175-
176235 conf := aws.Config {
177236 Region : ecrRegion ,
178- Credentials : credsProvider ,
237+ Credentials : accessToken .( * Token ). CredentialsProvider () ,
179238 }
180239
181240 if hc := o .GetHTTPClient (); hc != nil {
@@ -209,8 +268,10 @@ func (p Provider) NewArtifactRegistryToken(ctx context.Context, artifactReposito
209268 expiresAt = * exp
210269 }
211270 return & auth.ArtifactRegistryCredentials {
212- Username : s [0 ],
213- Password : s [1 ],
271+ Authenticator : authn .FromConfig (authn.AuthConfig {
272+ Username : s [0 ],
273+ Password : s [1 ],
274+ }),
214275 ExpiresAt : expiresAt ,
215276 }, nil
216277}
0 commit comments