From ea1ef93b55990bb492df4fe8ee28a211f1eab9e9 Mon Sep 17 00:00:00 2001 From: George Fu Date: Thu, 2 Nov 2023 14:57:49 -0400 Subject: [PATCH] feat: imdsv1 fallback toggle (#4517) * feat: imdsv1 fallback toggle * undo formatting --- .../next-release/feature-IMDS-3a28eff9.json | 5 +++ lib/credentials/ec2_metadata_credentials.d.ts | 14 ++++++++ lib/credentials/ec2_metadata_credentials.js | 1 + lib/metadata_service.d.ts | 17 ++++++++- lib/metadata_service.js | 36 +++++++++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 .changes/next-release/feature-IMDS-3a28eff9.json diff --git a/.changes/next-release/feature-IMDS-3a28eff9.json b/.changes/next-release/feature-IMDS-3a28eff9.json new file mode 100644 index 0000000000..919022ac6b --- /dev/null +++ b/.changes/next-release/feature-IMDS-3a28eff9.json @@ -0,0 +1,5 @@ +{ + "type": "feature", + "category": "IMDS", + "description": "IMDSv1 fallback toggle" +} \ No newline at end of file diff --git a/lib/credentials/ec2_metadata_credentials.d.ts b/lib/credentials/ec2_metadata_credentials.d.ts index d94ebb6842..7896bfecdd 100644 --- a/lib/credentials/ec2_metadata_credentials.d.ts +++ b/lib/credentials/ec2_metadata_credentials.d.ts @@ -28,4 +28,18 @@ interface EC2MetadataCredentialsOptions { } maxRetries?: number logger?: Logger + /** + * Prevent IMDSv1 fallback. + */ + ec2MetadataV1Disabled?: boolean + + /** + * profile name to check for IMDSv1 settings. + */ + profile?: string + + /** + * optional file from which to to get config. + */ + filename?: string } diff --git a/lib/credentials/ec2_metadata_credentials.js b/lib/credentials/ec2_metadata_credentials.js index 2aa2f5e28e..1b99c7f018 100644 --- a/lib/credentials/ec2_metadata_credentials.js +++ b/lib/credentials/ec2_metadata_credentials.js @@ -20,6 +20,7 @@ require('../metadata_service'); * maxRetries: 10, // retry 10 times * retryDelayOptions: { base: 200 }, // see AWS.Config for information * logger: console // see AWS.Config for information + * ec2MetadataV1Disabled: false // whether to block IMDS v1 fallback. * }); * ``` * diff --git a/lib/metadata_service.d.ts b/lib/metadata_service.d.ts index 8527167f22..329fdc2d65 100644 --- a/lib/metadata_service.d.ts +++ b/lib/metadata_service.d.ts @@ -54,4 +54,19 @@ interface MetadataServiceOptions { * A set of options to configure the retry delay on retryable errors. See AWS.Config for details. */ retryDelayOptions?: any -} \ No newline at end of file + + /** + * Prevent IMDSv1 fallback. + */ + ec2MetadataV1Disabled?: boolean + + /** + * profile name to check for IMDSv1 settings. + */ + profile?: string + + /** + * optional file from which to to get config. + */ + filename?: string +} diff --git a/lib/metadata_service.js b/lib/metadata_service.js index 321b4ca510..24dad3002b 100644 --- a/lib/metadata_service.js +++ b/lib/metadata_service.js @@ -58,12 +58,18 @@ AWS.MetadataService = inherit({ * 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); }, @@ -146,6 +152,36 @@ AWS.MetadataService = inherit({ 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);