2019-02-18 08:15:26 +01:00
|
|
|
package awsbase
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/endpoints"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/request"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
|
|
|
"github.com/aws/aws-sdk-go/service/iam"
|
|
|
|
"github.com/aws/aws-sdk-go/service/sts"
|
|
|
|
"github.com/hashicorp/go-cleanhttp"
|
|
|
|
)
|
|
|
|
|
|
|
|
// GetSessionOptions attempts to return valid AWS Go SDK session authentication
|
|
|
|
// options based on pre-existing credential provider, configured profile, or
|
|
|
|
// fallback to automatically a determined session via the AWS Go SDK.
|
|
|
|
func GetSessionOptions(c *Config) (*session.Options, error) {
|
|
|
|
options := &session.Options{
|
|
|
|
Config: aws.Config{
|
|
|
|
HTTPClient: cleanhttp.DefaultClient(),
|
|
|
|
MaxRetries: aws.Int(0),
|
|
|
|
Region: aws.String(c.Region),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-10-11 18:50:12 +02:00
|
|
|
// get and validate credentials
|
2019-02-18 08:15:26 +01:00
|
|
|
creds, err := GetCredentials(c)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-10-11 18:50:12 +02:00
|
|
|
// add the validated credentials to the session options
|
|
|
|
options.Config.Credentials = creds
|
2019-02-18 08:15:26 +01:00
|
|
|
|
|
|
|
if c.Insecure {
|
|
|
|
transport := options.Config.HTTPClient.Transport.(*http.Transport)
|
|
|
|
transport.TLSClientConfig = &tls.Config{
|
|
|
|
InsecureSkipVerify: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.DebugLogging {
|
|
|
|
options.Config.LogLevel = aws.LogLevel(aws.LogDebugWithHTTPBody | aws.LogDebugWithRequestRetries | aws.LogDebugWithRequestErrors)
|
|
|
|
options.Config.Logger = DebugLogger{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return options, nil
|
|
|
|
}
|
|
|
|
|
2019-10-11 18:50:12 +02:00
|
|
|
// GetSession attempts to return valid AWS Go SDK session.
|
2019-02-18 08:15:26 +01:00
|
|
|
func GetSession(c *Config) (*session.Session, error) {
|
|
|
|
options, err := GetSessionOptions(c)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
sess, err := session.NewSessionWithOptions(*options)
|
|
|
|
if err != nil {
|
|
|
|
if IsAWSErr(err, "NoCredentialProviders", "") {
|
2019-10-11 18:50:12 +02:00
|
|
|
return nil, ErrNoValidCredentialSources
|
2019-02-18 08:15:26 +01:00
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("Error creating AWS session: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.MaxRetries > 0 {
|
|
|
|
sess = sess.Copy(&aws.Config{MaxRetries: aws.Int(c.MaxRetries)})
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, product := range c.UserAgentProducts {
|
|
|
|
sess.Handlers.Build.PushBack(request.MakeAddToUserAgentHandler(product.Name, product.Version, product.Extra...))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generally, we want to configure a lower retry theshold for networking issues
|
|
|
|
// as the session retry threshold is very high by default and can mask permanent
|
|
|
|
// networking failures, such as a non-existent service endpoint.
|
|
|
|
// MaxRetries will override this logic if it has a lower retry threshold.
|
|
|
|
// NOTE: This logic can be fooled by other request errors raising the retry count
|
|
|
|
// before any networking error occurs
|
|
|
|
sess.Handlers.Retry.PushBack(func(r *request.Request) {
|
|
|
|
// We currently depend on the DefaultRetryer exponential backoff here.
|
|
|
|
// ~10 retries gives a fair backoff of a few seconds.
|
|
|
|
if r.RetryCount < 9 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// RequestError: send request failed
|
|
|
|
// caused by: Post https://FQDN/: dial tcp: lookup FQDN: no such host
|
|
|
|
if IsAWSErrExtended(r.Error, "RequestError", "send request failed", "no such host") {
|
|
|
|
log.Printf("[WARN] Disabling retries after next request due to networking issue")
|
|
|
|
r.Retryable = aws.Bool(false)
|
|
|
|
}
|
|
|
|
// RequestError: send request failed
|
|
|
|
// caused by: Post https://FQDN/: dial tcp IPADDRESS:443: connect: connection refused
|
|
|
|
if IsAWSErrExtended(r.Error, "RequestError", "send request failed", "connection refused") {
|
|
|
|
log.Printf("[WARN] Disabling retries after next request due to networking issue")
|
|
|
|
r.Retryable = aws.Bool(false)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
if !c.SkipCredsValidation {
|
|
|
|
stsClient := sts.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.StsEndpoint)}))
|
|
|
|
if _, _, err := GetAccountIDAndPartitionFromSTSGetCallerIdentity(stsClient); err != nil {
|
2019-10-11 18:50:12 +02:00
|
|
|
return nil, fmt.Errorf("error using credentials to get account ID: %s", err)
|
2019-02-18 08:15:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return sess, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetSessionWithAccountIDAndPartition attempts to return valid AWS Go SDK session
|
|
|
|
// along with account ID and partition information if available
|
|
|
|
func GetSessionWithAccountIDAndPartition(c *Config) (*session.Session, string, string, error) {
|
|
|
|
sess, err := GetSession(c)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.AssumeRoleARN != "" {
|
|
|
|
accountID, partition, _ := parseAccountIDAndPartitionFromARN(c.AssumeRoleARN)
|
|
|
|
return sess, accountID, partition, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
iamClient := iam.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)}))
|
|
|
|
stsClient := sts.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.StsEndpoint)}))
|
|
|
|
|
|
|
|
if !c.SkipCredsValidation {
|
|
|
|
accountID, partition, err := GetAccountIDAndPartitionFromSTSGetCallerIdentity(stsClient)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", "", fmt.Errorf("error validating provider credentials: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return sess, accountID, partition, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if !c.SkipRequestingAccountId {
|
|
|
|
credentialsProviderName := ""
|
|
|
|
|
|
|
|
if credentialsValue, err := sess.Config.Credentials.Get(); err == nil {
|
|
|
|
credentialsProviderName = credentialsValue.ProviderName
|
|
|
|
}
|
|
|
|
|
|
|
|
accountID, partition, err := GetAccountIDAndPartition(iamClient, stsClient, credentialsProviderName)
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
return sess, accountID, partition, nil
|
|
|
|
}
|
|
|
|
|
2019-08-07 22:33:57 +02:00
|
|
|
return nil, "", "", fmt.Errorf(
|
2019-02-18 08:15:26 +01:00
|
|
|
"AWS account ID not previously found and failed retrieving via all available methods. "+
|
|
|
|
"See https://www.terraform.io/docs/providers/aws/index.html#skip_requesting_account_id for workaround and implications. "+
|
2019-08-07 22:33:57 +02:00
|
|
|
"Errors: %s", err)
|
2019-02-18 08:15:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var partition string
|
|
|
|
if p, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), c.Region); ok {
|
|
|
|
partition = p.ID()
|
|
|
|
}
|
|
|
|
|
|
|
|
return sess, "", partition, nil
|
|
|
|
}
|