"borrow" access config code from terraform.

This gives us a few benefits:

* timeout early if metadata service can't be reached
* report which auth provider we're using
* give much better errors if something goes wrong
This commit is contained in:
Matthew Hooker 2018-01-04 15:04:07 -08:00
parent 89ba76b4aa
commit c5bcb97d06
No known key found for this signature in database
GPG Key ID: 7B5F933D9CE8C6A1
1 changed files with 81 additions and 18 deletions

View File

@ -1,13 +1,16 @@
package common
import (
"errors"
"fmt"
"log"
"os"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/hashicorp/go-cleanhttp"
@ -16,15 +19,16 @@ import (
// AccessConfig is for common configuration related to AWS access
type AccessConfig struct {
AccessKey string `mapstructure:"access_key"`
CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2"`
MFACode string `mapstructure:"mfa_code"`
ProfileName string `mapstructure:"profile"`
RawRegion string `mapstructure:"region"`
SecretKey string `mapstructure:"secret_key"`
SkipValidation bool `mapstructure:"skip_region_validation"`
Token string `mapstructure:"token"`
session *session.Session
AccessKey string `mapstructure:"access_key"`
CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2"`
MFACode string `mapstructure:"mfa_code"`
ProfileName string `mapstructure:"profile"`
RawRegion string `mapstructure:"region"`
SecretKey string `mapstructure:"secret_key"`
SkipValidation bool `mapstructure:"skip_region_validation"`
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
Token string `mapstructure:"token"`
session *session.Session
}
// Config returns a valid aws.Config object for access to AWS services, or
@ -34,14 +38,78 @@ func (c *AccessConfig) Session() (*session.Session, error) {
return c.session, nil
}
config := aws.NewConfig().WithMaxRetries(11).WithCredentialsChainVerboseErrors(true)
// build a chain provider, lazy-evaluated by aws-sdk
providers := []credentials.Provider{
&credentials.StaticProvider{Value: credentials.Value{
AccessKeyID: c.AccessKey,
SecretAccessKey: c.SecretKey,
SessionToken: c.Token,
}},
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{
Filename: "",
Profile: c.ProfileName,
},
}
if c.ProfileName != "" {
if err := os.Setenv("AWS_PROFILE", c.ProfileName); err != nil {
return nil, fmt.Errorf("Set env error: %s", err)
// Build isolated HTTP client to avoid issues with globally-shared settings
client := cleanhttp.DefaultClient()
// Keep the default timeout (100ms) low as we don't want to wait in non-EC2 environments
client.Timeout = 100 * time.Millisecond
const userTimeoutEnvVar = "AWS_METADATA_TIMEOUT"
userTimeout := os.Getenv(userTimeoutEnvVar)
if userTimeout != "" {
newTimeout, err := time.ParseDuration(userTimeout)
if err == nil {
if newTimeout.Nanoseconds() > 0 {
client.Timeout = newTimeout
} else {
log.Printf("[WARN] Non-positive value of %s (%s) is meaningless, ignoring", userTimeoutEnvVar, newTimeout.String())
}
} else {
log.Printf("[WARN] Error converting %s to time.Duration: %s", userTimeoutEnvVar, err)
}
}
log.Printf("[INFO] Setting AWS metadata API timeout to %s", client.Timeout.String())
cfg := &aws.Config{
HTTPClient: client,
}
if !c.SkipMetadataApiCheck {
// Real AWS should reply to a simple metadata request.
// We check it actually does to ensure something else didn't just
// happen to be listening on the same IP:Port
metadataClient := ec2metadata.New(session.New(cfg))
if metadataClient.Available() {
providers = append(providers, &ec2rolecreds.EC2RoleProvider{
Client: metadataClient,
})
log.Print("[INFO] AWS EC2 instance detected via default metadata" +
" API endpoint, EC2RoleProvider added to the auth chain")
} else {
log.Printf("[INFO] Ignoring AWS metadata API endpoint " +
"as it doesn't return any instance-id")
}
}
creds := credentials.NewChainCredentials(providers)
cp, err := creds.Get()
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
return nil, errors.New("No valid credential sources found for AWS Builder. " +
"Please see https://www.packer.io/docs/builders/amazon.html#specifying-amazon-credentials " +
"for more information on providing credentials for the AWS Builder.")
}
return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)
}
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
config := aws.NewConfig().WithMaxRetries(11).WithCredentialsChainVerboseErrors(true)
config = config.WithCredentials(creds)
if c.RawRegion != "" {
config = config.WithRegion(c.RawRegion)
} else if region := c.metadataRegion(); region != "" {
@ -52,11 +120,6 @@ func (c *AccessConfig) Session() (*session.Session, error) {
config = config.WithEndpoint(c.CustomEndpointEc2)
}
if c.AccessKey != "" {
config = config.WithCredentials(
credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token))
}
opts := session.Options{
SharedConfigState: session.SharedConfigEnable,
Config: *config,