Skip to content

Commit 9917399

Browse files
authored
support static stability in instance metadata credential provider (#4049)
* support static stability in EC2 credential provider * update test files eslintrc version * add changelog * update the error message
1 parent 6d7d96c commit 9917399

File tree

6 files changed

+267
-68
lines changed

6 files changed

+267
-68
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "feature",
3+
"category": "EC2MetadataCredentials",
4+
"description": "Allow EC2MetadataCredentials to extend the existing expiration when EC2 Metadata Service returns expired credentials or failure response."
5+
}

lib/config-base.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export interface HTTPOptions {
8383
export interface Logger {
8484
write?: (chunk: any, encoding?: string, callback?: () => void) => void
8585
log?: (...messages: any[]) => void;
86+
warn?: (...message: any[]) => void;
8687
}
8788
export interface ParamValidation {
8889
/**
+28-18
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
11
import {Credentials} from '../credentials';
2+
import {Logger} from '../config-base';
3+
24
export class EC2MetadataCredentials extends Credentials {
3-
/**
4-
* Creates credentials from the metadata service on an EC2 instance.
5-
* @param {object} options - Override the default (1s) timeout period.
6-
*/
7-
constructor(options?: EC2MetadataCredentialsOptions);
8-
}
9-
interface EC2MetadataCredentialsOptions {
10-
httpOptions?: {
11-
/**
12-
* Timeout in milliseconds.
13-
*/
14-
timeout?: number
15-
/**
16-
* Connection timeout in milliseconds.
17-
*/
18-
connectTimeout?: number
19-
}
20-
maxRetries?: number
5+
/**
6+
* Creates credentials from the metadata service on an EC2 instance.
7+
* @param {object} options - Override the default (1s) timeout period.
8+
*/
9+
constructor(options?: EC2MetadataCredentialsOptions);
10+
11+
/**
12+
* The original expiration of the current credential. In case of AWS
13+
* outage, the EC2 metadata will extend expiration of the existing
14+
* credential.
15+
*/
16+
originalExpiration?: Date;
17+
}
18+
interface EC2MetadataCredentialsOptions {
19+
httpOptions?: {
20+
/**
21+
* Timeout in milliseconds.
22+
*/
23+
timeout?: number
24+
/**
25+
* Connection timeout in milliseconds.
26+
*/
27+
connectTimeout?: number
2128
}
29+
maxRetries?: number
30+
logger?: Logger
31+
}

lib/credentials/ec2_metadata_credentials.js

+75-18
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,27 @@ require('../metadata_service');
1818
* AWS.config.credentials = new AWS.EC2MetadataCredentials({
1919
* httpOptions: { timeout: 5000 }, // 5 second timeout
2020
* maxRetries: 10, // retry 10 times
21-
* retryDelayOptions: { base: 200 } // see AWS.Config for information
21+
* retryDelayOptions: { base: 200 }, // see AWS.Config for information
22+
* logger: console // see AWS.Config for information
2223
* });
24+
* ```
2325
*
2426
* If your requests are timing out in connecting to the metadata service, such
2527
* as when testing on a development machine, you can use the connectTimeout
2628
* option, specified in milliseconds, which also defaults to 1 second.
27-
* ```
29+
*
30+
* If the requests failed or returns expired credentials, it will
31+
* extend the expiration of current credential, with a warning message. For more
32+
* information, please go to:
33+
* https://docs.aws.amazon.com/sdkref/latest/guide/feature-static-credentials.html
34+
*
35+
* @!attribute originalExpiration
36+
* @return [Date] The optional original expiration of the current credential.
37+
* In case of AWS outage, the EC2 metadata will extend expiration of the
38+
* existing credential.
2839
*
2940
* @see AWS.Config.retryDelayOptions
41+
* @see AWS.Config.logger
3042
*
3143
* @!macro nobrowser
3244
*/
@@ -44,7 +56,7 @@ AWS.EC2MetadataCredentials = AWS.util.inherit(AWS.Credentials, {
4456
options.httpOptions);
4557

4658
this.metadataService = new AWS.MetadataService(options);
47-
this.metadata = {};
59+
this.logger = options.logger || AWS.config && AWS.config.logger;
4860
},
4961

5062
/**
@@ -62,6 +74,13 @@ AWS.EC2MetadataCredentials = AWS.util.inherit(AWS.Credentials, {
6274
*/
6375
defaultMaxRetries: 3,
6476

77+
/**
78+
* The original expiration of the current credential. In case of AWS
79+
* outage, the EC2 metadata will extend expiration of the existing
80+
* credential.
81+
*/
82+
originalExpiration: undefined,
83+
6584
/**
6685
* Loads the credentials from the instance metadata service
6786
*
@@ -84,24 +103,62 @@ AWS.EC2MetadataCredentials = AWS.util.inherit(AWS.Credentials, {
84103
load: function load(callback) {
85104
var self = this;
86105
self.metadataService.loadCredentials(function(err, creds) {
87-
if (!err) {
88-
var currentTime = AWS.util.date.getDate();
89-
var expireTime = new Date(creds.Expiration);
90-
if (expireTime < currentTime) {
91-
err = AWS.util.error(
92-
new Error('EC2 Instance Metadata Serivce provided expired credentials'),
93-
{ code: 'EC2MetadataCredentialsProviderFailure' }
94-
);
106+
if (err) {
107+
if (self.hasLoadedCredentials()) {
108+
self.extendExpirationIfExpired();
109+
callback();
95110
} else {
96-
self.expired = false;
97-
self.metadata = creds;
98-
self.accessKeyId = creds.AccessKeyId;
99-
self.secretAccessKey = creds.SecretAccessKey;
100-
self.sessionToken = creds.Token;
101-
self.expireTime = expireTime;
111+
callback(err);
102112
}
113+
} else {
114+
self.setCredentials(creds);
115+
self.extendExpirationIfExpired();
116+
callback();
103117
}
104-
callback(err);
105118
});
119+
},
120+
121+
/**
122+
* Whether this credential has been loaded.
123+
* @api private
124+
*/
125+
hasLoadedCredentials: function hasLoadedCredentials() {
126+
return this.AccessKeyId && this.secretAccessKey;
127+
},
128+
129+
/**
130+
* if expired, extend the expiration by 15 minutes base plus a jitter of 5
131+
* minutes range.
132+
* @api private
133+
*/
134+
extendExpirationIfExpired: function extendExpirationIfExpired() {
135+
if (this.needsRefresh()) {
136+
this.originalExpiration = this.originalExpiration || this.expireTime;
137+
this.expired = false;
138+
var nextTimeout = 15 * 60 + Math.floor(Math.random() * 5 * 60);
139+
var currentTime = AWS.util.date.getDate().getTime();
140+
this.expireTime = new Date(currentTime + nextTimeout * 1000);
141+
// TODO: add doc link;
142+
this.logger.warn('Attempting credential expiration extension due to a '
143+
+ 'credential service availability issue. A refresh of these '
144+
+ 'credentials will be attempted again at ' + this.expireTime
145+
+ '\nFor more information, please visit: https://docs.aws.amazon.com/sdkref/latest/guide/feature-static-credentials.html');
146+
}
147+
},
148+
149+
/**
150+
* Update the credential with new credential responded from EC2 metadata
151+
* service.
152+
* @api private
153+
*/
154+
setCredentials: function setCredentials(creds) {
155+
var currentTime = AWS.util.date.getDate().getTime();
156+
var expireTime = new Date(creds.Expiration);
157+
this.expired = currentTime >= expireTime ? true : false;
158+
this.metadata = creds;
159+
this.accessKeyId = creds.AccessKeyId;
160+
this.secretAccessKey = creds.SecretAccessKey;
161+
this.sessionToken = creds.Token;
162+
this.expireTime = expireTime;
106163
}
107164
});

test/.eslintrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"extends": "../.eslintrc",
33
"parserOptions": {
4-
"ecmaVersion": 8
4+
"ecmaVersion": 9
55
}
66
}

0 commit comments

Comments
 (0)