-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'improvement/ZENKO-3925-add-e2e-iam-policies-tests-assum…
…rRoleWII' into q/2.3
- Loading branch information
Showing
4 changed files
with
238 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
const assert = require('assert'); | ||
const async = require('async'); | ||
const VaultClient = require('../../VaultClient'); | ||
const { getS3Client } = require('../../s3SDK'); | ||
const { getSTSClient } = require('../../stsSDK'); | ||
const { getTokenForIdentity } = require('../../utils/getWebIdentityToken'); | ||
const { metadataSearchResponseCode } = require('./utils'); | ||
|
||
let iamClient = null; | ||
let stsClient = null; | ||
let s3Client = null; | ||
|
||
const clientAdmin = VaultClient.getAdminClient(); | ||
const accountName = 'AccountTest'; | ||
const accountInfo = { | ||
email: `${accountName}@test.com`, | ||
password: 'test', | ||
}; | ||
const externalAccessKey = 'DZMMJUPWIUK8IWXRP0HQ'; | ||
const externalSecretKey = 'iTuJdlidzrLipymvAGrLP66Yxghl4NQxLZR3cLlu'; | ||
|
||
const duration = '1000'; | ||
|
||
const bucket1 = 'bucket1'; | ||
|
||
const storageManagerName = 'storage_manager'; | ||
const storageAccountOwnerName = 'storage_account_owner'; | ||
const dataConsumerName = 'data_consumer'; | ||
const storageManagerRole = 'storage-manager-role'; | ||
const storageAccountOwnerRole = 'storage-account-owner-role'; | ||
const dataConsumerRole = 'data-consumer-role'; | ||
|
||
describe('iam policies - cloudserver - AssumeRoleWithWebIdentity - Metadata', () => { | ||
|
||
before(done => { | ||
async.series([ | ||
// create an account, generateAccountAccessKey for it | ||
// get iam client, sts client and s3 client of this account | ||
next => clientAdmin.createAccount(accountName, accountInfo, next), | ||
next => clientAdmin.generateAccountAccessKey(accountName, next, { externalAccessKey, externalSecretKey }), | ||
next => { | ||
iamClient = VaultClient.getIamClient(externalAccessKey, externalSecretKey); | ||
stsClient = getSTSClient(externalAccessKey, externalSecretKey); | ||
s3Client = getS3Client(externalAccessKey, externalSecretKey); | ||
next(); | ||
}, | ||
// use s3 client to create a bucket and put 2 objects | ||
next => { | ||
async.series([ | ||
next => s3Client.createBucket({ Bucket: bucket1 }, next), | ||
next => s3Client.putObject({ Bucket: bucket1, Key: 'file1' }, next), | ||
next => s3Client.putObject({ Bucket: bucket1, Key: 'file2' }, next), | ||
], next); | ||
}, | ||
], done); | ||
}); | ||
|
||
after(done => { | ||
async.series([ | ||
next => s3Client.deleteObjects({ | ||
Bucket: bucket1, | ||
Delete: { | ||
Objects: [{ Key: 'file1' }, { Key: 'file2' }], | ||
Quiet: false, | ||
}, | ||
}, next), | ||
next => s3Client.deleteBucket({ Bucket: bucket1 }, next), | ||
next => clientAdmin.deleteAccount(accountName, next), | ||
], done); | ||
}); | ||
|
||
|
||
const tests = [ | ||
{ | ||
name: 'should be able to perform metadata search on all buckets for storage manager role', | ||
oidcIdentity: storageManagerName, | ||
roleName: storageManagerRole, | ||
assertion: result => assert.strictEqual(result.statusCode, 200), | ||
}, | ||
{ | ||
name: 'should be able to perform metadata search on all buckets for storage account owner role', | ||
oidcIdentity: storageAccountOwnerName, | ||
roleName: storageAccountOwnerRole, | ||
assertion: result => assert.strictEqual(result.statusCode, 200), | ||
}, | ||
{ | ||
name: 'should be able to perform metadata search on all buckets for data consumer role', | ||
oidcIdentity: dataConsumerName, | ||
roleName: dataConsumerRole, | ||
assertion: result => assert.strictEqual(result.statusCode, 200), | ||
}, | ||
]; | ||
|
||
tests.forEach((test, i) => { | ||
it(test.name, done => { | ||
let jwtToken = null; | ||
let roleArn = null; | ||
async.waterfall([ | ||
next => getTokenForIdentity(test.oidcIdentity, (err, token) => { | ||
assert.ifError(err); | ||
jwtToken = token; | ||
next(); | ||
}), | ||
next => iamClient.getRole({ RoleName: test.roleName }, (err, res) => { | ||
assert.ifError(err); | ||
roleArn = res.Role.Arn; | ||
next(); | ||
}), | ||
next => stsClient.assumeRoleWithWebIdentity({ | ||
RoleArn: roleArn, | ||
DurationSeconds: duration, | ||
WebIdentityToken: jwtToken, | ||
RoleSessionName: `session-name-test-${i}`, | ||
}, (err, res) => { | ||
assert.ifError(err); | ||
return next(null, res); | ||
}), | ||
(res, next) => { | ||
const sessionUserCredentials = { | ||
accessKeyId: res.Credentials.AccessKeyId, | ||
secretAccessKey: res.Credentials.SecretAccessKey, | ||
sessionToken: res.Credentials.SessionToken, | ||
}; | ||
// make metadataSearch request using session user's credentials | ||
// and see if can get the correct response | ||
metadataSearchResponseCode(sessionUserCredentials, bucket1, (err, res) => { | ||
if (err) { | ||
assert.ifError(err); | ||
return done(err); | ||
} | ||
test.assertion(res); | ||
return next(); | ||
}); | ||
}], done); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
const querystring = require("querystring"); | ||
const http = require("http"); | ||
const assert = require('assert'); | ||
|
||
const USER_1_PASSWORD = process.env.KEYCLOAK_TEST_PASSWORD || '123'; | ||
const HOST_1_URL = process.env.KEYCLOAK_TEST_HOST || 'http://keycloak.zenko.local'; | ||
const HOST_1_PORT = parseInt(process.env.KEYCLOAK_TEST_PORT, 10) || 80; | ||
const REALM_NAME = process.env.KEYCLOAK_TEST_REALM_NAME || 'zenko'; | ||
const KEYCLOAK_PATH = `/auth/realms/${REALM_NAME}/protocol/openid-connect/token`; | ||
const CLIENT_ID = process.env.KEYCLOAK_TEST_CLIENT_ID || 'zenko-ui'; | ||
const GRANT_TYPE = process.env.KEYCLOAK_TEST_GRANT_TYPE || 'password'; | ||
|
||
|
||
function getTokenForIdentity(identity, callback) { | ||
getWebIdentityToken(identity, USER_1_PASSWORD, HOST_1_URL, | ||
HOST_1_PORT, KEYCLOAK_PATH, CLIENT_ID, GRANT_TYPE, (err, token) => { | ||
assert(err === null); | ||
callback(err, token); | ||
}); | ||
} | ||
|
||
|
||
/** | ||
* HTTP client to request JWT token given the username and password. | ||
* | ||
* @param {string} username - username of user requesting token | ||
* @param {string} password - password of user requesting token | ||
* @param {string} host - host URL of keycloak service | ||
* @param {number} port - port of keycloak service | ||
* @param {string} path - path of keycloak service authentication API | ||
* @param {string} client_id - id of the client of the user | ||
* @param {string} grant_type - grant of the user | ||
* @param {function} callback - callback function called with error or result | ||
* @returns {undefined} undefined | ||
*/ | ||
function getWebIdentityToken(username, password, host, port, path, | ||
client_id, grant_type, callback) { | ||
// In Zenko, we are using an endpoint as the `KEYCLOAK_TEST_HOST` env variable | ||
// So we should remove any existing http of https prefix in HOST_1_URL. | ||
host = host.replace('https://', '').replace('http://', ''); | ||
const userData = querystring.stringify({ | ||
username, | ||
password, | ||
client_id, | ||
grant_type, | ||
}); | ||
const options = { | ||
host, | ||
port, | ||
method: 'POST', | ||
path, | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
'Content-Length': userData.length, | ||
}, | ||
rejectUnauthorized: false, | ||
}; | ||
const req = http.request(options, response => { | ||
if (response.statusCode < 200 || response.statusCode >= 300) { | ||
response.resume(); | ||
return callback(new Error(`Status Code: ${response.statusCode}`)); | ||
} | ||
const data = []; | ||
return response | ||
.on('data', chunk => data.push(chunk)) | ||
.on('end', () => { | ||
let accessToken = null; | ||
let error = null; | ||
try { | ||
accessToken = (JSON.parse(Buffer.concat(data))).access_token; | ||
} catch (err) { | ||
error = err; | ||
} | ||
return callback(error, accessToken); | ||
}) | ||
.on('error', err => callback(err)); | ||
}); | ||
req.on('error', err => callback(err)); | ||
req.write(userData); | ||
req.end(); | ||
} | ||
|
||
module.exports = { | ||
getTokenForIdentity, | ||
} |