1 package imds
2
3 import (
4 "context"
5 "fmt"
6 "net"
7 "net/http"
8 "os"
9 "strings"
10 "time"
11
12 "github.com/aws/aws-sdk-go-v2/aws"
13 "github.com/aws/aws-sdk-go-v2/aws/retry"
14 awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
15 internalconfig "github.com/aws/aws-sdk-go-v2/feature/ec2/imds/internal/config"
16 "github.com/aws/smithy-go"
17 "github.com/aws/smithy-go/logging"
18 "github.com/aws/smithy-go/middleware"
19 smithyhttp "github.com/aws/smithy-go/transport/http"
20 )
21
22
23 const ServiceID = "ec2imds"
24
25
26
27 type Client struct {
28 options Options
29 }
30
31
32
33 type ClientEnableState = internalconfig.ClientEnableState
34
35
36 const (
37 ClientDefaultEnableState ClientEnableState = internalconfig.ClientDefaultEnableState
38 ClientDisabled ClientEnableState = internalconfig.ClientDisabled
39 ClientEnabled ClientEnableState = internalconfig.ClientEnabled
40 )
41
42
43
44 type EndpointModeState = internalconfig.EndpointModeState
45
46
47 const (
48 EndpointModeStateUnset EndpointModeState = internalconfig.EndpointModeStateUnset
49 EndpointModeStateIPv4 EndpointModeState = internalconfig.EndpointModeStateIPv4
50 EndpointModeStateIPv6 EndpointModeState = internalconfig.EndpointModeStateIPv6
51 )
52
53 const (
54 disableClientEnvVar = "AWS_EC2_METADATA_DISABLED"
55
56
57 endpointEnvVar = "AWS_EC2_METADATA_SERVICE_ENDPOINT"
58
59 defaultIPv4Endpoint = "http://169.254.169.254"
60 defaultIPv6Endpoint = "http://[fd00:ec2::254]"
61 )
62
63
64
65
66 func New(options Options, optFns ...func(*Options)) *Client {
67 options = options.Copy()
68
69 for _, fn := range optFns {
70 fn(&options)
71 }
72
73 options.HTTPClient = resolveHTTPClient(options.HTTPClient)
74
75 if options.Retryer == nil {
76 options.Retryer = retry.NewStandard()
77 }
78 options.Retryer = retry.AddWithMaxBackoffDelay(options.Retryer, 1*time.Second)
79
80 if options.ClientEnableState == ClientDefaultEnableState {
81 if v := os.Getenv(disableClientEnvVar); strings.EqualFold(v, "true") {
82 options.ClientEnableState = ClientDisabled
83 }
84 }
85
86 if len(options.Endpoint) == 0 {
87 if v := os.Getenv(endpointEnvVar); len(v) != 0 {
88 options.Endpoint = v
89 }
90 }
91
92 client := &Client{
93 options: options,
94 }
95
96 if client.options.tokenProvider == nil && !client.options.disableAPIToken {
97 client.options.tokenProvider = newTokenProvider(client, defaultTokenTTL)
98 }
99
100 return client
101 }
102
103
104
105
106
107 func NewFromConfig(cfg aws.Config, optFns ...func(*Options)) *Client {
108 opts := Options{
109 APIOptions: append([]func(*middleware.Stack) error{}, cfg.APIOptions...),
110 HTTPClient: cfg.HTTPClient,
111 ClientLogMode: cfg.ClientLogMode,
112 Logger: cfg.Logger,
113 }
114
115 if cfg.Retryer != nil {
116 opts.Retryer = cfg.Retryer()
117 }
118
119 resolveClientEnableState(cfg, &opts)
120 resolveEndpointConfig(cfg, &opts)
121 resolveEndpointModeConfig(cfg, &opts)
122 resolveEnableFallback(cfg, &opts)
123
124 return New(opts, optFns...)
125 }
126
127
128 type Options struct {
129
130
131
132 APIOptions []func(*middleware.Stack) error
133
134
135
136
137
138
139
140
141
142
143 Endpoint string
144
145
146
147
148
149
150
151 EndpointMode EndpointModeState
152
153
154
155 HTTPClient HTTPClient
156
157
158
159 Retryer aws.Retryer
160
161
162
163
164
165
166
167
168
169
170 ClientEnableState ClientEnableState
171
172
173 ClientLogMode aws.ClientLogMode
174
175
176 Logger logging.Logger
177
178
179
180
181
182
183
184
185
186 EnableFallback aws.Ternary
187
188
189
190 DisableDefaultTimeout bool
191
192
193
194 tokenProvider *tokenProvider
195
196
197 disableAPIToken bool
198 }
199
200
201
202 type HTTPClient interface {
203 Do(*http.Request) (*http.Response, error)
204 }
205
206
207 func (o Options) Copy() Options {
208 to := o
209 to.APIOptions = append([]func(*middleware.Stack) error{}, o.APIOptions...)
210 return to
211 }
212
213
214
215
216 func WithAPIOptions(optFns ...func(*middleware.Stack) error) func(*Options) {
217 return func(o *Options) {
218 o.APIOptions = append(o.APIOptions, optFns...)
219 }
220 }
221
222 func (c *Client) invokeOperation(
223 ctx context.Context, opID string, params interface{}, optFns []func(*Options),
224 stackFns ...func(*middleware.Stack, Options) error,
225 ) (
226 result interface{}, metadata middleware.Metadata, err error,
227 ) {
228 stack := middleware.NewStack(opID, smithyhttp.NewStackRequest)
229 options := c.options.Copy()
230 for _, fn := range optFns {
231 fn(&options)
232 }
233
234 if options.ClientEnableState == ClientDisabled {
235 return nil, metadata, &smithy.OperationError{
236 ServiceID: ServiceID,
237 OperationName: opID,
238 Err: fmt.Errorf(
239 "access disabled to EC2 IMDS via client option, or %q environment variable",
240 disableClientEnvVar),
241 }
242 }
243
244 for _, fn := range stackFns {
245 if err := fn(stack, options); err != nil {
246 return nil, metadata, err
247 }
248 }
249
250 for _, fn := range options.APIOptions {
251 if err := fn(stack); err != nil {
252 return nil, metadata, err
253 }
254 }
255
256 handler := middleware.DecorateHandler(smithyhttp.NewClientHandler(options.HTTPClient), stack)
257 result, metadata, err = handler.Handle(ctx, params)
258 if err != nil {
259 return nil, metadata, &smithy.OperationError{
260 ServiceID: ServiceID,
261 OperationName: opID,
262 Err: err,
263 }
264 }
265
266 return result, metadata, err
267 }
268
269 const (
270
271 defaultDialerTimeout = 250 * time.Millisecond
272 defaultResponseHeaderTimeout = 500 * time.Millisecond
273 )
274
275 func resolveHTTPClient(client HTTPClient) HTTPClient {
276 if client == nil {
277 client = awshttp.NewBuildableClient()
278 }
279
280 if c, ok := client.(*awshttp.BuildableClient); ok {
281 client = c.
282 WithDialerOptions(func(d *net.Dialer) {
283
284
285
286
287 d.Timeout = defaultDialerTimeout
288 }).
289 WithTransportOptions(func(tr *http.Transport) {
290
291
292
293
294 tr.ResponseHeaderTimeout = defaultResponseHeaderTimeout
295 })
296 }
297
298 return client
299 }
300
301 func resolveClientEnableState(cfg aws.Config, options *Options) error {
302 if options.ClientEnableState != ClientDefaultEnableState {
303 return nil
304 }
305 value, found, err := internalconfig.ResolveClientEnableState(cfg.ConfigSources)
306 if err != nil || !found {
307 return err
308 }
309 options.ClientEnableState = value
310 return nil
311 }
312
313 func resolveEndpointModeConfig(cfg aws.Config, options *Options) error {
314 if options.EndpointMode != EndpointModeStateUnset {
315 return nil
316 }
317 value, found, err := internalconfig.ResolveEndpointModeConfig(cfg.ConfigSources)
318 if err != nil || !found {
319 return err
320 }
321 options.EndpointMode = value
322 return nil
323 }
324
325 func resolveEndpointConfig(cfg aws.Config, options *Options) error {
326 if len(options.Endpoint) != 0 {
327 return nil
328 }
329 value, found, err := internalconfig.ResolveEndpointConfig(cfg.ConfigSources)
330 if err != nil || !found {
331 return err
332 }
333 options.Endpoint = value
334 return nil
335 }
336
337 func resolveEnableFallback(cfg aws.Config, options *Options) {
338 if options.EnableFallback != aws.UnknownTernary {
339 return
340 }
341
342 disabled, ok := internalconfig.ResolveV1FallbackDisabled(cfg.ConfigSources)
343 if !ok {
344 return
345 }
346
347 if disabled {
348 options.EnableFallback = aws.FalseTernary
349 } else {
350 options.EnableFallback = aws.TrueTernary
351 }
352 }
353
View as plain text