@@ -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,29 @@ 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+ // In most cases it's possible to extract the region from the AWS resource
120+ // we are trying to access, e.g. ECR repositories have the region in the
121+ // registry part of the URL, KMS ARNs also have the region in the ARN.
122+ // However, in the S3 case for example, the region is not part necessarily
123+ // part of the Flux object spec, the spec.region field is optional and the
124+ // bucket name or endpoint also do not contain the region. So for supporting
125+ // this feature for the Bucket API we must take advantage of the spec.region
126+ // field and tell users to set it on the object spec.
127+ switch {
128+ case o .ArtifactRepository != "" :
129+ // We can safely ignore the error here, auth.GetToken() has already called
130+ // ParseArtifactRepository() and validated the repository at this point.
131+ ecrRegion , _ := p .ParseArtifactRepository (o .ArtifactRepository )
132+ stsRegion = ecrRegion
133+ default :
134+ return nil , errors .New ("an AWS region is required for authenticating with a service account. " +
135+ "please specify it on the Flux object spec" )
136+ }
106137 }
107138
108139 roleARN , err := getRoleARN (serviceAccount )
@@ -151,31 +182,41 @@ func (p Provider) NewTokenForServiceAccount(ctx context.Context, oidcToken strin
151182 return token , nil
152183}
153184
154- // GetArtifactCacheKey implements auth.Provider.
155- func (Provider ) GetArtifactCacheKey (artifactRepository string ) string {
156- if _ , ecrRegion , ok := ParseRegistry (artifactRepository ); ok {
157- return ecrRegion
185+ // This regex is sourced from the AWS ECR Credential Helper (https://github.com/awslabs/amazon-ecr-credential-helper).
186+ // It covers both public AWS partitions like amazonaws.com, China partitions like amazonaws.com.cn, and non-public partitions.
187+ const registryPattern = `([0-9+]*).dkr.ecr(?:-fips)?\.([^/.]*)\.(amazonaws\.com[.cn]*|sc2s\.sgov\.gov|c2s\.ic\.gov|cloud\.adc-e\.uk|csp\.hci\.ic\.gov)`
188+
189+ var registryRegex = regexp .MustCompile (registryPattern )
190+
191+ // ParseArtifactRepository implements auth.Provider.
192+ // ParseArtifactRepository returns the ECR region.
193+ func (Provider ) ParseArtifactRepository (artifactRepository string ) (string , error ) {
194+ registry , err := auth .GetRegistryFromArtifactRepository (artifactRepository )
195+ if err != nil {
196+ return "" , err
197+ }
198+
199+ parts := registryRegex .FindAllStringSubmatch (registry , - 1 )
200+ if len (parts ) < 1 || len (parts [0 ]) < 3 {
201+ return "" , fmt .Errorf ("invalid AWS registry: '%s'. must match %s" ,
202+ registry , registryPattern )
158203 }
159- return ""
204+
205+ // For issuing AWS registry credentials the ECR region is required.
206+ ecrRegion := parts [0 ][2 ]
207+ return ecrRegion , nil
160208}
161209
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 ) {
210+ // NewArtifactRegistryCredentials implements auth.Provider.
211+ func (p Provider ) NewArtifactRegistryCredentials (ctx context.Context , ecrRegion string ,
212+ accessToken auth.Token , opts ... auth.Option ) (* auth.ArtifactRegistryCredentials , error ) {
165213
166214 var o auth.Options
167215 o .Apply (opts ... )
168216
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-
176217 conf := aws.Config {
177218 Region : ecrRegion ,
178- Credentials : credsProvider ,
219+ Credentials : accessToken .( * Token ). CredentialsProvider () ,
179220 }
180221
181222 if hc := o .GetHTTPClient (); hc != nil {
@@ -209,8 +250,10 @@ func (p Provider) NewArtifactRegistryToken(ctx context.Context, artifactReposito
209250 expiresAt = * exp
210251 }
211252 return & auth.ArtifactRegistryCredentials {
212- Username : s [0 ],
213- Password : s [1 ],
253+ Authenticator : authn .FromConfig (authn.AuthConfig {
254+ Username : s [0 ],
255+ Password : s [1 ],
256+ }),
214257 ExpiresAt : expiresAt ,
215258 }, nil
216259}
0 commit comments