From 09797df9582851b9c75621c9573b3434e6204ac2 Mon Sep 17 00:00:00 2001 From: Matt DeBoer Date: Mon, 2 Oct 2017 13:12:42 -0700 Subject: [PATCH] use a UI wrapper to auto-decode error messages update docs to reflect optional config --- builder/amazon/common/access_config.go | 6 ++ builder/amazon/common/errors.go | 58 +++++++++++++++ builder/amazon/common/errors_test.go | 70 +++++++++++++++++++ .../docs/builders/amazon-chroot.html.md | 5 ++ .../source/docs/builders/amazon-ebs.html.md | 5 ++ .../docs/builders/amazon-ebssurrogate.html.md | 5 ++ .../docs/builders/amazon-ebsvolume.html.md | 5 ++ .../docs/builders/amazon-instance.html.md | 5 ++ 8 files changed, 159 insertions(+) create mode 100644 builder/amazon/common/errors.go create mode 100644 builder/amazon/common/errors_test.go diff --git a/builder/amazon/common/access_config.go b/builder/amazon/common/access_config.go index 161b91375..8e36ead63 100644 --- a/builder/amazon/common/access_config.go +++ b/builder/amazon/common/access_config.go @@ -19,6 +19,7 @@ import ( type AccessConfig struct { AccessKey string `mapstructure:"access_key"` CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2"` + DecodeAuthZMessages bool `mapstructure:"decode_authorization_messages"` MFACode string `mapstructure:"mfa_code"` ProfileName string `mapstructure:"profile"` RawRegion string `mapstructure:"region"` @@ -91,6 +92,11 @@ func (c *AccessConfig) Session() (*session.Session, error) { } log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName) } + + if c.DecodeAuthZMessages { + DecodeAuthZMessages(c.session) + } + return c.session, nil } diff --git a/builder/amazon/common/errors.go b/builder/amazon/common/errors.go new file mode 100644 index 000000000..f12f58e7d --- /dev/null +++ b/builder/amazon/common/errors.go @@ -0,0 +1,58 @@ +package common + +import ( + "fmt" + "log" + "regexp" + + "github.com/aws/aws-sdk-go/aws/awserr" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/sts" +) + +var encodedFailureMessagePattern = regexp.MustCompile(`(?i)(.*) Encoded authorization failure message: ([\w-]+) ?( .*)?`) + +type stsDecoder interface { + DecodeAuthorizationMessage(input *sts.DecodeAuthorizationMessageInput) (*sts.DecodeAuthorizationMessageOutput, error) +} + +// decodeError replaces encoded authorization messages with the +// decoded results +func decodeAWSError(decoder stsDecoder, err error) error { + + groups := encodedFailureMessagePattern.FindStringSubmatch(err.Error()) + if groups != nil && len(groups) > 1 { + result, decodeErr := decoder.DecodeAuthorizationMessage(&sts.DecodeAuthorizationMessageInput{ + EncodedMessage: aws.String(groups[2]), + }) + if decodeErr == nil { + msg := aws.StringValue(result.DecodedMessage) + return fmt.Errorf("%s Authorization failure message: '%s'%s", groups[1], msg, groups[3]) + } + log.Printf("[WARN] Attempted to decode authorization message, but received: %v", decodeErr) + } + return err +} + +// DecodeAuthZMessages enables automatic decoding of any +// encoded authorization messages +func DecodeAuthZMessages(sess *session.Session) { + azd := &authZMessageDecoder{ + Decoder: sts.New(sess), + } + sess.Handlers.UnmarshalError.AfterEachFn = azd.afterEachFn +} + +type authZMessageDecoder struct { + Decoder stsDecoder +} + +func (a *authZMessageDecoder) afterEachFn(item request.HandlerListRunItem) bool { + if err, ok := item.Request.Error.(awserr.Error); ok && err.Code() == "UnauthorizedOperation" { + item.Request.Error = decodeAWSError(a.Decoder, err) + } + return true +} diff --git a/builder/amazon/common/errors_test.go b/builder/amazon/common/errors_test.go new file mode 100644 index 000000000..b2c05390e --- /dev/null +++ b/builder/amazon/common/errors_test.go @@ -0,0 +1,70 @@ +package common + +import ( + "fmt" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/sts" + + "github.com/aws/aws-sdk-go/aws/awserr" +) + +type mockSTS struct { +} + +func (m *mockSTS) DecodeAuthorizationMessage(input *sts.DecodeAuthorizationMessageInput) (*sts.DecodeAuthorizationMessageOutput, error) { + return &sts.DecodeAuthorizationMessageOutput{ + DecodedMessage: aws.String(`{ + "allowed": false, + "explicitDeny": true, + "matchedStatements": {} + }`), + }, nil +} + +func TestErrorsParsing_RequestFailure(t *testing.T) { + + ae := awserr.New("UnauthorizedOperation", + `You are not authorized to perform this operation. Encoded authorization failure message: D9Q7oicjOMr9l2CC-NPP1FiZXK9Ijia1k-3l0siBFCcrK3oSuMFMkBIO5TNj0HdXE-WfwnAcdycFOohfKroNO6toPJEns8RFVfy_M_IjNGmrEFJ6E62pnmBW0OLrMsXxR9FQE4gB4gJzSM0AD6cV6S3FOfqYzWBRX-sQdOT4HryGkFNRoFBr9Xbp-tRwiadwkbdHdfnV9fbRkXmnwCdULml16NBSofC4ZPepLMKmIB5rKjwk-m179UUh2XA-J5no0si6XcRo5GbHQB5QfCIwSHL4vsro2wLZUd16-8OWKyr3tVlTbQe0ERZskqRqRQ5E28QuiBCVV6XstUyo-T4lBSr75Fgnyr3wCO-dS3b_5Ns3WzA2JD4E2AJOAStXIU8IH5YuKkAg7C-dJMuBMPpmKCBEXhNoHDwCyOo5PsV3xMlc0jSb0qYGpfst_TDDtejcZfn7NssUjxVq9qkdH-OXz2gPoQB-hX8ycmZCL5UZwKc3TCLUr7TGnudHjmnMrE9cUo-yTCWfyHPLprhiYhTCKW18EikJ0O1EKI3FJ_b4F19_jFBPARjSwQc7Ut6MNCVzrPdZGYSF6acj5gPaxdy9uSkVQwWXK7Pd5MFP7EBDE1_DgYbzodgwDO2PXeVFUbSLBHKWo_ebZS9ZX2nYPcGss_sYaly0ZVSIJXp7G58B5BoFVhvVH6jYnF9XiAOjMltuP_ycu1pQP1lki500RY3baLvfeYeAsB38XZHKEgWZzq7Fei-uh89q0cjJTmlVyrfRU3q6`, + fmt.Errorf("You can't do it!!")) + rf := awserr.NewRequestFailure(ae, 400, "abc-def-123-456") + + result := decodeAWSError(&mockSTS{}, rf) + if result == nil { + t.Error("Expected resulting error") + } + if !strings.Contains(result.Error(), "Authorization failure message:") { + t.Error("Expected authorization failure message") + } +} + +func TestErrorsParsing_NonAuthorizationFailure(t *testing.T) { + + ae := awserr.New("BadRequest", + `You did something wrong. Try again`, + fmt.Errorf("Request was no good.")) + rf := awserr.NewRequestFailure(ae, 400, "abc-def-123-456") + + result := decodeAWSError(&mockSTS{}, rf) + if result == nil { + t.Error("Expected resulting error") + } + if result != rf { + t.Error("Expected original error to be returned unchanged") + } +} + +func TestErrorsParsing_NonAWSError(t *testing.T) { + + err := fmt.Errorf("Random error occurred") + + result := decodeAWSError(&mockSTS{}, err) + if result == nil { + t.Error("Expected resulting error") + } + if result != err { + t.Error("Expected original error to be returned unchanged") + } +} diff --git a/website/source/docs/builders/amazon-chroot.html.md b/website/source/docs/builders/amazon-chroot.html.md index 42acdcfda..bcc837e1a 100644 --- a/website/source/docs/builders/amazon-chroot.html.md +++ b/website/source/docs/builders/amazon-chroot.html.md @@ -121,6 +121,11 @@ each category, the available configuration keys are alphabetized. provider whose API is compatible with aws EC2. Specify another endpoint like this `https://ec2.custom.endpoint.com`. +- `decode_authorization_messages` (boolean) - Enable automatic decoding of any + encoded authorization (error) messages using the `sts:DecodeAuthorizationMessage` API. + Note: requires that the effective user/role have permissions to `sts:DecodeAuthorizationMessage` + on resource `*`. Default `false`. + - `device_path` (string) - The path to the device where the root volume of the source AMI will be attached. This defaults to "" (empty string), which forces Packer to find an open device automatically. diff --git a/website/source/docs/builders/amazon-ebs.html.md b/website/source/docs/builders/amazon-ebs.html.md index 78d8f1453..05596e082 100644 --- a/website/source/docs/builders/amazon-ebs.html.md +++ b/website/source/docs/builders/amazon-ebs.html.md @@ -151,6 +151,11 @@ builder. provider whose API is compatible with aws EC2. Specify another endpoint like this `https://ec2.custom.endpoint.com`. +- `decode_authorization_messages` (boolean) - Enable automatic decoding of any + encoded authorization (error) messages using the `sts:DecodeAuthorizationMessage` API. + Note: requires that the effective user/role have permissions to `sts:DecodeAuthorizationMessage` + on resource `*`. Default `false`. + - `disable_stop_instance` (boolean) - Packer normally stops the build instance after all provisioners have run. For Windows instances, it is sometimes desirable to [run Sysprep](http://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ami-create-standard.html) diff --git a/website/source/docs/builders/amazon-ebssurrogate.html.md b/website/source/docs/builders/amazon-ebssurrogate.html.md index 78a4eeeb1..e79216d7c 100644 --- a/website/source/docs/builders/amazon-ebssurrogate.html.md +++ b/website/source/docs/builders/amazon-ebssurrogate.html.md @@ -144,6 +144,11 @@ builder. provider whose API is compatible with aws EC2. Specify another endpoint like this `https://ec2.custom.endpoint.com`. +- `decode_authorization_messages` (boolean) - Enable automatic decoding of any + encoded authorization (error) messages using the `sts:DecodeAuthorizationMessage` API. + Note: requires that the effective user/role have permissions to `sts:DecodeAuthorizationMessage` + on resource `*`. Default `false`. + - `disable_stop_instance` (boolean) - Packer normally stops the build instance after all provisioners have run. For Windows instances, it is sometimes desirable to [run Sysprep](http://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ami-create-standard.html) diff --git a/website/source/docs/builders/amazon-ebsvolume.html.md b/website/source/docs/builders/amazon-ebsvolume.html.md index 64cf2a235..4e032ae1a 100644 --- a/website/source/docs/builders/amazon-ebsvolume.html.md +++ b/website/source/docs/builders/amazon-ebsvolume.html.md @@ -137,6 +137,11 @@ builder. } ``` +- `decode_authorization_messages` (boolean) - Enable automatic decoding of any + encoded authorization (error) messages using the `sts:DecodeAuthorizationMessage` API. + Note: requires that the effective user/role have permissions to `sts:DecodeAuthorizationMessage` + on resource `*`. Default `false`. + - `ebs_optimized` (boolean) - Mark instance as [EBS Optimized](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSOptimized.html). Default `false`. diff --git a/website/source/docs/builders/amazon-instance.html.md b/website/source/docs/builders/amazon-instance.html.md index 42037164d..317692c02 100644 --- a/website/source/docs/builders/amazon-instance.html.md +++ b/website/source/docs/builders/amazon-instance.html.md @@ -189,6 +189,11 @@ builder. provider whose API is compatible with aws EC2. Specify another endpoint like this `https://ec2.custom.endpoint.com`. +- `decode_authorization_messages` (boolean) - Enable automatic decoding of any + encoded authorization (error) messages using the `sts:DecodeAuthorizationMessage` API. + Note: requires that the effective user/role have permissions to `sts:DecodeAuthorizationMessage` + on resource `*`. Default `false`. + - `ebs_optimized` (boolean) - Mark instance as [EBS Optimized](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSOptimized.html). Default `false`.