Skip to content

Commit

Permalink
common: use regional STS on non-default regions
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lbajolet-hashicorp committed Apr 24, 2024
1 parent abeb4a7 commit 8afb1fc
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 3 deletions.
17 changes: 17 additions & 0 deletions builder/chroot/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"fmt"
"runtime"

"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/hcl/v2/hcldec"
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
Expand Down Expand Up @@ -420,6 +421,22 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
if err != nil {
return nil, err
}

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

ec2conn := ec2.New(session)

wrappedCommand := func(command string) (string, error) {
Expand Down
1 change: 1 addition & 0 deletions builder/common/access_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ func (c *AccessConfig) Session() (*session.Session, error) {
return nil, err
}
log.Printf("Found region %s", *sess.Config.Region)

c.session = sess

cp, err := c.session.Config.Credentials.Get()
Expand Down
77 changes: 77 additions & 0 deletions builder/common/ami_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,83 @@ func (c *AMIConfig) prepareRegions(accessConfig *AccessConfig) (errs []error) {
return errs
}

func (c AMIConfig) getRegions() []string {
regions := map[string]struct{}{}

for _, region := range c.AMIRegions {
regions[region] = struct{}{}
}

for region := range c.AMIRegionKMSKeyIDs {
regions[region] = struct{}{}
}

ret := make([]string, 0, len(regions))
for region := range regions {
ret = append(ret, region)
}

return ret
}

// NonDefaultRegions attempts to detect usage of non-default regions.
//
// If a non-default region is defined, the build should use regional STS
// endpoints instead of the global one, as these do not support the type of
// token required by those endpoints.
//
// So this is meant to be called by builders/post-processors that need to
// interact with AWS in those regions, so they can change the value of the
// Session.STSEndpoint
func (c *AMIConfig) NonDefaultRegions(accessConfig *AccessConfig) []string {
var retRegions []string

// If the default (build) region is already a non-default one, we will
// automatically use STSv2 tokens, even in 'legacy' (default) mode. Therefore
// in such a case, we can immediately return as we won't have a problem
// afterwards when it is time to copy the AMI to other regions.
if IsNonDefaultRegion(accessConfig.RawRegion) {
return retRegions
}

regions := c.getRegions()
for _, reg := range regions {
if IsNonDefaultRegion(reg) {
retRegions = append(retRegions, reg)
}
}

return retRegions
}

// Return true if the `region` is not a default one.
//
// Any region that is not one of those that are defined here will require opt-in
// and STSv2, hence why we try to figure it out here.
func IsNonDefaultRegion(region string) bool {
switch region {
case "ap-south-1",
"eu-north-1",
"eu-west-3",
"eu-west-2",
"eu-west-1",
"ap-northeast-3",
"ap-northeast-2",
"ap-northeast-1",
"ca-central-1",
"sa-east-1",
"ap-southeast-1",
"ap-southeast-2",
"eu-central-1",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2":
return false
}
return true
}

// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html
func ValidateKmsKey(kmsKey string) (valid bool) {
//Pattern for matching KMS Key ID for multi-region keys
Expand Down
16 changes: 16 additions & 0 deletions builder/ebs/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"fmt"
"time"

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

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

ec2conn := ec2.New(session)
iam := iam.New(session)
// Setup the state bag and initial state for the steps
Expand Down
6 changes: 3 additions & 3 deletions builder/ebs/builder_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestAccBuilder_EbsRegionCopy(t *testing.T) {
}
_ = ami.CleanUpAmi()
ami = amazon_acc.AMIHelper{
Region: "us-west-2",
Region: "ca-west-1",
Name: amiName,
}
_ = ami.CleanUpAmi()
Expand All @@ -76,7 +76,7 @@ func TestAccBuilder_EbsRegionCopy(t *testing.T) {
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
}
}
return checkRegionCopy(amiName, []string{"us-east-1", "us-west-2"})
return checkRegionCopy(amiName, []string{"us-east-1", "ca-west-1"})
},
}
acctest.TestPlugin(t, testCase)
Expand Down Expand Up @@ -1546,7 +1546,7 @@ const testBuilderAccRegionCopy = `
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"ami_name": "%s",
"ami_regions": ["us-east-1", "us-west-2"]
"ami_regions": ["us-east-1", "ca-west-1"]
}]
}
`
Expand Down
16 changes: 16 additions & 0 deletions builder/ebssurrogate/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"errors"
"fmt"

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

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

ec2conn := ec2.New(session)
iam := iam.New(session)

Expand Down
17 changes: 17 additions & 0 deletions builder/instance/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"os"
"strings"

"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/hcl/v2/hcldec"
Expand Down Expand Up @@ -258,6 +259,22 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
if err != nil {
return nil, err
}

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

ec2conn := ec2.New(session)
iam := iam.New(session)

Expand Down

0 comments on commit 8afb1fc

Please sign in to comment.