Skip to content

Commit 2f3826c

Browse files
authored
feat: add opt-in support for S3 us-east-1 regional endpoint (#2960)
* generalize resolveRegionalEndpointsFlag function; update STS customization
1 parent 151ca09 commit 2f3826c

File tree

8 files changed

+328
-98
lines changed

8 files changed

+328
-98
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "feature",
3+
"category": "Region",
4+
"description": "s3 client now support sending request to us-east-1 regional endpoint with `s3UsEast1RegionalEndpoint` client configuration set to `regional`"
5+
}

lib/config.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ var PromisesDependency;
8282
* @return [Boolean] whether to disable S3 body signing when using signature version `v4`.
8383
* Body signing can only be disabled when using https. Defaults to `true`.
8484
*
85+
* @!attribute s3UsEast1RegionalEndpoint
86+
* @return ['legacy'|'regional'] when region is set to 'us-east-1', whether to send s3
87+
* request to global endpoints or 'us-east-1' regional endpoints. This config is only
88+
* applicable to S3 client;
89+
* Defaults to 'legacy'
90+
*
8591
* @!attribute useAccelerateEndpoint
8692
* @note This configuration option is only compatible with S3 while accessing
8793
* dns-compatible buckets.
@@ -238,6 +244,10 @@ AWS.Config = AWS.util.inherit({
238244
* @option options s3DisableBodySigning [Boolean] whether S3 body signing
239245
* should be disabled when using signature version `v4`. Body signing
240246
* can only be disabled when using https. Defaults to `true`.
247+
* @option options s3UsEast1RegionalEndpoint ['legacy'|'regional'] when region
248+
* is set to 'us-east-1', whether to send s3 request to global endpoints or
249+
* 'us-east-1' regional endpoints. This config is only applicable to S3 client.
250+
* Defaults to `legacy`
241251
*
242252
* @option options retryDelayOptions [map] A set of options to configure
243253
* the retry delay on retryable errors. Currently supported options are:
@@ -523,6 +533,7 @@ AWS.Config = AWS.util.inherit({
523533
s3ForcePathStyle: false,
524534
s3BucketEndpoint: false,
525535
s3DisableBodySigning: true,
536+
s3UsEast1RegionalEndpoint: 'legacy',
526537
computeChecksums: true,
527538
convertResponseTypes: true,
528539
correctClockSkew: false,
@@ -537,7 +548,7 @@ AWS.Config = AWS.util.inherit({
537548
endpointDiscoveryEnabled: false,
538549
endpointCacheSize: 1000,
539550
hostPrefixEnabled: true,
540-
stsRegionalEndpoints: null
551+
stsRegionalEndpoints: 'legacy'
541552
},
542553

543554
/**

lib/config_regional_endpoint.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
var AWS = require('./core');
2+
/**
3+
* @api private
4+
*/
5+
function validateRegionalEndpointsFlagValue(configValue, errorOptions) {
6+
if (typeof configValue !== 'string') return undefined;
7+
else if (['legacy', 'regional'].indexOf(configValue.toLowerCase()) >= 0) {
8+
return configValue.toLowerCase();
9+
} else {
10+
throw AWS.util.error(new Error(), errorOptions);
11+
}
12+
}
13+
14+
/**
15+
* Resolve the configuration value for regional endpoint from difference sources: client
16+
* config, environmental variable, shared config file. Value can be case-insensitive
17+
* 'legacy' or 'reginal'.
18+
* @param originalConfig user-supplied config object to resolve
19+
* @param options a map of config property names from individual configuration source
20+
* - env: name of environmental variable that refers to the config
21+
* - sharedConfig: name of shared configuration file property that refers to the config
22+
* - clientConfig: name of client configuration property that refers to the config
23+
*
24+
* @api private
25+
*/
26+
function resolveRegionalEndpointsFlag(originalConfig, options) {
27+
originalConfig = originalConfig || {};
28+
//validate config value
29+
var resolved;
30+
if (originalConfig[options.clientConfig]) {
31+
resolved = validateRegionalEndpointsFlagValue(originalConfig[options.clientConfig], {
32+
code: 'InvalidConfiguration',
33+
message: 'invalid "' + options.clientConfig + '" configuration. Expect "legacy" ' +
34+
' or "regional". Got "' + originalConfig[options.clientConfig] + '".'
35+
});
36+
if (resolved) return resolved;
37+
}
38+
if (!AWS.util.isNode()) return resolved;
39+
//validate environmental variable
40+
if (Object.prototype.hasOwnProperty.call(process.env, options.env)) {
41+
var envFlag = process.env[options.env];
42+
resolved = validateRegionalEndpointsFlagValue(envFlag, {
43+
code: 'InvalidEnvironmentalVariable',
44+
message: 'invalid ' + options.env + ' environmental variable. Expect "legacy" ' +
45+
' or "regional". Got "' + process.env[options.env] + '".'
46+
});
47+
if (resolved) return resolved;
48+
}
49+
//validate shared config file
50+
var profile = {};
51+
try {
52+
var profiles = AWS.util.getProfilesFromSharedConfig(AWS.util.iniLoader);
53+
profile = profiles[process.env.AWS_PROFILE || AWS.util.defaultProfile];
54+
} catch (e) {};
55+
if (profile && Object.prototype.hasOwnProperty.call(profile, options.sharedConfig)) {
56+
var fileFlag = profile[options.sharedConfig];
57+
resolved = validateRegionalEndpointsFlagValue(fileFlag, {
58+
code: 'InvalidConfiguration',
59+
message: 'invalid ' + options.sharedConfig + ' profile config. Expect "legacy" ' +
60+
' or "regional". Got "' + profile[options.sharedConfig] + '".'
61+
});
62+
if (resolved) return resolved;
63+
}
64+
return resolved;
65+
}
66+
67+
module.exports = resolveRegionalEndpointsFlag;

lib/services/s3.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
var AWS = require('../core');
22
var v4Credentials = require('../signers/v4_credentials');
3+
var resolveRegionalEndpointsFlag = require('../config_regional_endpoint');
34

45
// Pull in managed upload extension
56
require('../s3/managed_upload');
@@ -105,6 +106,7 @@ AWS.util.update(AWS.S3.prototype, {
105106
request.addListener('validate', this.validateBucketEndpoint);
106107
request.addListener('validate', this.correctBucketRegionFromCache);
107108
request.addListener('validate', this.validateBucketName, prependListener);
109+
request.addListener('validate', this.optInUsEast1RegionalEndpoint, prependListener);
108110

109111
request.addListener('build', this.addContentType);
110112
request.addListener('build', this.populateURI);
@@ -189,6 +191,34 @@ AWS.util.update(AWS.S3.prototype, {
189191
return invalidOperations.indexOf(operation) === -1;
190192
},
191193

194+
/**
195+
* When us-east-1 region endpoint configuration is set, in stead of sending request to
196+
* global endpoint(e.g. 's3.amazonaws.com'), we will send request to
197+
* 's3.us-east-1.amazonaws.com'.
198+
* @api private
199+
*/
200+
optInUsEast1RegionalEndpoint: function optInUsEast1RegionalEndpoint(req) {
201+
var service = req.service;
202+
var config = service.config;
203+
config.s3UsEast1RegionalEndpoint = resolveRegionalEndpointsFlag(service._originalConfig, {
204+
env: 'AWS_S3_US_EAST_1_REGIONAL_ENDPOINT',
205+
sharedConfig: 's3_us_east_1_regional_endpoint',
206+
clientConfig: 's3UsEast1RegionalEndpoint'
207+
});
208+
if (
209+
!(service._originalConfig || {}).endpoint &&
210+
req.httpRequest.region === 'us-east-1' &&
211+
config.s3UsEast1RegionalEndpoint === 'regional' &&
212+
req.httpRequest.endpoint.hostname.indexOf('s3.amazonaws.com') >= 0
213+
) {
214+
var insertPoint = config.endpoint.indexOf('.amazonaws.com');
215+
regionalEndpoint = config.endpoint.substring(0, insertPoint) +
216+
'.us-east-1' + config.endpoint.substring(insertPoint);
217+
var endpoint = req.httpRequest.endpoint;
218+
endpoint.hostname = regionalEndpoint;
219+
endpoint.host = regionalEndpoint;
220+
}
221+
},
192222

193223
/**
194224
* S3 prefers dns-compatible bucket names to be moved from the uri path

lib/services/sts.js

Lines changed: 20 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
var AWS = require('../core');
2-
var regionConfig = require('../region_config');
2+
var resolveRegionalEndpointsFlag = require('../config_regional_endpoint');
33
var ENV_REGIONAL_ENDPOINT_ENABLED = 'AWS_STS_REGIONAL_ENDPOINTS';
44
var CONFIG_REGIONAL_ENDPOINT_ENABLED = 'sts_regional_endpoints';
55

@@ -51,77 +51,38 @@ AWS.util.update(AWS.STS.prototype, {
5151
/**
5252
* @api private
5353
*/
54-
validateRegionalEndpointsFlagValue: function validateRegionalEndpointsFlagValue(configValue, errorOptions) {
55-
if (typeof configValue === 'string' && ['legacy', 'regional'].indexOf(configValue.toLowerCase()) >= 0) {
56-
this.config.stsRegionalEndpoints = configValue.toLowerCase();
57-
return;
58-
} else {
59-
throw AWS.util.error(new Error(), errorOptions);
60-
}
54+
setupRequestListeners: function setupRequestListeners(request) {
55+
request.addListener('validate', this.optInRegionalEndpoint, true);
6156
},
6257

6358
/**
6459
* @api private
6560
*/
66-
validateRegionalEndpointsFlag: function validateRegionalEndpointsFlag() {
67-
//validate config value
68-
var config = this.config;
69-
if (config.stsRegionalEndpoints) {
70-
this.validateRegionalEndpointsFlagValue(config.stsRegionalEndpoints, {
71-
code: 'InvalidConfiguration',
72-
message: 'invalid "stsRegionalEndpoints" configuration. Expect "legacy" ' +
73-
' or "regional". Got "' + config.stsRegionalEndpoints + '".'
74-
});
75-
}
76-
if (!AWS.util.isNode()) return;
77-
//validate environmental variable
78-
if (Object.prototype.hasOwnProperty.call(process.env, ENV_REGIONAL_ENDPOINT_ENABLED)) {
79-
var envFlag = process.env[ENV_REGIONAL_ENDPOINT_ENABLED];
80-
this.validateRegionalEndpointsFlagValue(envFlag, {
81-
code: 'InvalidEnvironmentalVariable',
82-
message: 'invalid ' + ENV_REGIONAL_ENDPOINT_ENABLED + ' environmental variable. Expect "legacy" ' +
83-
' or "regional". Got "' + process.env[ENV_REGIONAL_ENDPOINT_ENABLED] + '".'
84-
});
85-
}
86-
//validate shared config file
87-
var profile = {};
88-
try {
89-
var profiles = AWS.util.getProfilesFromSharedConfig(AWS.util.iniLoader);
90-
profile = profiles[process.env.AWS_PROFILE || AWS.util.defaultProfile];
91-
} catch (e) {};
92-
if (profile && Object.prototype.hasOwnProperty.call(profile, CONFIG_REGIONAL_ENDPOINT_ENABLED)) {
93-
var fileFlag = profile[CONFIG_REGIONAL_ENDPOINT_ENABLED];
94-
this.validateRegionalEndpointsFlagValue(fileFlag, {
95-
code: 'InvalidConfiguration',
96-
message: 'invalid '+CONFIG_REGIONAL_ENDPOINT_ENABLED+' profile config. Expect "legacy" ' +
97-
' or "regional". Got "' + profile[CONFIG_REGIONAL_ENDPOINT_ENABLED] + '".'
98-
});
99-
}
100-
},
101-
102-
/**
103-
* @api private
104-
*/
105-
optInRegionalEndpoint: function optInRegionalEndpoint() {
106-
this.validateRegionalEndpointsFlag();
107-
var config = this.config;
108-
if (config.stsRegionalEndpoints === 'regional') {
109-
regionConfig(this);
110-
if (!this.isGlobalEndpoint) return;
111-
this.isGlobalEndpoint = false;
61+
optInRegionalEndpoint: function optInRegionalEndpoint(req) {
62+
var service = req.service;
63+
var config = service.config;
64+
config.stsRegionalEndpoints = resolveRegionalEndpointsFlag(service._originalConfig, {
65+
env: ENV_REGIONAL_ENDPOINT_ENABLED,
66+
sharedConfig: CONFIG_REGIONAL_ENDPOINT_ENABLED,
67+
clientConfig: 'stsRegionalEndpoints'
68+
});
69+
if (
70+
config.stsRegionalEndpoints === 'regional' &&
71+
service.isGlobalEndpoint
72+
) {
11273
//client will throw if region is not supplied; request will be signed with specified region
11374
if (!config.region) {
11475
throw AWS.util.error(new Error(),
11576
{code: 'ConfigError', message: 'Missing region in config'});
11677
}
11778
var insertPoint = config.endpoint.indexOf('.amazonaws.com');
118-
config.endpoint = config.endpoint.substring(0, insertPoint) +
79+
regionalEndpoint = config.endpoint.substring(0, insertPoint) +
11980
'.' + config.region + config.endpoint.substring(insertPoint);
81+
var endpoint = req.httpRequest.endpoint;
82+
endpoint.hostname = regionalEndpoint;
83+
endpoint.host = regionalEndpoint;
84+
req.httpRequest.region = config.region;
12085
}
121-
},
122-
123-
validateService: function validateService() {
124-
this.optInRegionalEndpoint();
12586
}
12687

12788
});

scripts/region-checker/whitelist.js

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ var whitelist = {
22
'/config.js': [
33
24,
44
25,
5-
187
5+
85,
6+
86,
7+
193,
8+
247,
9+
248
610
],
711
'/credentials/cognito_identity_credentials.js': [
812
78,
@@ -25,16 +29,20 @@ var whitelist = {
2529
316
2630
],
2731
'/services/s3.js': [
28-
68,
2932
69,
30-
515,
31-
517,
32-
516,
33-
636,
34-
647,
35-
648,
36-
649,
37-
654
33+
70,
34+
194,
35+
196,
36+
209,
37+
215,
38+
545,
39+
546,
40+
547,
41+
666,
42+
677,
43+
678,
44+
684,
45+
679
3846
]
3947
};
4048

0 commit comments

Comments
 (0)