"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:
parent
89ba76b4aa
commit
c5bcb97d06
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue