-
Notifications
You must be signed in to change notification settings - Fork 36
/
session.go
181 lines (149 loc) · 5.69 KB
/
session.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package awsbase
import (
"crypto/tls"
"fmt"
"log"
"net/http"
"os"
"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/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/go-cleanhttp"
)
const (
// Maximum network retries.
// We depend on the AWS Go SDK DefaultRetryer exponential backoff.
// Ensure that if the AWS Config MaxRetries is set high (which it is by
// default), that we only retry for a few seconds with typically
// unrecoverable network errors, such as DNS lookup failures.
MaxNetworkRetryCount = 9
)
// 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{
EndpointResolver: c.EndpointResolver(),
HTTPClient: cleanhttp.DefaultClient(),
MaxRetries: aws.Int(0),
Region: aws.String(c.Region),
},
Profile: c.Profile,
SharedConfigState: session.SharedConfigEnable,
}
// get and validate credentials
creds, err := GetCredentials(c)
if err != nil {
return nil, err
}
// add the validated credentials to the session options
options.Config.Credentials = creds
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
}
// GetSession attempts to return valid AWS Go SDK session.
func GetSession(c *Config) (*session.Session, error) {
if c.SkipMetadataApiCheck {
os.Setenv("AWS_EC2_METADATA_DISABLED", "true")
}
options, err := GetSessionOptions(c)
if err != nil {
return nil, err
}
sess, err := session.NewSessionWithOptions(*options)
if err != nil {
if tfawserr.ErrCodeEquals(err, "NoCredentialProviders") {
return nil, c.NewNoValidCredentialSourcesError(err)
}
return nil, fmt.Errorf("Error creating AWS session: %w", 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) {
if r.RetryCount < MaxNetworkRetryCount {
return
}
// RequestError: send request failed
// caused by: Post https://FQDN/: dial tcp: lookup FQDN: no such host
if tfawserr.ErrMessageAndOrigErrContain(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 tfawserr.ErrMessageAndOrigErrContain(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 {
if _, _, err := GetAccountIDAndPartitionFromSTSGetCallerIdentity(sts.New(sess)); err != nil {
return nil, fmt.Errorf("error validating provider credentials: %w", err)
}
}
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)
stsClient := sts.New(sess)
if !c.SkipCredsValidation {
accountID, partition, err := GetAccountIDAndPartitionFromSTSGetCallerIdentity(stsClient)
if err != nil {
return nil, "", "", fmt.Errorf("error validating provider credentials: %w", 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
}
return nil, "", "", fmt.Errorf(
"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. "+
"Errors: %w", err)
}
var partition string
if p, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), c.Region); ok {
partition = p.ID()
}
return sess, "", partition, nil
}