Skip to content

Commit a15d4c2

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 a15d4c2

7 files changed

Lines changed: 151 additions & 3 deletions

File tree

builder/chroot/builder.go

Lines changed: 18 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,23 @@ 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 is built on, or copies to, a region that is not part of
426+
// the default regions, we will switch to using regional STS endpoints
427+
// for authentication, as these non-default regions only support STSv2
428+
// tokens, and by default we reach global endpoints which provide
429+
// STSv1 tokens, leading to errors when contacting those non-default
430+
// regional endpoints.
431+
nonDefaultRegions := b.config.AMIConfig.NonDefaultRegions(&b.config.AccessConfig)
432+
if nonDefaultRegions != nil &&
433+
session.Config.STSRegionalEndpoint == endpoints.LegacySTSEndpoint {
434+
ui.Say(fmt.Sprintf("The configuration uses non-default regions: %v\n"+
435+
"This will likely fail when contacting those endpoints.\n"+
436+
"To make this message disappear, AWS_STS_REGIONAL_ENDPOINTS=regional "+
437+
"should be set in your environment", nonDefaultRegions))
438+
session.Config.STSRegionalEndpoint = endpoints.RegionalSTSEndpoint
439+
}
440+
423441
ec2conn := ec2.New(session)
424442

425443
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: 17 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,22 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
208209
return nil, err
209210
}
210211

212+
// If the AMI is built on, or copies to, a region that is not part of
213+
// the default regions, we will switch to using regional STS endpoints
214+
// for authentication, as these non-default regions only support STSv2
215+
// tokens, and by default we reach global endpoints which provide
216+
// STSv1 tokens, leading to errors when contacting those non-default
217+
// regional endpoints.
218+
nonDefaultRegions := b.config.AMIConfig.NonDefaultRegions(&b.config.AccessConfig)
219+
if nonDefaultRegions != nil &&
220+
session.Config.STSRegionalEndpoint == endpoints.LegacySTSEndpoint {
221+
ui.Say(fmt.Sprintf("The configuration uses non-default regions: %v\n"+
222+
"This will likely fail when contacting those endpoints.\n"+
223+
"To make this message disappear, AWS_STS_REGIONAL_ENDPOINTS=regional "+
224+
"should be set in your environment", nonDefaultRegions))
225+
session.Config.STSRegionalEndpoint = endpoints.RegionalSTSEndpoint
226+
}
227+
211228
ec2conn := ec2.New(session)
212229
iam := iam.New(session)
213230
// 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: 17 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,22 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
232233
return nil, err
233234
}
234235

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

builder/instance/builder.go

Lines changed: 18 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,23 @@ 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 is built on, or copies to, a region that is not part of
264+
// the default regions, we will switch to using regional STS endpoints
265+
// for authentication, as these non-default regions only support STSv2
266+
// tokens, and by default we reach global endpoints which provide
267+
// STSv1 tokens, leading to errors when contacting those non-default
268+
// regional endpoints.
269+
nonDefaultRegions := b.config.AMIConfig.NonDefaultRegions(&b.config.AccessConfig)
270+
if nonDefaultRegions != nil &&
271+
session.Config.STSRegionalEndpoint == endpoints.LegacySTSEndpoint {
272+
ui.Say(fmt.Sprintf("The configuration uses non-default regions: %v\n"+
273+
"This will likely fail when contacting those endpoints.\n"+
274+
"To make this message disappear, AWS_STS_REGIONAL_ENDPOINTS=regional "+
275+
"should be set in your environment", nonDefaultRegions))
276+
session.Config.STSRegionalEndpoint = endpoints.RegionalSTSEndpoint
277+
}
278+
261279
ec2conn := ec2.New(session)
262280
iam := iam.New(session)
263281

0 commit comments

Comments
 (0)