@@ -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,23 @@ 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 := os .Getenv ("AWS_REGION" )
58+ if stsRegion == "" {
59+ // A region is required. EKS sets this environment variable variable automatically
60+ // if the controller pod is properly configured with IRSA or EKS Pod Identity, so
61+ // we can rely on this and communicate this to users since this is controller-level
62+ // configuration.
63+ return nil , errors .New ("AWS_REGION environment variable is not set in the Flux controller. " +
64+ "if you have properly configured IAM Roles for Service Accounts (IRSA) or EKS Pod Identity, " +
65+ "please delete/replace the controller pod so the EKS admission controllers can inject this " +
66+ "environment variable, or set it manually if the cluster is not EKS" )
5667 }
5768 awsOpts = append (awsOpts , config .WithRegion (stsRegion ))
5869
@@ -100,9 +111,23 @@ func (p Provider) NewTokenForServiceAccount(ctx context.Context, oidcToken strin
100111 var o auth.Options
101112 o .Apply (opts ... )
102113
103- stsRegion , err := getSTSRegion ()
104- if err != nil {
105- return nil , err
114+ stsRegion := o .STSRegion
115+ if stsRegion == "" {
116+ // A region is also required here, but in this case we can't rely on IRSA or
117+ // EKS Pod Identity for the controller pod because this is object-level
118+ // configuration, so we show a different error message.
119+ switch {
120+ // None of the Flux APIs for artifact repositories have a region field.
121+ // However, we can take advantage of the fact that ECR repositories have
122+ // a region we can use.
123+ case o .ArtifactRepository != "" :
124+ // We can safely ignore the error here, auth.GetToken() has already called
125+ // ParseArtifactRepository() and validated the repository at this point.
126+ ecrRegion , _ := p .ParseArtifactRepository (o .ArtifactRepository )
127+ stsRegion = ecrRegion
128+ default :
129+ return nil , errors .New ("an AWS region is required for authenticating with a service account" )
130+ }
106131 }
107132
108133 roleARN , err := getRoleARN (serviceAccount )
@@ -151,31 +176,41 @@ func (p Provider) NewTokenForServiceAccount(ctx context.Context, oidcToken strin
151176 return token , nil
152177}
153178
154- // GetArtifactCacheKey implements auth.Provider.
155- func (Provider ) GetArtifactCacheKey (artifactRepository string ) string {
156- if _ , ecrRegion , ok := ParseRegistry (artifactRepository ); ok {
157- return ecrRegion
179+ // This regex is sourced from the AWS ECR Credential Helper (https://github.com/awslabs/amazon-ecr-credential-helper).
180+ // It covers both public AWS partitions like amazonaws.com, China partitions like amazonaws.com.cn, and non-public partitions.
181+ const registryPattern = `([0-9+]*).dkr.ecr(?:-fips)?\.([^/.]*)\.(amazonaws\.com[.cn]*|sc2s\.sgov\.gov|c2s\.ic\.gov|cloud\.adc-e\.uk|csp\.hci\.ic\.gov)`
182+
183+ var registryRegex = regexp .MustCompile (registryPattern )
184+
185+ // ParseArtifactRepository implements auth.Provider.
186+ // ParseArtifactRepository returns the ECR region.
187+ func (Provider ) ParseArtifactRepository (artifactRepository string ) (string , error ) {
188+ registry , err := auth .GetRegistryFromArtifactRepository (artifactRepository )
189+ if err != nil {
190+ return "" , err
191+ }
192+
193+ parts := registryRegex .FindAllStringSubmatch (registry , - 1 )
194+ if len (parts ) < 1 || len (parts [0 ]) < 3 {
195+ return "" , fmt .Errorf ("invalid AWS registry: '%s'. must match %s" ,
196+ registry , registryPattern )
158197 }
159- return ""
198+
199+ // For issuing AWS registry credentials the ECR region is required.
200+ ecrRegion := parts [0 ][2 ]
201+ return ecrRegion , nil
160202}
161203
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 ) {
204+ // NewArtifactRegistryCredentials implements auth.Provider.
205+ func (p Provider ) NewArtifactRegistryCredentials (ctx context.Context , ecrRegion string ,
206+ accessToken auth.Token , opts ... auth.Option ) (* auth.ArtifactRegistryCredentials , error ) {
165207
166208 var o auth.Options
167209 o .Apply (opts ... )
168210
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-
176211 conf := aws.Config {
177212 Region : ecrRegion ,
178- Credentials : credsProvider ,
213+ Credentials : accessToken .( * Token ). CredentialsProvider () ,
179214 }
180215
181216 if hc := o .GetHTTPClient (); hc != nil {
@@ -209,8 +244,10 @@ func (p Provider) NewArtifactRegistryToken(ctx context.Context, artifactReposito
209244 expiresAt = * exp
210245 }
211246 return & auth.ArtifactRegistryCredentials {
212- Username : s [0 ],
213- Password : s [1 ],
247+ Authenticator : authn .FromConfig (authn.AuthConfig {
248+ Username : s [0 ],
249+ Password : s [1 ],
250+ }),
214251 ExpiresAt : expiresAt ,
215252 }, nil
216253}
0 commit comments