Skip to content

Commit

Permalink
support static stability in instance metadata credential provider (#4049
Browse files Browse the repository at this point in the history
)

* support static stability in EC2 credential provider

* update test files eslintrc version

* add changelog

* update the error message
  • Loading branch information
AllanZhengYP authored Mar 9, 2022
1 parent 6d7d96c commit 9917399
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "feature",
"category": "EC2MetadataCredentials",
"description": "Allow EC2MetadataCredentials to extend the existing expiration when EC2 Metadata Service returns expired credentials or failure response."
}
1 change: 1 addition & 0 deletions lib/config-base.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export interface HTTPOptions {
export interface Logger {
write?: (chunk: any, encoding?: string, callback?: () => void) => void
log?: (...messages: any[]) => void;
warn?: (...message: any[]) => void;
}
export interface ParamValidation {
/**
Expand Down
46 changes: 28 additions & 18 deletions lib/credentials/ec2_metadata_credentials.d.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import {Credentials} from '../credentials';
import {Logger} from '../config-base';

export class EC2MetadataCredentials extends Credentials {
/**
* Creates credentials from the metadata service on an EC2 instance.
* @param {object} options - Override the default (1s) timeout period.
*/
constructor(options?: EC2MetadataCredentialsOptions);
}
interface EC2MetadataCredentialsOptions {
httpOptions?: {
/**
* Timeout in milliseconds.
*/
timeout?: number
/**
* Connection timeout in milliseconds.
*/
connectTimeout?: number
}
maxRetries?: number
/**
* Creates credentials from the metadata service on an EC2 instance.
* @param {object} options - Override the default (1s) timeout period.
*/
constructor(options?: EC2MetadataCredentialsOptions);

/**
* The original expiration of the current credential. In case of AWS
* outage, the EC2 metadata will extend expiration of the existing
* credential.
*/
originalExpiration?: Date;
}
interface EC2MetadataCredentialsOptions {
httpOptions?: {
/**
* Timeout in milliseconds.
*/
timeout?: number
/**
* Connection timeout in milliseconds.
*/
connectTimeout?: number
}
maxRetries?: number
logger?: Logger
}
93 changes: 75 additions & 18 deletions lib/credentials/ec2_metadata_credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,27 @@ require('../metadata_service');
* AWS.config.credentials = new AWS.EC2MetadataCredentials({
* httpOptions: { timeout: 5000 }, // 5 second timeout
* maxRetries: 10, // retry 10 times
* retryDelayOptions: { base: 200 } // see AWS.Config for information
* retryDelayOptions: { base: 200 }, // see AWS.Config for information
* logger: console // see AWS.Config for information
* });
* ```
*
* If your requests are timing out in connecting to the metadata service, such
* as when testing on a development machine, you can use the connectTimeout
* option, specified in milliseconds, which also defaults to 1 second.
* ```
*
* If the requests failed or returns expired credentials, it will
* extend the expiration of current credential, with a warning message. For more
* information, please go to:
* https://docs.aws.amazon.com/sdkref/latest/guide/feature-static-credentials.html
*
* @!attribute originalExpiration
* @return [Date] The optional original expiration of the current credential.
* In case of AWS outage, the EC2 metadata will extend expiration of the
* existing credential.
*
* @see AWS.Config.retryDelayOptions
* @see AWS.Config.logger
*
* @!macro nobrowser
*/
Expand All @@ -44,7 +56,7 @@ AWS.EC2MetadataCredentials = AWS.util.inherit(AWS.Credentials, {
options.httpOptions);

this.metadataService = new AWS.MetadataService(options);
this.metadata = {};
this.logger = options.logger || AWS.config && AWS.config.logger;
},

/**
Expand All @@ -62,6 +74,13 @@ AWS.EC2MetadataCredentials = AWS.util.inherit(AWS.Credentials, {
*/
defaultMaxRetries: 3,

/**
* The original expiration of the current credential. In case of AWS
* outage, the EC2 metadata will extend expiration of the existing
* credential.
*/
originalExpiration: undefined,

/**
* Loads the credentials from the instance metadata service
*
Expand All @@ -84,24 +103,62 @@ AWS.EC2MetadataCredentials = AWS.util.inherit(AWS.Credentials, {
load: function load(callback) {
var self = this;
self.metadataService.loadCredentials(function(err, creds) {
if (!err) {
var currentTime = AWS.util.date.getDate();
var expireTime = new Date(creds.Expiration);
if (expireTime < currentTime) {
err = AWS.util.error(
new Error('EC2 Instance Metadata Serivce provided expired credentials'),
{ code: 'EC2MetadataCredentialsProviderFailure' }
);
if (err) {
if (self.hasLoadedCredentials()) {
self.extendExpirationIfExpired();
callback();
} else {
self.expired = false;
self.metadata = creds;
self.accessKeyId = creds.AccessKeyId;
self.secretAccessKey = creds.SecretAccessKey;
self.sessionToken = creds.Token;
self.expireTime = expireTime;
callback(err);
}
} else {
self.setCredentials(creds);
self.extendExpirationIfExpired();
callback();
}
callback(err);
});
},

/**
* Whether this credential has been loaded.
* @api private
*/
hasLoadedCredentials: function hasLoadedCredentials() {
return this.AccessKeyId && this.secretAccessKey;
},

/**
* if expired, extend the expiration by 15 minutes base plus a jitter of 5
* minutes range.
* @api private
*/
extendExpirationIfExpired: function extendExpirationIfExpired() {
if (this.needsRefresh()) {
this.originalExpiration = this.originalExpiration || this.expireTime;
this.expired = false;
var nextTimeout = 15 * 60 + Math.floor(Math.random() * 5 * 60);
var currentTime = AWS.util.date.getDate().getTime();
this.expireTime = new Date(currentTime + nextTimeout * 1000);
// TODO: add doc link;
this.logger.warn('Attempting credential expiration extension due to a '
+ 'credential service availability issue. A refresh of these '
+ 'credentials will be attempted again at ' + this.expireTime
+ '\nFor more information, please visit: https://docs.aws.amazon.com/sdkref/latest/guide/feature-static-credentials.html');
}
},

/**
* Update the credential with new credential responded from EC2 metadata
* service.
* @api private
*/
setCredentials: function setCredentials(creds) {
var currentTime = AWS.util.date.getDate().getTime();
var expireTime = new Date(creds.Expiration);
this.expired = currentTime >= expireTime ? true : false;
this.metadata = creds;
this.accessKeyId = creds.AccessKeyId;
this.secretAccessKey = creds.SecretAccessKey;
this.sessionToken = creds.Token;
this.expireTime = expireTime;
}
});
2 changes: 1 addition & 1 deletion test/.eslintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"extends": "../.eslintrc",
"parserOptions": {
"ecmaVersion": 8
"ecmaVersion": 9
}
}
Loading

0 comments on commit 9917399

Please sign in to comment.