From 3704a053d02654404fec6b384fa86524a4b6e65c Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Tue, 5 Feb 2019 14:07:04 -0800 Subject: [PATCH] move region validation and credential wait into step pre validate --- builder/amazon/chroot/builder.go | 2 + builder/amazon/common/access_config.go | 20 ++++---- builder/amazon/common/step_pre_validate.go | 50 +++++++++++++++++++ builder/amazon/ebs/builder.go | 10 ++-- builder/amazon/ebssurrogate/builder.go | 2 + builder/amazon/ebsvolume/builder.go | 1 + builder/amazon/instance/builder.go | 2 + .../source/docs/builders/amazon-ebs.html.md | 3 ++ 8 files changed, 74 insertions(+), 16 deletions(-) diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index 6ce5e2c16..3b1b4609e 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -205,6 +205,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Setup the state bag and initial state for the steps state := new(multistep.BasicStateBag) state.Put("config", &b.config) + state.Put("access_config", &b.config.AccessConfig) + state.Put("ami_config", &b.config.AMIConfig) state.Put("ec2", ec2conn) state.Put("awsSession", session) state.Put("hook", hook) diff --git a/builder/amazon/common/access_config.go b/builder/amazon/common/access_config.go index 23d8dc3b4..ed6910b34 100644 --- a/builder/amazon/common/access_config.go +++ b/builder/amazon/common/access_config.go @@ -145,6 +145,9 @@ func (c *AccessConfig) GetCredsFromVault() error { if err != nil { return fmt.Errorf("Error getting Vault client: %s", err) } + if c.VaultAWSEngine.EngineName == "" { + c.VaultAWSEngine.EngineName = "aws" + } path := fmt.Sprintf("/%s/creds/%s", c.VaultAWSEngine.EngineName, c.VaultAWSEngine.Name) secret, err := cli.Logical().Read(path) @@ -155,11 +158,14 @@ func (c *AccessConfig) GetCredsFromVault() error { return fmt.Errorf("Vault Secret does not exist at the given path.") } - data, _ := secret.Data["data"] - unpacked := data.(map[string]interface{}) - c.AccessKey = unpacked["access_key"].(string) - c.SecretKey = unpacked["secret_key"].(string) - c.Token = unpacked["security_token"].(string) + c.AccessKey = secret.Data["access_key"].(string) + c.SecretKey = secret.Data["secret_key"].(string) + token := secret.Data["security_token"] + if token != nil { + c.Token = token.(string) + } else { + c.Token = "" + } return nil } @@ -192,10 +198,6 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error { fmt.Errorf("`access_key` and `secret_key` must both be either set or not set.")) } - // abort build early so I can test more quickly - errs = append(errs, - fmt.Errorf("Megan remove this error to continue with build: \n\nAccess: %s, \n\nSecret: %s, \n\nToken: %s", c.AccessKey, c.SecretKey, c.Token)) - return errs } diff --git a/builder/amazon/common/step_pre_validate.go b/builder/amazon/common/step_pre_validate.go index 73b4022b7..018457b21 100644 --- a/builder/amazon/common/step_pre_validate.go +++ b/builder/amazon/common/step_pre_validate.go @@ -3,9 +3,12 @@ package common import ( "context" "fmt" + "log" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" + retry "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) @@ -20,6 +23,53 @@ type StepPreValidate struct { func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) + + if accessConfig, ok := state.GetOk("access_config"); ok { + accessconf := accessConfig.(*AccessConfig) + if !accessconf.VaultAWSEngine.Empty() { + // loop over the authentication a few times to give vault-created creds + // time to become eventually-consistent + ui.Say("You're using Vault-generated AWS credentials. It may take a " + + "few moments for them to become available on AWS. Waiting...") + err := retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) { + ec2conn, err := accessconf.NewEC2Connection() + if err != nil { + return true, err + } + _, err = listEC2Regions(ec2conn) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "AuthFailure" { + log.Printf("Waiting for Vault-generated AWS credentials" + + " to pass authentication... trying again.") + return false, nil + } + } else { + return true, nil + } + + return true, err + }) + + if err != nil { + state.Put("error", fmt.Errorf("Was unable to Authenticate to AWS using Vault-"+ + "Generated Credentials within the retry timeout.")) + return multistep.ActionHalt + } + } + + if amiConfig, ok := state.GetOk("ami_config"); ok { + amiconf := amiConfig.(*AMIConfig) + if !amiconf.AMISkipRegionValidation { + regionsToValidate := append(amiconf.AMIRegions, accessconf.RawRegion) + err := accessconf.ValidateRegion(regionsToValidate...) + if err != nil { + state.Put("error", fmt.Errorf("error validating regions: %v", err)) + return multistep.ActionHalt + } + } + } + } + if s.ForceDeregister { ui.Say("Force Deregister flag found, skipping prevalidating AMI Name") return multistep.ActionContinue diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index c1a36a71b..4734995b5 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -92,13 +92,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe if err != nil { return nil, err } - if !b.config.AMISkipRegionValidation { - regionsToValidate := append(b.config.AMIRegions, b.config.RawRegion) - err := b.config.AccessConfig.ValidateRegion(regionsToValidate...) - if err != nil { - return nil, fmt.Errorf("error validating regions: %v", err) - } - } + ec2conn := ec2.New(session, &aws.Config{ HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(), }) @@ -106,6 +100,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Setup the state bag and initial state for the steps state := new(multistep.BasicStateBag) state.Put("config", &b.config) + state.Put("access_config", &b.config.AccessConfig) + state.Put("ami_config", &b.config.AMIConfig) state.Put("ec2", ec2conn) state.Put("awsSession", session) state.Put("hook", hook) diff --git a/builder/amazon/ebssurrogate/builder.go b/builder/amazon/ebssurrogate/builder.go index 464ebfa31..82dd58125 100644 --- a/builder/amazon/ebssurrogate/builder.go +++ b/builder/amazon/ebssurrogate/builder.go @@ -114,6 +114,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Setup the state bag and initial state for the steps state := new(multistep.BasicStateBag) state.Put("config", &b.config) + state.Put("access_config", &b.config.AccessConfig) + state.Put("ami_config", &b.config.AMIConfig) state.Put("ec2", ec2conn) state.Put("awsSession", session) state.Put("hook", hook) diff --git a/builder/amazon/ebsvolume/builder.go b/builder/amazon/ebsvolume/builder.go index 8a75c05ce..95801c80e 100644 --- a/builder/amazon/ebsvolume/builder.go +++ b/builder/amazon/ebsvolume/builder.go @@ -103,6 +103,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Setup the state bag and initial state for the steps state := new(multistep.BasicStateBag) state.Put("config", &b.config) + state.Put("access_config", &b.config.AccessConfig) state.Put("ec2", ec2conn) state.Put("hook", hook) state.Put("ui", ui) diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index 1d9e29b20..5eef95538 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -184,6 +184,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Setup the state bag and initial state for the steps state := new(multistep.BasicStateBag) state.Put("config", &b.config) + state.Put("access_config", &b.config.AccessConfig) + state.Put("ami_config", &b.config.AMIConfig) state.Put("ec2", ec2conn) state.Put("awsSession", session) state.Put("hook", hook) diff --git a/website/source/docs/builders/amazon-ebs.html.md b/website/source/docs/builders/amazon-ebs.html.md index 6d0f180bc..f0e7d97ce 100644 --- a/website/source/docs/builders/amazon-ebs.html.md +++ b/website/source/docs/builders/amazon-ebs.html.md @@ -516,6 +516,9 @@ builder. flag, you must also set the below options: - `name` (string) - Required. Specifies the name of the role to generate credentials against. This is part of the request URL. + - `engine_name` (string) - The name of the aws secrets engine. In the Vault + docs, this is normally referred to as "aws", and Packer will default to + "aws" if `engine_name` is not set. - `role_arn` (string)- The ARN of the role to assume if credential_type on the Vault role is assumed_role. Must match one of the allowed role ARNs in the Vault role. Optional if the Vault role only allows a single AWS