UNPKG

7.05 kBJavaScriptView Raw
1var AWS = require('./core');
2require('./http');
3var inherit = AWS.util.inherit;
4
5/**
6 * Represents a metadata service available on EC2 instances. Using the
7 * {request} method, you can receieve metadata about any available resource
8 * on the metadata service.
9 *
10 * You can disable the use of the IMDS by setting the AWS_EC2_METADATA_DISABLED
11 * environment variable to a truthy value.
12 *
13 * @!attribute [r] httpOptions
14 * @return [map] a map of options to pass to the underlying HTTP request:
15 *
16 * * **timeout** (Number) — a timeout value in milliseconds to wait
17 * before aborting the connection. Set to 0 for no timeout.
18 *
19 * @!macro nobrowser
20 */
21AWS.MetadataService = inherit({
22 /**
23 * @return [String] the hostname of the instance metadata service
24 */
25 host: '169.254.169.254',
26
27 /**
28 * @!ignore
29 */
30
31 /**
32 * Default HTTP options. By default, the metadata service is set to not
33 * timeout on long requests. This means that on non-EC2 machines, this
34 * request will never return. If you are calling this operation from an
35 * environment that may not always run on EC2, set a `timeout` value so
36 * the SDK will abort the request after a given number of milliseconds.
37 */
38 httpOptions: { timeout: 0 },
39
40 /**
41 * when enabled, metadata service will not fetch token
42 */
43 disableFetchToken: false,
44
45 /**
46 * Creates a new MetadataService object with a given set of options.
47 *
48 * @option options host [String] the hostname of the instance metadata
49 * service
50 * @option options httpOptions [map] a map of options to pass to the
51 * underlying HTTP request:
52 *
53 * * **timeout** (Number) — a timeout value in milliseconds to wait
54 * before aborting the connection. Set to 0 for no timeout.
55 * @option options maxRetries [Integer] the maximum number of retries to
56 * perform for timeout errors
57 * @option options retryDelayOptions [map] A set of options to configure the
58 * retry delay on retryable errors. See AWS.Config for details.
59 */
60 constructor: function MetadataService(options) {
61 AWS.util.update(this, options);
62 },
63
64 /**
65 * Sends a request to the instance metadata service for a given resource.
66 *
67 * @param path [String] the path of the resource to get
68 *
69 * @param options [map] an optional map used to make request
70 *
71 * * **method** (String) — HTTP request method
72 *
73 * * **headers** (map<String,String>) &mdash; a map of response header keys and their respective values
74 *
75 * @callback callback function(err, data)
76 * Called when a response is available from the service.
77 * @param err [Error, null] if an error occurred, this value will be set
78 * @param data [String, null] if the request was successful, the body of
79 * the response
80 */
81 request: function request(path, options, callback) {
82 if (arguments.length === 2) {
83 callback = options;
84 options = {};
85 }
86
87 if (process.env[AWS.util.imdsDisabledEnv]) {
88 callback(new Error('EC2 Instance Metadata Service access disabled'));
89 return;
90 }
91
92 path = path || '/';
93 var httpRequest = new AWS.HttpRequest('http://' + this.host + path);
94 httpRequest.method = options.method || 'GET';
95 if (options.headers) {
96 httpRequest.headers = options.headers;
97 }
98 AWS.util.handleRequestWithRetries(httpRequest, this, callback);
99 },
100
101 /**
102 * @api private
103 */
104 loadCredentialsCallbacks: [],
105
106 /**
107 * Fetches metadata token used for getting credentials
108 *
109 * @api private
110 * @callback callback function(err, token)
111 * Called when token is loaded from the resource
112 */
113 fetchMetadataToken: function fetchMetadataToken(callback) {
114 var self = this;
115 var tokenFetchPath = '/latest/api/token';
116 self.request(
117 tokenFetchPath,
118 {
119 'method': 'PUT',
120 'headers': {
121 'x-aws-ec2-metadata-token-ttl-seconds': '21600'
122 }
123 },
124 callback
125 );
126 },
127
128 /**
129 * Fetches credentials
130 *
131 * @api private
132 * @callback cb function(err, creds)
133 * Called when credentials are loaded from the resource
134 */
135 fetchCredentials: function fetchCredentials(options, cb) {
136 var self = this;
137 var basePath = '/latest/meta-data/iam/security-credentials/';
138
139 self.request(basePath, options, function (err, roleName) {
140 if (err) {
141 self.disableFetchToken = !(err.statusCode === 401);
142 cb(AWS.util.error(
143 err,
144 {
145 message: 'EC2 Metadata roleName request returned error'
146 }
147 ));
148 return;
149 }
150 roleName = roleName.split('\n')[0]; // grab first (and only) role
151 self.request(basePath + roleName, options, function (credErr, credData) {
152 if (credErr) {
153 self.disableFetchToken = !(credErr.statusCode === 401);
154 cb(AWS.util.error(
155 credErr,
156 {
157 message: 'EC2 Metadata creds request returned error'
158 }
159 ));
160 return;
161 }
162 try {
163 var credentials = JSON.parse(credData);
164 cb(null, credentials);
165 } catch (parseError) {
166 cb(parseError);
167 }
168 });
169 });
170 },
171
172 /**
173 * Loads a set of credentials stored in the instance metadata service
174 *
175 * @api private
176 * @callback callback function(err, credentials)
177 * Called when credentials are loaded from the resource
178 * @param err [Error] if an error occurred, this value will be set
179 * @param credentials [Object] the raw JSON object containing all
180 * metadata from the credentials resource
181 */
182 loadCredentials: function loadCredentials(callback) {
183 var self = this;
184 self.loadCredentialsCallbacks.push(callback);
185 if (self.loadCredentialsCallbacks.length > 1) { return; }
186
187 function callbacks(err, creds) {
188 var cb;
189 while ((cb = self.loadCredentialsCallbacks.shift()) !== undefined) {
190 cb(err, creds);
191 }
192 }
193
194 if (self.disableFetchToken) {
195 self.fetchCredentials({}, callbacks);
196 } else {
197 self.fetchMetadataToken(function(tokenError, token) {
198 if (tokenError) {
199 if (tokenError.code === 'TimeoutError') {
200 self.disableFetchToken = true;
201 } else if (tokenError.retryable === true) {
202 callbacks(AWS.util.error(
203 tokenError,
204 {
205 message: 'EC2 Metadata token request returned error'
206 }
207 ));
208 return;
209 } else if (tokenError.statusCode === 400) {
210 callbacks(AWS.util.error(
211 tokenError,
212 {
213 message: 'EC2 Metadata token request returned 400'
214 }
215 ));
216 return;
217 }
218 }
219 var options = {};
220 if (token) {
221 options.headers = {
222 'x-aws-ec2-metadata-token': token
223 };
224 }
225 self.fetchCredentials(options, callbacks);
226 });
227
228 }
229 }
230});
231
232/**
233 * @api private
234 */
235module.exports = AWS.MetadataService;