Skip to content

Commit af16180

Browse files
committed
Fix STS token expiration
Signed-off-by: Ben <belimele@redhat.com>
1 parent 4a116e5 commit af16180

File tree

3 files changed

+117
-21
lines changed

3 files changed

+117
-21
lines changed

config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,8 @@ config.QUOTA_MAX_OBJECTS = Number.MAX_SAFE_INTEGER;
905905
//////////////////////////
906906

907907
config.STS_DEFAULT_SESSION_TOKEN_EXPIRY_MS = 60 * 60 * 1000; // 1 hour
908+
config.STS_MIN_DURATION_SECONDS = 15 * 60; // 15 minutes
909+
config.STS_MAX_DURATION_SECONDS = 12 * 60 * 60; // 12 hours
908910

909911
/////////////////////////
910912
// BLOCK STORE FS //

src/endpoint/sts/ops/sts_post_assume_role.js

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ const { StsError } = require('../sts_errors');
66
const jwt_utils = require('../../../util/jwt_utils');
77
const config = require('../../../../config');
88
const { CONTENT_TYPE_APP_FORM_URLENCODED } = require('../../../util/http_utils');
9+
const s3_utils = require('../../s3/s3_utils');
910

1011
/**
1112
* https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
1213
*/
1314
async function assume_role(req) {
1415
dbg.log1('sts_post_assume_role body: ', req.body);
16+
const duration_ms = _parse_sts_duration(req.body.duration_seconds);
17+
const duration_sec = Math.ceil(duration_ms / 1000);
18+
const expiration_time = Date.now() + duration_ms;
1519
let assumed_role;
1620
try {
1721
assumed_role = await req.sts_sdk.get_assumed_role(req);
@@ -21,10 +25,11 @@ async function assume_role(req) {
2125
}
2226
throw new StsError(StsError.InternalFailure);
2327
}
28+
2429
// Temporary credentials are NOT stored in noobaa
2530
// The generated session token will store in it the temporary credentials and expiry and the role's access key
2631
const access_keys = await req.sts_sdk.generate_temp_access_keys();
27-
const expiry = config.STS_DEFAULT_SESSION_TOKEN_EXPIRY_MS;
32+
2833
return {
2934
AssumeRoleResponse: {
3035
AssumeRoleResult: {
@@ -35,12 +40,12 @@ async function assume_role(req) {
3540
Credentials: {
3641
AccessKeyId: access_keys.access_key.unwrap(),
3742
SecretAccessKey: access_keys.secret_key.unwrap(),
38-
Expiration: expiry,
43+
Expiration: s3_utils.format_s3_xml_date(expiration_time),
3944
SessionToken: generate_session_token({
4045
access_key: access_keys.access_key.unwrap(),
4146
secret_key: access_keys.secret_key.unwrap(),
4247
assumed_role_access_key: assumed_role.access_key
43-
}, expiry)
48+
}, duration_sec)
4449
},
4550
PackedPolicySize: 0
4651
}
@@ -49,11 +54,50 @@ async function assume_role(req) {
4954
}
5055

5156
// create and return the signed token
57+
/**
58+
* @param {Object} auth_options
59+
* @param {Number} expiry in seconds
60+
* @returns {String}
61+
*/
5262
function generate_session_token(auth_options, expiry) {
5363
dbg.log1('sts_post_assume_role.make_session_token: ', auth_options, expiry);
5464
return jwt_utils.make_auth_token(auth_options, { expiresIn: expiry });
5565
}
5666

67+
// TODO: Generalize and move to a utils file in the future
68+
/**
69+
* @param {String|undefined} duration_input duration in seconds
70+
* @returns {Number} duration in milliseconds
71+
*/
72+
function _parse_sts_duration(duration_input) {
73+
if (duration_input === undefined) {
74+
return config.STS_DEFAULT_SESSION_TOKEN_EXPIRY_MS;
75+
}
76+
77+
const duration_sec = Number(duration_input);
78+
79+
if (!Number.isInteger(duration_sec)) {
80+
throw new StsError(StsError.InvalidParameterValue);
81+
}
82+
83+
if (duration_sec < config.STS_MIN_DURATION_SECONDS) {
84+
throw new StsError(_sts_duration_validation_error(duration_input, 'greater', config.STS_MIN_DURATION_SECONDS));
85+
}
86+
if (duration_sec > config.STS_MAX_DURATION_SECONDS) {
87+
throw new StsError(_sts_duration_validation_error(duration_input, 'less', config.STS_MAX_DURATION_SECONDS));
88+
}
89+
90+
const duration_ms = duration_sec * 1000;
91+
return duration_ms;
92+
}
93+
94+
function _sts_duration_validation_error(duration_input, constraint, constraint_value) {
95+
return {
96+
...StsError.ValidationError,
97+
message: `Value ${duration_input} for durationSeconds failed to satisfy constraint: Member must have value ${constraint} than or equal to ${constraint_value}`,
98+
};
99+
}
100+
57101
module.exports = {
58102
handler: assume_role,
59103
body: {

src/test/unit_tests/test_sts.js

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const jwt_utils = require('../../util/jwt_utils');
1616
const config = require('../../../config');
1717
const { S3Error } = require('../../endpoint/s3/s3_errors');
1818

19+
const defualt_expiry_seconds = Math.ceil(config.STS_DEFAULT_SESSION_TOKEN_EXPIRY_MS / 1000);
20+
1921
const errors = {
2022
expired_token_s3: {
2123
code: S3Error.ExpiredToken.code,
@@ -53,6 +55,10 @@ const errors = {
5355
code: stsErr.InvalidAction.code,
5456
message: stsErr.InvalidAction.message
5557
},
58+
validation_error: {
59+
code: stsErr.ValidationError.code,
60+
message: stsErr.ValidationError.message
61+
},
5662
invalid_schema_params: {
5763
code: 'INVALID_SCHEMA_PARAMS',
5864
message: 'INVALID_SCHEMA_PARAMS CLIENT account_api#/methods/create_account'
@@ -177,7 +183,7 @@ mocha.describe('STS tests', function() {
177183
};
178184
const json = await assume_role_and_parse_xml(sts_admin, params);
179185
validate_assume_role_response(json, `arn:aws:sts::${user_b_key}:assumed-role/${role_b}/${params.RoleSessionName}`,
180-
`${user_b_key}:${params.RoleSessionName}`, user_b_key);
186+
`${user_b_key}:${params.RoleSessionName}`, user_b_key, defualt_expiry_seconds);
181187
});
182188

183189
mocha.it('admin assume non existing role of user b - should be rejected', async function() {
@@ -208,10 +214,10 @@ mocha.describe('STS tests', function() {
208214
};
209215
const json = await assume_role_and_parse_xml(sts_c, params);
210216
validate_assume_role_response(json, `arn:aws:sts::${user_b_key}:assumed-role/${role_b}/${params.RoleSessionName}`,
211-
`${user_b_key}:${params.RoleSessionName}`, user_b_key);
217+
`${user_b_key}:${params.RoleSessionName}`, user_b_key, defualt_expiry_seconds);
212218

213219
const temp_creds = validate_assume_role_response(json, `arn:aws:sts::${user_b_key}:assumed-role/${role_b}/${params.RoleSessionName}`,
214-
`${user_b_key}:${params.RoleSessionName}`, user_b_key);
220+
`${user_b_key}:${params.RoleSessionName}`, user_b_key, defualt_expiry_seconds);
215221
const s3 = new AWS.S3({
216222
...sts_creds,
217223
accessKeyId: temp_creds.access_key,
@@ -255,7 +261,7 @@ mocha.describe('STS tests', function() {
255261
};
256262
const json = await assume_role_and_parse_xml(sts, params);
257263
validate_assume_role_response(json, `arn:aws:sts::${user_b_key}:assumed-role/${role_b}/${params.RoleSessionName}`,
258-
`${user_b_key}:${params.RoleSessionName}`, user_b_key);
264+
`${user_b_key}:${params.RoleSessionName}`, user_b_key, defualt_expiry_seconds);
259265
});
260266

261267
mocha.it('update assume role policy of user b to allow user a', async function() {
@@ -296,7 +302,7 @@ mocha.describe('STS tests', function() {
296302
};
297303
const json = await assume_role_and_parse_xml(sts_c, params);
298304
validate_assume_role_response(json, `arn:aws:sts::${user_b_key}:assumed-role/${role_b}/${params.RoleSessionName}`,
299-
`${user_b_key}:${params.RoleSessionName}`, user_b_key);
305+
`${user_b_key}:${params.RoleSessionName}`, user_b_key, defualt_expiry_seconds);
300306
});
301307

302308
mocha.it('update assume role policy of user b to allow user a sts:*', async function() {
@@ -337,7 +343,7 @@ mocha.describe('STS tests', function() {
337343
};
338344
const json = await assume_role_and_parse_xml(sts_c, params);
339345
validate_assume_role_response(json, `arn:aws:sts::${user_b_key}:assumed-role/${role_b}/${params.RoleSessionName}`,
340-
`${user_b_key}:${params.RoleSessionName}`, user_b_key);
346+
`${user_b_key}:${params.RoleSessionName}`, user_b_key, defualt_expiry_seconds);
341347
});
342348

343349
mocha.it('update assume role policy of user b to allow user a *', async function() {
@@ -383,7 +389,7 @@ async function assume_role_and_parse_xml(sts, params) {
383389
return json;
384390
}
385391

386-
function validate_assume_role_response(json, expected_arn, expected_role_id, assumed_access_key) {
392+
function validate_assume_role_response(json, expected_arn, expected_role_id, assumed_access_key, duration_seconds) {
387393
dbg.log0('test.sts.validate_assume_role_response: ', json);
388394
assert.ok(json && json.AssumeRoleResponse && json.AssumeRoleResponse.AssumeRoleResult);
389395
const result = json.AssumeRoleResponse.AssumeRoleResult[0];
@@ -392,7 +398,9 @@ function validate_assume_role_response(json, expected_arn, expected_role_id, ass
392398
// validate credentials
393399
const credentials = result.Credentials[0];
394400
assert.ok(credentials && credentials.AccessKeyId[0] && credentials.SecretAccessKey[0]);
395-
assert.equal(credentials.Expiration[0], config.STS_DEFAULT_SESSION_TOKEN_EXPIRY_MS);
401+
const duration_ms = duration_seconds ? duration_seconds * 1000 : config.STS_DEFAULT_SESSION_TOKEN_EXPIRY_MS;
402+
const creds_generation_time_ms = new Date(credentials.Expiration[0]).getTime() - duration_ms;
403+
assert(creds_generation_time_ms < Date.now());
396404
if (config.STS_DEFAULT_SESSION_TOKEN_EXPIRY_MS !== 0) {
397405
verify_session_token(credentials.SessionToken[0], credentials.AccessKeyId[0],
398406
credentials.SecretAccessKey[0], assumed_access_key);
@@ -533,7 +541,7 @@ mocha.describe('Session token tests', function() {
533541

534542
const json = await assume_role_and_parse_xml(accounts[1].sts, params);
535543
const result_obj = validate_assume_role_response(json, `arn:aws:sts::${user_a_key}:assumed-role/${role_alice}/${params.RoleSessionName}`,
536-
`${user_a_key}:${params.RoleSessionName}`, user_a_key);
544+
`${user_a_key}:${params.RoleSessionName}`, user_a_key, defualt_expiry_seconds);
537545

538546
const temp_s3_with_session_token = new AWS.S3({
539547
...sts_creds,
@@ -547,6 +555,48 @@ mocha.describe('Session token tests', function() {
547555
assert.ok(buckets1.Buckets.length > 0);
548556
});
549557

558+
mocha.it('user b assume role of user a - valid expiry via durationSeconds - list s3 - should be allowed', async function() {
559+
const user_a_key = accounts[0].access_keys[0].access_key.unwrap();
560+
const duration_seconds = 25000;
561+
const params = {
562+
DurationSeconds: duration_seconds,
563+
RoleArn: `arn:aws:sts::${user_a_key}:role/${role_alice}`,
564+
RoleSessionName: 'just_a_dummy_session_name'
565+
};
566+
567+
const json = await assume_role_and_parse_xml(accounts[1].sts, params);
568+
const result_obj = validate_assume_role_response(json, `arn:aws:sts::${user_a_key}:assumed-role/${role_alice}/${params.RoleSessionName}`,
569+
`${user_a_key}:${params.RoleSessionName}`, user_a_key, duration_seconds);
570+
571+
const temp_s3_with_session_token = new AWS.S3({
572+
...sts_creds,
573+
endpoint: coretest.get_https_address(),
574+
accessKeyId: result_obj.access_key,
575+
secretAccessKey: result_obj.secret_key,
576+
sessionToken: result_obj.session_token
577+
});
578+
579+
const buckets1 = await temp_s3_with_session_token.listBuckets().promise();
580+
assert.ok(buckets1.Buckets.length > 0);
581+
});
582+
583+
mocha.it('user b assume role of user a - invalid expiry via durationSeconds - should be rejected', async function() {
584+
const user_a_key = accounts[0].access_keys[0].access_key.unwrap();
585+
const params = {
586+
DurationSeconds: 43201,
587+
RoleArn: `arn:aws:sts::${user_a_key}:role/${role_alice}`,
588+
RoleSessionName: 'just_a_dummy_session_name'
589+
};
590+
591+
const expected_error_message = `Value 43201 for durationSeconds failed to satisfy constraint:
592+
Member must have value less than or equal to 43200`;
593+
assert_throws_async(
594+
assume_role_and_parse_xml(accounts[0].sts, params),
595+
errors.validation_error.code,
596+
expected_error_message
597+
);
598+
});
599+
550600
mocha.it('user b assume role of user a - default expiry - list s3 without session token - should be rejected', async function() {
551601
const user_a_key = accounts[0].access_keys[0].access_key.unwrap();
552602
const params = {
@@ -556,7 +606,7 @@ mocha.describe('Session token tests', function() {
556606

557607
const json = await assume_role_and_parse_xml(accounts[1].sts, params);
558608
const result_obj = validate_assume_role_response(json, `arn:aws:sts::${user_a_key}:assumed-role/${role_alice}/${params.RoleSessionName}`,
559-
`${user_a_key}:${params.RoleSessionName}`, user_a_key);
609+
`${user_a_key}:${params.RoleSessionName}`, user_a_key, defualt_expiry_seconds);
560610

561611
const temp_s3 = new AWS.S3({
562612
...sts_creds,
@@ -578,11 +628,11 @@ mocha.describe('Session token tests', function() {
578628

579629
const json1 = await assume_role_and_parse_xml(accounts[1].sts, params);
580630
const result_obj1 = validate_assume_role_response(json1, `arn:aws:sts::${user_a_key}:assumed-role/${role_alice}/${params.RoleSessionName}`,
581-
`${user_a_key}:${params.RoleSessionName}`, user_a_key);
631+
`${user_a_key}:${params.RoleSessionName}`, user_a_key, defualt_expiry_seconds);
582632

583633
const json2 = await assume_role_and_parse_xml(accounts[2].sts, params);
584634
const result_obj2 = validate_assume_role_response(json2, `arn:aws:sts::${user_a_key}:assumed-role/${role_alice}/${params.RoleSessionName}`,
585-
`${user_a_key}:${params.RoleSessionName}`, user_a_key);
635+
`${user_a_key}:${params.RoleSessionName}`, user_a_key, defualt_expiry_seconds);
586636

587637
const temp_s3 = new AWS.S3({
588638
...sts_creds,
@@ -606,7 +656,7 @@ mocha.describe('Session token tests', function() {
606656

607657
const json = await assume_role_and_parse_xml(accounts[1].sts, params);
608658
const result_obj = validate_assume_role_response(json, `arn:aws:sts::${user_a_key}:assumed-role/${role_alice}/${params.RoleSessionName}`,
609-
`${user_a_key}:${params.RoleSessionName}`, user_a_key);
659+
`${user_a_key}:${params.RoleSessionName}`, user_a_key, defualt_expiry_seconds);
610660

611661
const temp_s3_with_session_token = new AWS.S3({
612662
...sts_creds,
@@ -629,7 +679,7 @@ mocha.describe('Session token tests', function() {
629679

630680
const json = await assume_role_and_parse_xml(accounts[1].sts, params);
631681
const result_obj = validate_assume_role_response(json, `arn:aws:sts::${user_a_key}:assumed-role/${role_alice}/${params.RoleSessionName}`,
632-
`${user_a_key}:${params.RoleSessionName}`, user_a_key);
682+
`${user_a_key}:${params.RoleSessionName}`, user_a_key, defualt_expiry_seconds);
633683

634684
const temp_s3_with_session_token = new AWS.S3({
635685
...sts_creds,
@@ -653,7 +703,7 @@ mocha.describe('Session token tests', function() {
653703

654704
const json = await assume_role_and_parse_xml(accounts[1].sts, params);
655705
const result_obj = validate_assume_role_response(json, `arn:aws:sts::${user_a_key}:assumed-role/${role_alice}/${params.RoleSessionName}`,
656-
`${user_a_key}:${params.RoleSessionName}`, user_a_key);
706+
`${user_a_key}:${params.RoleSessionName}`, user_a_key, defualt_expiry_seconds);
657707

658708
const temp_sts_with_session_token = new AWS.STS({
659709
...sts_creds,
@@ -676,7 +726,7 @@ mocha.describe('Session token tests', function() {
676726

677727
const json = await assume_role_and_parse_xml(accounts[1].sts, params);
678728
const result_obj = validate_assume_role_response(json, `arn:aws:sts::${user_a_key}:assumed-role/${role_alice}/${params.RoleSessionName}`,
679-
`${user_a_key}:${params.RoleSessionName}`, user_a_key);
729+
`${user_a_key}:${params.RoleSessionName}`, user_a_key, defualt_expiry_seconds);
680730

681731
const temp_sts_with_session_token = new AWS.STS({
682732
...sts_creds,
@@ -700,7 +750,7 @@ mocha.describe('Session token tests', function() {
700750

701751
const json = await assume_role_and_parse_xml(accounts[1].sts, params);
702752
const result_obj = validate_assume_role_response(json, `arn:aws:sts::${user_a_key}:assumed-role/${role_alice}/${params.RoleSessionName}`,
703-
`${user_a_key}:${params.RoleSessionName}`, user_a_key);
753+
`${user_a_key}:${params.RoleSessionName}`, user_a_key, defualt_expiry_seconds);
704754

705755
const temp_s3_with_session_token = new AWS.S3({
706756
...sts_creds,
@@ -725,7 +775,7 @@ mocha.describe('Session token tests', function() {
725775

726776
const json = await assume_role_and_parse_xml(accounts[1].sts, params);
727777
const result_obj = validate_assume_role_response(json, `arn:aws:sts::${user_a_key}:assumed-role/${role_alice}/${params.RoleSessionName}`,
728-
`${user_a_key}:${params.RoleSessionName}`, user_a_key);
778+
`${user_a_key}:${params.RoleSessionName}`, user_a_key, defualt_expiry_seconds);
729779

730780
const temp_sts_with_session_token = new AWS.STS({
731781
...sts_creds,

0 commit comments

Comments
 (0)