diff --git a/builder/chroot/builder.go b/builder/chroot/builder.go index ba8150eae..7b8f73648 100644 --- a/builder/chroot/builder.go +++ b/builder/chroot/builder.go @@ -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" @@ -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) { diff --git a/builder/common/access_config.go b/builder/common/access_config.go index 100aaa20d..825fde2a9 100644 --- a/builder/common/access_config.go +++ b/builder/common/access_config.go @@ -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() diff --git a/builder/common/ami_config.go b/builder/common/ami_config.go index ea010b3d1..5714d5534 100644 --- a/builder/common/ami_config.go +++ b/builder/common/ami_config.go @@ -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 diff --git a/builder/ebs/builder.go b/builder/ebs/builder.go index b6c1da1b3..965300656 100644 --- a/builder/ebs/builder.go +++ b/builder/ebs/builder.go @@ -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" @@ -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 diff --git a/builder/ebs/builder_acc_test.go b/builder/ebs/builder_acc_test.go index afc70d853..5514ee844 100644 --- a/builder/ebs/builder_acc_test.go +++ b/builder/ebs/builder_acc_test.go @@ -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() @@ -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) @@ -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"] }] } ` diff --git a/builder/ebssurrogate/builder.go b/builder/ebssurrogate/builder.go index c35945def..6544f71be 100644 --- a/builder/ebssurrogate/builder.go +++ b/builder/ebssurrogate/builder.go @@ -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" @@ -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) diff --git a/builder/instance/builder.go b/builder/instance/builder.go index a018cd28f..e199d9e03 100644 --- a/builder/instance/builder.go +++ b/builder/instance/builder.go @@ -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" @@ -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)