123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- var AWS = require('./core');
- require('./http');
- var inherit = AWS.util.inherit;
- var getMetadataServiceEndpoint = require('./metadata_service/get_metadata_service_endpoint');
- var URL = require('url').URL;
- /**
- * Represents a metadata service available on EC2 instances. Using the
- * {request} method, you can receieve metadata about any available resource
- * on the metadata service.
- *
- * You can disable the use of the IMDS by setting the AWS_EC2_METADATA_DISABLED
- * environment variable to a truthy value.
- *
- * @!attribute [r] httpOptions
- * @return [map] a map of options to pass to the underlying HTTP request:
- *
- * * **timeout** (Number) — a timeout value in milliseconds to wait
- * before aborting the connection. Set to 0 for no timeout.
- *
- * @!macro nobrowser
- */
- AWS.MetadataService = inherit({
- /**
- * @return [String] the endpoint of the instance metadata service
- */
- endpoint: getMetadataServiceEndpoint(),
- /**
- * @!ignore
- */
- /**
- * Default HTTP options. By default, the metadata service is set to not
- * timeout on long requests. This means that on non-EC2 machines, this
- * request will never return. If you are calling this operation from an
- * environment that may not always run on EC2, set a `timeout` value so
- * the SDK will abort the request after a given number of milliseconds.
- */
- httpOptions: { timeout: 0 },
- /**
- * when enabled, metadata service will not fetch token
- */
- disableFetchToken: false,
- /**
- * Creates a new MetadataService object with a given set of options.
- *
- * @option options host [String] the hostname of the instance metadata
- * service
- * @option options httpOptions [map] a map of options to pass to the
- * underlying HTTP request:
- *
- * * **timeout** (Number) — a timeout value in milliseconds to wait
- * before aborting the connection. Set to 0 for no timeout.
- * @option options maxRetries [Integer] the maximum number of retries to
- * perform for timeout errors
- * @option options retryDelayOptions [map] A set of options to configure the
- * retry delay on retryable errors. See AWS.Config for details.
- * @option options ec2MetadataV1Disabled [boolean] Whether to block IMDS v1 fallback.
- * @option options profile [string] A profile to check for IMDSv1 fallback settings.
- * @option options filename [string] Optional filename for the config file.
- */
- constructor: function MetadataService(options) {
- if (options && options.host) {
- options.endpoint = 'http://' + options.host;
- delete options.host;
- }
- this.profile = options && options.profile || process.env.AWS_PROFILE || AWS.util.defaultProfile;
- this.ec2MetadataV1Disabled = !!(options && options.ec2MetadataV1Disabled);
- this.filename = options && options.filename;
- AWS.util.update(this, options);
- },
- /**
- * Sends a request to the instance metadata service for a given resource.
- *
- * @param path [String] the path of the resource to get
- *
- * @param options [map] an optional map used to make request
- *
- * * **method** (String) — HTTP request method
- *
- * * **headers** (map<String,String>) — a map of response header keys and their respective values
- *
- * @callback callback function(err, data)
- * Called when a response is available from the service.
- * @param err [Error, null] if an error occurred, this value will be set
- * @param data [String, null] if the request was successful, the body of
- * the response
- */
- request: function request(path, options, callback) {
- if (arguments.length === 2) {
- callback = options;
- options = {};
- }
- if (process.env[AWS.util.imdsDisabledEnv]) {
- callback(new Error('EC2 Instance Metadata Service access disabled'));
- return;
- }
- path = path || '/';
- // Verify that host is a valid URL
- if (URL) { new URL(this.endpoint); }
- var httpRequest = new AWS.HttpRequest(this.endpoint + path);
- httpRequest.method = options.method || 'GET';
- if (options.headers) {
- httpRequest.headers = options.headers;
- }
- AWS.util.handleRequestWithRetries(httpRequest, this, callback);
- },
- /**
- * @api private
- */
- loadCredentialsCallbacks: [],
- /**
- * Fetches metadata token used for getting credentials
- *
- * @api private
- * @callback callback function(err, token)
- * Called when token is loaded from the resource
- */
- fetchMetadataToken: function fetchMetadataToken(callback) {
- var self = this;
- var tokenFetchPath = '/latest/api/token';
- self.request(
- tokenFetchPath,
- {
- 'method': 'PUT',
- 'headers': {
- 'x-aws-ec2-metadata-token-ttl-seconds': '21600'
- }
- },
- callback
- );
- },
- /**
- * Fetches credentials
- *
- * @api private
- * @callback cb function(err, creds)
- * Called when credentials are loaded from the resource
- */
- fetchCredentials: function fetchCredentials(options, cb) {
- var self = this;
- var basePath = '/latest/meta-data/iam/security-credentials/';
- var isImdsV1Fallback = self.disableFetchToken
- || !(options && options.headers && options.headers['x-aws-ec2-metadata-token']);
- if (isImdsV1Fallback && !(process.env.AWS_EC2_METADATA_DISABLED)) {
- try {
- var profiles = AWS.util.getProfilesFromSharedConfig(AWS.util.iniLoader, this.filename);
- var profileSettings = profiles[this.profile] || {};
- } catch (e) {
- profileSettings = {};
- }
- if (profileSettings.ec2_metadata_v1_disabled && profileSettings.ec2_metadata_v1_disabled !== 'false') {
- return cb(AWS.util.error(
- new Error('AWS EC2 Metadata v1 fallback has been blocked by AWS config file profile.')
- ));
- }
- if (self.ec2MetadataV1Disabled) {
- return cb(AWS.util.error(
- new Error('AWS EC2 Metadata v1 fallback has been blocked by AWS.MetadataService::options.ec2MetadataV1Disabled=true.')
- ));
- }
- if (process.env.AWS_EC2_METADATA_V1_DISABLED && process.env.AWS_EC2_METADATA_V1_DISABLED !== 'false') {
- return cb(AWS.util.error(
- new Error('AWS EC2 Metadata v1 fallback has been blocked by process.env.AWS_EC2_METADATA_V1_DISABLED.')
- ));
- }
- }
- self.request(basePath, options, function (err, roleName) {
- if (err) {
- self.disableFetchToken = !(err.statusCode === 401);
- cb(AWS.util.error(
- err,
- {
- message: 'EC2 Metadata roleName request returned error'
- }
- ));
- return;
- }
- roleName = roleName.split('\n')[0]; // grab first (and only) role
- self.request(basePath + roleName, options, function (credErr, credData) {
- if (credErr) {
- self.disableFetchToken = !(credErr.statusCode === 401);
- cb(AWS.util.error(
- credErr,
- {
- message: 'EC2 Metadata creds request returned error'
- }
- ));
- return;
- }
- try {
- var credentials = JSON.parse(credData);
- cb(null, credentials);
- } catch (parseError) {
- cb(parseError);
- }
- });
- });
- },
- /**
- * Loads a set of credentials stored in the instance metadata service
- *
- * @api private
- * @callback callback function(err, credentials)
- * Called when credentials are loaded from the resource
- * @param err [Error] if an error occurred, this value will be set
- * @param credentials [Object] the raw JSON object containing all
- * metadata from the credentials resource
- */
- loadCredentials: function loadCredentials(callback) {
- var self = this;
- self.loadCredentialsCallbacks.push(callback);
- if (self.loadCredentialsCallbacks.length > 1) { return; }
- function callbacks(err, creds) {
- var cb;
- while ((cb = self.loadCredentialsCallbacks.shift()) !== undefined) {
- cb(err, creds);
- }
- }
- if (self.disableFetchToken) {
- self.fetchCredentials({}, callbacks);
- } else {
- self.fetchMetadataToken(function(tokenError, token) {
- if (tokenError) {
- if (tokenError.code === 'TimeoutError') {
- self.disableFetchToken = true;
- } else if (tokenError.retryable === true) {
- callbacks(AWS.util.error(
- tokenError,
- {
- message: 'EC2 Metadata token request returned error'
- }
- ));
- return;
- } else if (tokenError.statusCode === 400) {
- callbacks(AWS.util.error(
- tokenError,
- {
- message: 'EC2 Metadata token request returned 400'
- }
- ));
- return;
- }
- }
- var options = {};
- if (token) {
- options.headers = {
- 'x-aws-ec2-metadata-token': token
- };
- }
- self.fetchCredentials(options, callbacks);
- });
- }
- }
- });
- /**
- * @api private
- */
- module.exports = AWS.MetadataService;
|