]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | // Package ec2metadata provides the client for making API calls to the |
2 | // EC2 Metadata service. | |
15c0b25d AP |
3 | // |
4 | // This package's client can be disabled completely by setting the environment | |
5 | // variable "AWS_EC2_METADATA_DISABLED=true". This environment variable set to | |
6 | // true instructs the SDK to disable the EC2 Metadata client. The client cannot | |
107c1cdb | 7 | // be used while the environment variable is set to true, (case insensitive). |
bae9f6d2 JC |
8 | package ec2metadata |
9 | ||
10 | import ( | |
11 | "bytes" | |
12 | "errors" | |
13 | "io" | |
14 | "net/http" | |
15c0b25d AP |
15 | "os" |
16 | "strings" | |
bae9f6d2 JC |
17 | "time" |
18 | ||
19 | "github.com/aws/aws-sdk-go/aws" | |
20 | "github.com/aws/aws-sdk-go/aws/awserr" | |
21 | "github.com/aws/aws-sdk-go/aws/client" | |
22 | "github.com/aws/aws-sdk-go/aws/client/metadata" | |
15c0b25d | 23 | "github.com/aws/aws-sdk-go/aws/corehandlers" |
bae9f6d2 JC |
24 | "github.com/aws/aws-sdk-go/aws/request" |
25 | ) | |
26 | ||
27 | // ServiceName is the name of the service. | |
28 | const ServiceName = "ec2metadata" | |
15c0b25d | 29 | const disableServiceEnvVar = "AWS_EC2_METADATA_DISABLED" |
bae9f6d2 JC |
30 | |
31 | // A EC2Metadata is an EC2 Metadata service Client. | |
32 | type EC2Metadata struct { | |
33 | *client.Client | |
34 | } | |
35 | ||
36 | // New creates a new instance of the EC2Metadata client with a session. | |
37 | // This client is safe to use across multiple goroutines. | |
38 | // | |
39 | // | |
40 | // Example: | |
41 | // // Create a EC2Metadata client from just a session. | |
42 | // svc := ec2metadata.New(mySession) | |
43 | // | |
44 | // // Create a EC2Metadata client with additional configuration | |
45 | // svc := ec2metadata.New(mySession, aws.NewConfig().WithLogLevel(aws.LogDebugHTTPBody)) | |
46 | func New(p client.ConfigProvider, cfgs ...*aws.Config) *EC2Metadata { | |
47 | c := p.ClientConfig(ServiceName, cfgs...) | |
48 | return NewClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion) | |
49 | } | |
50 | ||
51 | // NewClient returns a new EC2Metadata client. Should be used to create | |
52 | // a client when not using a session. Generally using just New with a session | |
53 | // is preferred. | |
54 | // | |
55 | // If an unmodified HTTP client is provided from the stdlib default, or no client | |
56 | // the EC2RoleProvider's EC2Metadata HTTP client's timeout will be shortened. | |
57 | // To disable this set Config.EC2MetadataDisableTimeoutOverride to false. Enabled by default. | |
58 | func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string, opts ...func(*client.Client)) *EC2Metadata { | |
59 | if !aws.BoolValue(cfg.EC2MetadataDisableTimeoutOverride) && httpClientZero(cfg.HTTPClient) { | |
60 | // If the http client is unmodified and this feature is not disabled | |
61 | // set custom timeouts for EC2Metadata requests. | |
62 | cfg.HTTPClient = &http.Client{ | |
63 | // use a shorter timeout than default because the metadata | |
64 | // service is local if it is running, and to fail faster | |
65 | // if not running on an ec2 instance. | |
66 | Timeout: 5 * time.Second, | |
67 | } | |
68 | } | |
69 | ||
70 | svc := &EC2Metadata{ | |
71 | Client: client.New( | |
72 | cfg, | |
73 | metadata.ClientInfo{ | |
74 | ServiceName: ServiceName, | |
107c1cdb | 75 | ServiceID: ServiceName, |
bae9f6d2 JC |
76 | Endpoint: endpoint, |
77 | APIVersion: "latest", | |
78 | }, | |
79 | handlers, | |
80 | ), | |
81 | } | |
82 | ||
83 | svc.Handlers.Unmarshal.PushBack(unmarshalHandler) | |
84 | svc.Handlers.UnmarshalError.PushBack(unmarshalError) | |
85 | svc.Handlers.Validate.Clear() | |
86 | svc.Handlers.Validate.PushBack(validateEndpointHandler) | |
87 | ||
15c0b25d AP |
88 | // Disable the EC2 Metadata service if the environment variable is set. |
89 | // This shortcirctes the service's functionality to always fail to send | |
90 | // requests. | |
91 | if strings.ToLower(os.Getenv(disableServiceEnvVar)) == "true" { | |
92 | svc.Handlers.Send.SwapNamed(request.NamedHandler{ | |
93 | Name: corehandlers.SendHandler.Name, | |
94 | Fn: func(r *request.Request) { | |
107c1cdb ND |
95 | r.HTTPResponse = &http.Response{ |
96 | Header: http.Header{}, | |
97 | } | |
15c0b25d AP |
98 | r.Error = awserr.New( |
99 | request.CanceledErrorCode, | |
100 | "EC2 IMDS access disabled via "+disableServiceEnvVar+" env var", | |
101 | nil) | |
102 | }, | |
103 | }) | |
104 | } | |
105 | ||
bae9f6d2 JC |
106 | // Add additional options to the service config |
107 | for _, option := range opts { | |
108 | option(svc.Client) | |
109 | } | |
110 | ||
111 | return svc | |
112 | } | |
113 | ||
114 | func httpClientZero(c *http.Client) bool { | |
115 | return c == nil || (c.Transport == nil && c.CheckRedirect == nil && c.Jar == nil && c.Timeout == 0) | |
116 | } | |
117 | ||
118 | type metadataOutput struct { | |
119 | Content string | |
120 | } | |
121 | ||
122 | func unmarshalHandler(r *request.Request) { | |
123 | defer r.HTTPResponse.Body.Close() | |
124 | b := &bytes.Buffer{} | |
125 | if _, err := io.Copy(b, r.HTTPResponse.Body); err != nil { | |
126 | r.Error = awserr.New("SerializationError", "unable to unmarshal EC2 metadata respose", err) | |
127 | return | |
128 | } | |
129 | ||
130 | if data, ok := r.Data.(*metadataOutput); ok { | |
131 | data.Content = b.String() | |
132 | } | |
133 | } | |
134 | ||
135 | func unmarshalError(r *request.Request) { | |
136 | defer r.HTTPResponse.Body.Close() | |
137 | b := &bytes.Buffer{} | |
138 | if _, err := io.Copy(b, r.HTTPResponse.Body); err != nil { | |
139 | r.Error = awserr.New("SerializationError", "unable to unmarshal EC2 metadata error respose", err) | |
140 | return | |
141 | } | |
142 | ||
143 | // Response body format is not consistent between metadata endpoints. | |
144 | // Grab the error message as a string and include that as the source error | |
145 | r.Error = awserr.New("EC2MetadataError", "failed to make EC2Metadata request", errors.New(b.String())) | |
146 | } | |
147 | ||
148 | func validateEndpointHandler(r *request.Request) { | |
149 | if r.ClientInfo.Endpoint == "" { | |
150 | r.Error = aws.ErrMissingEndpoint | |
151 | } | |
152 | } |