Skip to content

Commit 8afb1fc

Browse files
common: use regional STS on non-default regions
STS is the service used by AWS for emitting authentication tokens for API clients. This comes in two variants: v1 (global) and v2 (regional). As of today (2024-04-24), the default for the Go SDK is "legacy", i.e. if the connection is used to communicate with a non-default region it will use a regional endpoint, otherwise it'll use the global endpoint. Builds are generally not affected by operations like these as the SDK will pick the right type of endpoint for that, but problems may arise later, when copying AMIs for example, as they will need tokens compatible with both the source and destination regions. This means that if the build was performed in a default region, then copied to a non-default region, we'll have gotten a v1 (global) token, which will be rejected by the target region, causing the build to fail. This is already fixable by user-action, through either a setting in their AWS config file, or through an environment variable, but this may come as a surprise if users aren't aware of that pitfall. Therefore, this commit attempts to heuristically determine if an action may fail in the process, and enable regional endpoints for the EC2 session we create during a build. Note: the volume builder and the post-processor are not affected by this, as they only work within one region at a time, so the SDK will choose the right type of endpoint/token for the action, and no cross-region action will be done.
1 parent abeb4a7 commit 8afb1fc

7 files changed

Lines changed: 147 additions & 3 deletions

File tree

builder/chroot/builder.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"fmt"
1717
"runtime"
1818

19+
"github.com/aws/aws-sdk-go/aws/endpoints"
1920
"github.com/aws/aws-sdk-go/service/ec2"
2021
"github.com/hashicorp/hcl/v2/hcldec"
2122
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
@@ -420,6 +421,22 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
420421
if err != nil {
421422
return nil, err
422423
}
424+
425+
// If the AMI copies to a region that is not part of the default regions,
426+
// we will switch to using regional STS endpoints for authentication, as
427+
// these non-default regions only support STSv2 tokens, and by default
428+
// we reach global endpoints which provide STSv1 tokens, leading to
429+
// errors when copying to those non-default regional endpoints.
430+
nonDefaultRegions := b.config.AMIConfig.NonDefaultRegions(&b.config.AccessConfig)
431+
if nonDefaultRegions != nil &&
432+
session.Config.STSRegionalEndpoint == endpoints.LegacySTSEndpoint {
433+
ui.Say(fmt.Sprintf("The configuration uses non-default regions: %v\n"+
434+
"This will likely fail when contacting those endpoints.\n"+
435+
"To make this message disappear, AWS_STS_REGIONAL_ENDPOINTS=regional "+
436+
"should be set in your environment", nonDefaultRegions))
437+
session.Config.STSRegionalEndpoint = endpoints.RegionalSTSEndpoint
438+
}
439+
423440
ec2conn := ec2.New(session)
424441

425442
wrappedCommand := func(command string) (string, error) {

builder/common/access_config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ func (c *AccessConfig) Session() (*session.Session, error) {
277277
return nil, err
278278
}
279279
log.Printf("Found region %s", *sess.Config.Region)
280+
280281
c.session = sess
281282

282283
cp, err := c.session.Config.Credentials.Get()

builder/common/ami_config.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,83 @@ func (c *AMIConfig) prepareRegions(accessConfig *AccessConfig) (errs []error) {
302302
return errs
303303
}
304304

305+
func (c AMIConfig) getRegions() []string {
306+
regions := map[string]struct{}{}
307+
308+
for _, region := range c.AMIRegions {
309+
regions[region] = struct{}{}
310+
}
311+
312+
for region := range c.AMIRegionKMSKeyIDs {
313+
regions[region] = struct{}{}
314+
}
315+
316+
ret := make([]string, 0, len(regions))
317+
for region := range regions {
318+
ret = append(ret, region)
319+
}
320+
321+
return ret
322+
}
323+
324+
// NonDefaultRegions attempts to detect usage of non-default regions.
325+
//
326+
// If a non-default region is defined, the build should use regional STS
327+
// endpoints instead of the global one, as these do not support the type of
328+
// token required by those endpoints.
329+
//
330+
// So this is meant to be called by builders/post-processors that need to
331+
// interact with AWS in those regions, so they can change the value of the
332+
// Session.STSEndpoint
333+
func (c *AMIConfig) NonDefaultRegions(accessConfig *AccessConfig) []string {
334+
var retRegions []string
335+
336+
// If the default (build) region is already a non-default one, we will
337+
// automatically use STSv2 tokens, even in 'legacy' (default) mode. Therefore
338+
// in such a case, we can immediately return as we won't have a problem
339+
// afterwards when it is time to copy the AMI to other regions.
340+
if IsNonDefaultRegion(accessConfig.RawRegion) {
341+
return retRegions
342+
}
343+
344+
regions := c.getRegions()
345+
for _, reg := range regions {
346+
if IsNonDefaultRegion(reg) {
347+
retRegions = append(retRegions, reg)
348+
}
349+
}
350+
351+
return retRegions
352+
}
353+
354+
// Return true if the `region` is not a default one.
355+
//
356+
// Any region that is not one of those that are defined here will require opt-in
357+
// and STSv2, hence why we try to figure it out here.
358+
func IsNonDefaultRegion(region string) bool {
359+
switch region {
360+
case "ap-south-1",
361+
"eu-north-1",
362+
"eu-west-3",
363+
"eu-west-2",
364+
"eu-west-1",
365+
"ap-northeast-3",
366+
"ap-northeast-2",
367+
"ap-northeast-1",
368+
"ca-central-1",
369+
"sa-east-1",
370+
"ap-southeast-1",
371+
"ap-southeast-2",
372+
"eu-central-1",
373+
"us-east-1",
374+
"us-east-2",
375+
"us-west-1",
376+
"us-west-2":
377+
return false
378+
}
379+
return true
380+
}
381+
305382
// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html
306383
func ValidateKmsKey(kmsKey string) (valid bool) {
307384
//Pattern for matching KMS Key ID for multi-region keys

builder/ebs/builder.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"fmt"
1717
"time"
1818

19+
"github.com/aws/aws-sdk-go/aws/endpoints"
1920
"github.com/aws/aws-sdk-go/service/ec2"
2021
"github.com/aws/aws-sdk-go/service/iam"
2122
"github.com/hashicorp/hcl/v2/hcldec"
@@ -208,6 +209,21 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
208209
return nil, err
209210
}
210211

212+
// If the AMI copies to a region that is not part of the default regions,
213+
// we will switch to using regional STS endpoints for authentication, as
214+
// these non-default regions only support STSv2 tokens, and by default
215+
// we reach global endpoints which provide STSv1 tokens, leading to
216+
// errors when copying to those non-default regional endpoints.
217+
nonDefaultRegions := b.config.AMIConfig.NonDefaultRegions(&b.config.AccessConfig)
218+
if nonDefaultRegions != nil &&
219+
session.Config.STSRegionalEndpoint == endpoints.LegacySTSEndpoint {
220+
ui.Say(fmt.Sprintf("The configuration uses non-default regions: %v\n"+
221+
"This will likely fail when contacting those endpoints.\n"+
222+
"To make this message disappear, AWS_STS_REGIONAL_ENDPOINTS=regional "+
223+
"should be set in your environment", nonDefaultRegions))
224+
session.Config.STSRegionalEndpoint = endpoints.RegionalSTSEndpoint
225+
}
226+
211227
ec2conn := ec2.New(session)
212228
iam := iam.New(session)
213229
// Setup the state bag and initial state for the steps

builder/ebs/builder_acc_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func TestAccBuilder_EbsRegionCopy(t *testing.T) {
6464
}
6565
_ = ami.CleanUpAmi()
6666
ami = amazon_acc.AMIHelper{
67-
Region: "us-west-2",
67+
Region: "ca-west-1",
6868
Name: amiName,
6969
}
7070
_ = ami.CleanUpAmi()
@@ -76,7 +76,7 @@ func TestAccBuilder_EbsRegionCopy(t *testing.T) {
7676
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
7777
}
7878
}
79-
return checkRegionCopy(amiName, []string{"us-east-1", "us-west-2"})
79+
return checkRegionCopy(amiName, []string{"us-east-1", "ca-west-1"})
8080
},
8181
}
8282
acctest.TestPlugin(t, testCase)
@@ -1546,7 +1546,7 @@ const testBuilderAccRegionCopy = `
15461546
"source_ami": "ami-76b2a71e",
15471547
"ssh_username": "ubuntu",
15481548
"ami_name": "%s",
1549-
"ami_regions": ["us-east-1", "us-west-2"]
1549+
"ami_regions": ["us-east-1", "ca-west-1"]
15501550
}]
15511551
}
15521552
`

builder/ebssurrogate/builder.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"errors"
1414
"fmt"
1515

16+
"github.com/aws/aws-sdk-go/aws/endpoints"
1617
"github.com/aws/aws-sdk-go/service/ec2"
1718
"github.com/aws/aws-sdk-go/service/iam"
1819
"github.com/hashicorp/hcl/v2/hcldec"
@@ -232,6 +233,21 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
232233
return nil, err
233234
}
234235

236+
// If the AMI copies to a region that is not part of the default regions,
237+
// we will switch to using regional STS endpoints for authentication, as
238+
// these non-default regions only support STSv2 tokens, and by default
239+
// we reach global endpoints which provide STSv1 tokens, leading to
240+
// errors when copying to those non-default regional endpoints.
241+
nonDefaultRegions := b.config.AMIConfig.NonDefaultRegions(&b.config.AccessConfig)
242+
if nonDefaultRegions != nil &&
243+
session.Config.STSRegionalEndpoint == endpoints.LegacySTSEndpoint {
244+
ui.Say(fmt.Sprintf("The configuration uses non-default regions: %v\n"+
245+
"This will likely fail when contacting those endpoints.\n"+
246+
"To make this message disappear, AWS_STS_REGIONAL_ENDPOINTS=regional "+
247+
"should be set in your environment", nonDefaultRegions))
248+
session.Config.STSRegionalEndpoint = endpoints.RegionalSTSEndpoint
249+
}
250+
235251
ec2conn := ec2.New(session)
236252
iam := iam.New(session)
237253

builder/instance/builder.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"os"
1616
"strings"
1717

18+
"github.com/aws/aws-sdk-go/aws/endpoints"
1819
"github.com/aws/aws-sdk-go/service/ec2"
1920
"github.com/aws/aws-sdk-go/service/iam"
2021
"github.com/hashicorp/hcl/v2/hcldec"
@@ -258,6 +259,22 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
258259
if err != nil {
259260
return nil, err
260261
}
262+
263+
// If the AMI copies to a region that is not part of the default regions,
264+
// we will switch to using regional STS endpoints for authentication, as
265+
// these non-default regions only support STSv2 tokens, and by default
266+
// we reach global endpoints which provide STSv1 tokens, leading to
267+
// errors when copying to those non-default regional endpoints.
268+
nonDefaultRegions := b.config.AMIConfig.NonDefaultRegions(&b.config.AccessConfig)
269+
if nonDefaultRegions != nil &&
270+
session.Config.STSRegionalEndpoint == endpoints.LegacySTSEndpoint {
271+
ui.Say(fmt.Sprintf("The configuration uses non-default regions: %v\n"+
272+
"This will likely fail when contacting those endpoints.\n"+
273+
"To make this message disappear, AWS_STS_REGIONAL_ENDPOINTS=regional "+
274+
"should be set in your environment", nonDefaultRegions))
275+
session.Config.STSRegionalEndpoint = endpoints.RegionalSTSEndpoint
276+
}
277+
261278
ec2conn := ec2.New(session)
262279
iam := iam.New(session)
263280

0 commit comments

Comments
 (0)