forked from hashicorp/aws-sdk-go-base
-
Notifications
You must be signed in to change notification settings - Fork 0
/
session.go
202 lines (169 loc) · 6.92 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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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 (
// AppendUserAgentEnvVar is a conventionally used environment variable
// containing additional HTTP User-Agent information.
// If present and its value is non-empty, it is directly appended to the
// User-Agent header for HTTP requests.
AppendUserAgentEnvVar = "TF_APPEND_USER_AGENT"
// 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)})
}
// AWS SDK Go automatically adds a User-Agent product to HTTP requests,
// which contains helpful information about the SDK version and runtime.
// The configuration of additional User-Agent header products should take
// precedence over that product. Since the AWS SDK Go request package
// functions only append, we must PushFront on the build handlers instead
// of PushBack. To properly keep the order given by the configuration, we
// must reverse iterate through the products so the last item is PushFront
// first through the first item being PushFront last.
for i := len(c.UserAgentProducts) - 1; i >= 0; i-- {
product := c.UserAgentProducts[i]
sess.Handlers.Build.PushFront(request.MakeAddToUserAgentHandler(product.Name, product.Version, product.Extra...))
}
// Add custom input from ENV to the User-Agent request header
// Reference: https://github.com/terraform-providers/terraform-provider-aws/issues/9149
if v := os.Getenv(AppendUserAgentEnvVar); v != "" {
log.Printf("[DEBUG] Using additional User-Agent Info: %s", v)
sess.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler(v))
}
// 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
}