Compare commits

..

No commits in common. "9a5fe5a9f6a5d079af63fee10576f5b5f47a566b" and "de4620dc70697621a944e19165a43fd7ea3ac4c9" have entirely different histories.

12 changed files with 254 additions and 234 deletions

View File

@ -3,8 +3,8 @@
const errors = require('../errors'); const errors = require('../errors');
const queryString = require('querystring'); const queryString = require('querystring');
const AuthInfo = require('./AuthInfo'); const AuthInfo = require('./AuthInfo');
const v2 = require('./v2/authV2'); const authV2 = require('./v2/authV2');
const v4 = require('./v4/authV4'); const authV4 = require('./v4/authV4');
const constants = require('../constants'); const constants = require('../constants');
const constructStringToSignV4 = require('./v4/constructStringToSign'); const constructStringToSignV4 = require('./v4/constructStringToSign');
const convertUTCtoISO8601 = require('./v4/timeUtils').convertUTCtoISO8601; const convertUTCtoISO8601 = require('./v4/timeUtils').convertUTCtoISO8601;
@ -13,14 +13,10 @@ const vaultUtilities = require('./in_memory/vaultUtilities');
let vault = null; let vault = null;
const auth = {}; const auth = {};
// If no auth information is provided in request, then user is part of auth.setAuthHandler = handler => {
// 'All Users Group' so use this group as the canonicalID for the publicUser
const publicUserInfo = new AuthInfo({ canonicalID: constants.publicId });
function setAuthHandler(handler) {
vault = handler; vault = handler;
return auth; return auth;
} };
/** /**
* This function will check validity of request parameters to authenticate * This function will check validity of request parameters to authenticate
@ -30,92 +26,69 @@ function setAuthHandler(handler) {
* @param {string} awsService - Aws service related * @param {string} awsService - Aws service related
* @param {object} data - Parameters from queryString parsing or body of * @param {object} data - Parameters from queryString parsing or body of
* POST request * POST request
* * @return {object} Return object with information to authenticate
* @return {object} ret
* @return {object} ret.err - arsenal.errors object if any error was found
* @return {object} ret.params - auth parameters to use later on for signature
* computation and check
* @return {object} ret.params.version - the auth scheme version
* (undefined, 2, 4)
* @return {object} ret.params.data - the auth scheme's specific data
*/ */
function extractParams(request, log, awsService, data) { auth.check = (request, log, awsService, data) => {
log.trace('extracting authentication signature informations'); log.debug('running auth checks', { method: 'auth' });
const authHeader = request.headers.authorization; const authHeader = request.headers.authorization;
const checkFunctions = { // Check whether signature is in header
v2: {
headers: v2.header.check,
query: v2.query.check,
},
v4: {
headers: v4.header.check,
query: v4.query.check,
},
};
let version = null;
let method = null;
// Identify auth version and method to dispatch to the right check function
if (authHeader) { if (authHeader) {
method = 'headers'; log.trace('authorization header', { authHeader });
// TODO: Check for security token header to handle temporary security // TODO: Check for security token header to
// credentials // handle temporary security credentials
if (authHeader.startsWith('AWS ')) { if (authHeader.startsWith('AWS ')) {
version = 'v2'; log.trace('authenticating request with auth v2 using headers');
return authV2.headerAuthCheck.check(request, log, data);
} else if (authHeader.startsWith('AWS4')) { } else if (authHeader.startsWith('AWS4')) {
version = 'v4'; log.trace('authenticating request with Auth V4 using headers');
} else { return authV4.headerAuthCheck.check(request, log, data, awsService);
log.trace('missing authorization security header', }
{ header: authHeader }); log.debug('missing authorization security header');
return { err: errors.MissingSecurityHeader }; return { err: errors.MissingSecurityHeader };
}
} else if (data.Signature) { } else if (data.Signature) {
method = 'query'; // Check whether signature is in query string
version = 'v2'; log.trace('authenticating request with auth v2 using query string');
return authV2.queryAuthCheck.check(request, log, data);
} else if (data['X-Amz-Algorithm']) { } else if (data['X-Amz-Algorithm']) {
method = 'query'; log.trace('authenticating request with Auth v4 using query string');
version = 'v4'; return authV4.queryAuthCheck.check(request, log, data);
} }
// If no auth information is provided in request, then
// user is part of 'All Users Group' so send back this
// group as the canonicalID
log.debug('No authentication provided. User identified as public');
const authInfo = new AuthInfo({ canonicalID: constants.publicId });
return { err: null, data: authInfo };
};
// Here, either both values are set, or none is set auth.doAuth = (request, log, cb, awsService, data) => {
if (version !== null && method !== null) { const res = auth.check(request, log, awsService, data);
if (!checkFunctions[version] || !checkFunctions[version][method]) {
log.trace('invalid auth version or method', { version, method });
return { err: errors.NotImplemented };
}
log.trace('identified auth method', { version, method });
return checkFunctions[version][method](request, log, data, awsService);
}
// no auth info identified
log.debug('assuming public user');
return { err: null, params: publicUserInfo };
}
function doAuth(request, log, cb, awsService, data) {
const res = extractParams(request, log, awsService, data);
if (res.err) { if (res.err) {
return cb(res.err); return cb(res.err);
} else if (res.params instanceof AuthInfo) { } else if (res.version === 2) {
return cb(null, res.params); return vault.authenticateV2Request(res.data.accessKey,
res.data.signatureFromRequest,
res.data.stringToSign, res.data.algo, log,
(err, authInfo) => {
if (err) {
return cb(err);
} }
return cb(null, authInfo);
// Corner cases managed, we're left with normal auth });
res.params.log = log; } else if (res.version === 4) {
if (res.params.version === 2) { res.data.log = log;
return vault.authenticateV2Request(res.params, cb); return vault.authenticateV4Request(res.data, cb, awsService);
} else if (res.params.version === 4) { } else if (res.data instanceof AuthInfo) {
return vault.authenticateV4Request(res.params, cb, awsService); return cb(null, res.data);
} }
log.error('authentication method not found', { log.error('authentication method not found', {
method: 'Arsenal.auth.doAuth', method: 'Arsenal.auth.doAuth',
}); });
return cb(errors.InternalError); return cb(errors.InternalError);
} };
function generateV4Headers(request, data, accessKey, secretKeyValue, auth.generateV4Headers =
awsService) { (request, data, accessKey, secretKeyValue, awsService) => {
Object.assign(request, { headers: {} }); Object.assign(request, { headers: {} });
const amzDate = convertUTCtoISO8601(Date.now()); const amzDate = convertUTCtoISO8601(Date.now());
// get date without time // get date without time
@ -162,15 +135,6 @@ function generateV4Headers(request, data, accessKey, secretKeyValue,
request.setHeader('authorization', authorizationHeader); request.setHeader('authorization', authorizationHeader);
Object.assign(request, { headers: {} }); Object.assign(request, { headers: {} });
Object.assign(request, { path }); Object.assign(request, { path });
} };
module.exports = { module.exports = auth;
setHandler: setAuthHandler,
server: {
extractParams,
doAuth,
},
client: {
generateV4Headers,
},
};

View File

@ -1,11 +1,11 @@
'use strict'; // eslint-disable-line strict 'use strict'; // eslint-disable-line strict
const headerAuthCheck = require('./headerAuthCheck');
const queryAuthCheck = require('./queryAuthCheck'); const queryAuthCheck = require('./queryAuthCheck');
const headerAuthCheck = require('./headerAuthCheck');
const authV2 = { const authV2 = {
header: headerAuthCheck, headerAuthCheck,
query: queryAuthCheck, queryAuthCheck,
}; };
module.exports = authV2; module.exports = authV2;

View File

@ -5,7 +5,9 @@ const constructStringToSign = require('./constructStringToSign');
const checkRequestExpiry = require('./checkRequestExpiry'); const checkRequestExpiry = require('./checkRequestExpiry');
const algoCheck = require('./algoCheck'); const algoCheck = require('./algoCheck');
function check(request, log, data) { const headerAuthCheck = {};
headerAuthCheck.check = (request, log, data) => {
log.trace('running header auth check'); log.trace('running header auth check');
const headers = request.headers; const headers = request.headers;
@ -55,7 +57,6 @@ function check(request, log, data) {
} }
return { return {
err: null, err: null,
params: {
version: 2, version: 2,
data: { data: {
accessKey, accessKey,
@ -63,8 +64,7 @@ function check(request, log, data) {
stringToSign, stringToSign,
algo, algo,
}, },
},
}; };
} };
module.exports = { check }; module.exports = headerAuthCheck;

View File

@ -6,7 +6,9 @@ const algoCheck = require('./algoCheck');
const constructStringToSign = require('./constructStringToSign'); const constructStringToSign = require('./constructStringToSign');
const checkRequestExpiry = require('./checkRequestExpiry'); const checkRequestExpiry = require('./checkRequestExpiry');
function check(request, log, data) { const queryAuthCheck = {};
queryAuthCheck.check = (request, log, data) => {
log.trace('running query auth check'); log.trace('running query auth check');
if (request.method === 'POST') { if (request.method === 'POST') {
log.debug('query string auth not supported for post requests'); log.debug('query string auth not supported for post requests');
@ -47,7 +49,6 @@ function check(request, log, data) {
} }
return { return {
err: null, err: null,
params: {
version: 2, version: 2,
data: { data: {
accessKey, accessKey,
@ -55,8 +56,7 @@ function check(request, log, data) {
stringToSign, stringToSign,
algo, algo,
}, },
},
}; };
} };
module.exports = { check }; module.exports = queryAuthCheck;

View File

@ -4,8 +4,8 @@ const headerAuthCheck = require('./headerAuthCheck');
const queryAuthCheck = require('./queryAuthCheck'); const queryAuthCheck = require('./queryAuthCheck');
const authV4 = { const authV4 = {
header: headerAuthCheck, headerAuthCheck,
query: queryAuthCheck, queryAuthCheck,
}; };
module.exports = authV4; module.exports = authV4;

View File

@ -8,6 +8,8 @@ const convertUTCtoISO8601 = require('./timeUtils').convertUTCtoISO8601;
const extractAuthItems = require('./validateInputs').extractAuthItems; const extractAuthItems = require('./validateInputs').extractAuthItems;
const validateCredentials = require('./validateInputs').validateCredentials; const validateCredentials = require('./validateInputs').validateCredentials;
const headerAuthCheck = {};
/** /**
* V4 header auth check * V4 header auth check
* @param {object} request - HTTP request object * @param {object} request - HTTP request object
@ -17,7 +19,7 @@ const validateCredentials = require('./validateInputs').validateCredentials;
* @param {string} awsService - Aws service ('iam' or 's3') * @param {string} awsService - Aws service ('iam' or 's3')
* @return {callback} calls callback * @return {callback} calls callback
*/ */
function check(request, log, data, awsService) { headerAuthCheck.check = (request, log, data, awsService) => {
log.trace('running header auth check'); log.trace('running header auth check');
// authorization header // authorization header
const authHeader = request.headers.authorization; const authHeader = request.headers.authorization;
@ -106,7 +108,6 @@ function check(request, log, data, awsService) {
} }
return { return {
err: null, err: null,
params: {
version: 4, version: 4,
data: { data: {
accessKey, accessKey,
@ -115,8 +116,7 @@ function check(request, log, data, awsService) {
scopeDate, scopeDate,
stringToSign, stringToSign,
}, },
},
}; };
} };
module.exports = { check }; module.exports = headerAuthCheck;

View File

@ -7,6 +7,8 @@ const checkTimeSkew = require('./timeUtils').checkTimeSkew;
const validateCredentials = require('./validateInputs').validateCredentials; const validateCredentials = require('./validateInputs').validateCredentials;
const extractQueryParams = require('./validateInputs').extractQueryParams; const extractQueryParams = require('./validateInputs').extractQueryParams;
const queryAuthCheck = {};
/** /**
* V4 query auth check * V4 query auth check
* @param {object} request - HTTP request object * @param {object} request - HTTP request object
@ -14,7 +16,7 @@ const extractQueryParams = require('./validateInputs').extractQueryParams;
* @param {object} data - Contain authentification params (GET or POST data) * @param {object} data - Contain authentification params (GET or POST data)
* @return {callback} calls callback * @return {callback} calls callback
*/ */
function check(request, log, data) { queryAuthCheck.check = (request, log, data) => {
const authParams = extractQueryParams(data, log); const authParams = extractQueryParams(data, log);
if (Object.keys(authParams).length !== 5) { if (Object.keys(authParams).length !== 5) {
@ -73,7 +75,6 @@ function check(request, log, data) {
log.trace('constructed stringToSign', { stringToSign }); log.trace('constructed stringToSign', { stringToSign });
return { return {
err: null, err: null,
params: {
version: 4, version: 4,
data: { data: {
accessKey, accessKey,
@ -82,8 +83,7 @@ function check(request, log, data) {
scopeDate, scopeDate,
stringToSign, stringToSign,
}, },
},
}; };
} };
module.exports = { check }; module.exports = queryAuthCheck;

View File

@ -1,5 +1,7 @@
'use strict'; // eslint-disable-line strict 'use strict'; // eslint-disable-line strict
const errors = require('../../index').errors;
const AuthInfo = require('./AuthInfo'); const AuthInfo = require('./AuthInfo');
const backend = require('./in_memory/backend'); const backend = require('./in_memory/backend');
@ -31,55 +33,109 @@ const vault = {};
/** /**
* authenticateV2Request * authenticateV2Request
* *
* @param {string} params - the authentication parameters as returned by * @param {string} accessKey - user's accessKey
* auth.extractParams * @param {string} signatureFromRequest - signature sent with request
* @param {number} params.version - shall equal 4 * @param {string} stringToSign - string to sign built per AWS rules
* @param {string} params.data.accessKey - the user's accessKey * @param {string} algo - either SHA256 or SHA1
* @param {string} params.data.signatureFromRequest - the signature read from * @param {object} log - Werelogs logger
* the request
* @param {string} params.data.stringToSign - the stringToSign
* @param {string} params.data.log - the logger object
* @param {function} callback - callback with either error or user info * @param {function} callback - callback with either error or user info
* @return {undefined} * @return {undefined}
*/ */
vault.authenticateV2Request = (params, callback) => { vault.authenticateV2Request = (accessKey, signatureFromRequest,
params.log.debug('authenticating V2 request'); stringToSign, algo, log, callback) => {
client.verifySignatureV2( log.debug('authenticating V2 request');
params.data.stringToSign, client.verifySignatureV2(stringToSign, signatureFromRequest, accessKey,
params.data.signatureFromRequest, { algo, reqUid: log.getSerializedUids() },
params.data.accessKey, (err, userInfo) => vaultSignatureCb(err, userInfo, log, callback));
{ algo: params.data.algo, reqUid: params.log.getSerializedUids() },
(err, userInfo) => vaultSignatureCb(err, userInfo,
params.log, callback)
);
}; };
/** authenticateV4Request /** authenticateV4Request
* @param {object} params - the authentication parameters as returned by * @param {object} params - contains accessKey (string),
* auth.extractParams * signatureFromRequest (string), region (string),
* @param {number} params.version - shall equal 4 * stringToSign (string) and log (object)
* @param {string} params.data.accessKey - the user's accessKey
* @param {string} params.data.signatureFromRequest - the signature read from
* the request
* @param {string} params.data.region - the AWS region
* @param {string} params.data.stringToSign - the stringToSign
* @param {string} params.data.scopeDate - the timespan to allow the request
* @param {string} params.data.log - the logger object
* @param {function} callback - callback with either error or user info * @param {function} callback - callback with either error or user info
* @return {undefined} * @return {undefined}
*/ */
vault.authenticateV4Request = (params, callback) => { vault.authenticateV4Request = (params, callback) => {
params.log.debug('authenticating V4 request'); const accessKey = params.accessKey;
client.verifySignatureV4( const signatureFromRequest = params.signatureFromRequest;
params.data.stringToSign, const region = params.region;
params.data.signatureFromRequest, const scopeDate = params.scopeDate;
params.data.accessKey, const stringToSign = params.stringToSign;
params.data.region, const log = params.log;
params.data.scopeDate,
{ reqUid: params.log.getSerializedUids() }, log.debug('authenticating V4 request');
(err, userInfo) => vaultSignatureCb(err, userInfo, client.verifySignatureV4(stringToSign, signatureFromRequest,
params.log, callback) accessKey, region, scopeDate, { reqUid: log.getSerializedUids() },
); (err, userInfo) => vaultSignatureCb(err, userInfo, log, callback));
};
/** getCanonicalIds -- call Vault to get canonicalIDs based on email addresses
* @param {array} emailAddresses - list of emailAddresses
* @param {object} log - log object
* @param {function} callback - callback with either error or an array
* of objects with each object containing the canonicalID and emailAddress
* of an account as properties
* @return {undefined}
*/
vault.getCanonicalIds = (emailAddresses, log, callback) => {
log.trace('getting canonicalIDs from Vault based on emailAddresses',
{ emailAddresses });
client.getCanonicalIds(emailAddresses, { reqUid: log.getSerializedUids() },
(err, info) => {
if (err) {
log.error('received error message from vault',
{ errorMessage: err });
return callback(err);
}
const infoFromVault = info.message.body;
log.trace('info received from vault', { infoFromVault });
const foundIds = [];
for (let i = 0; i < Object.keys(infoFromVault).length; i++) {
const key = Object.keys(infoFromVault)[i];
if (infoFromVault[key] === 'WrongFormat'
|| infoFromVault[key] === 'NotFound') {
return callback(errors.UnresolvableGrantByEmailAddress);
}
const obj = {};
obj.email = key;
obj.canonicalID = infoFromVault[key];
foundIds.push(obj);
}
return callback(null, foundIds);
});
};
/** getEmailAddresses -- call Vault to get email addresses based on canonicalIDs
* @param {array} canonicalIDs - list of canonicalIDs
* @param {object} log - log object
* @param {function} callback - callback with either error or an object
* with canonicalID keys and email address values
* @return {undefined}
*/
vault.getEmailAddresses = (canonicalIDs, log, callback) => {
log.trace('getting emailAddresses from Vault based on canonicalIDs',
{ canonicalIDs });
client.getEmailAddresses(canonicalIDs, { reqUid: log.getSerializedUids() },
(err, info) => {
if (err) {
log.error('received error message from vault',
{ errorMessage: err });
return callback(err);
}
const infoFromVault = info.message.body;
log.trace('info received from vault', { infoFromVault });
const result = {};
/* If the email address was not found in Vault, do not
send the canonicalID back to the API */
Object.keys(infoFromVault).forEach(key => {
if (infoFromVault[key] !== 'NotFound' &&
infoFromVault[key] !== 'WrongFormat') {
result[key] = infoFromVault[key];
}
});
return callback(null, result);
});
}; };
module.exports = vault; module.exports = vault;

View File

@ -6,7 +6,7 @@ const errors = require('../../../../lib/errors');
const auth = require('../../../../lib/auth/auth'); const auth = require('../../../../lib/auth/auth');
const DummyRequestLogger = require('../../helpers').DummyRequestLogger; const DummyRequestLogger = require('../../helpers').DummyRequestLogger;
auth.setHandler(require('../../../../lib/auth/vault')); auth.setAuthHandler(require('../../../../lib/auth/vault'));
const logger = new DummyRequestLogger(); const logger = new DummyRequestLogger();
@ -26,7 +26,7 @@ describe('Error handling in checkAuth', () => {
url: '/bucket', url: '/bucket',
query: {}, query: {},
}; };
auth.server.doAuth(request, logger, err => { auth.doAuth(request, logger, err => {
assert.deepStrictEqual(err, errors.InvalidAccessKeyId); assert.deepStrictEqual(err, errors.InvalidAccessKeyId);
done(); done();
}, 's3', request.query); }, 's3', request.query);
@ -45,7 +45,7 @@ describe('Error handling in checkAuth', () => {
url: '/bucket', url: '/bucket',
}; };
auth.server.doAuth(request, logger, err => { auth.doAuth(request, logger, err => {
assert.deepStrictEqual(err, errors.MissingSecurityHeader); assert.deepStrictEqual(err, errors.MissingSecurityHeader);
done(); done();
}, 's3', request.query); }, 's3', request.query);
@ -66,7 +66,7 @@ describe('Error handling in checkAuth', () => {
}, },
headers: {}, headers: {},
}; };
auth.server.doAuth(request, logger, err => { auth.doAuth(request, logger, err => {
assert.deepStrictEqual(err, errors.RequestTimeTooSkewed); assert.deepStrictEqual(err, errors.RequestTimeTooSkewed);
done(); done();
}, 's3', request.query); }, 's3', request.query);
@ -91,7 +91,7 @@ describe('Error handling in checkAuth', () => {
}, },
headers: { host: 's3.amazonaws.com' }, headers: { host: 's3.amazonaws.com' },
}; };
auth.server.doAuth(request, logger, err => { auth.doAuth(request, logger, err => {
assert.deepStrictEqual(err, errors.SignatureDoesNotMatch); assert.deepStrictEqual(err, errors.SignatureDoesNotMatch);
done(); done();
}, 's3', request.query); }, 's3', request.query);
@ -112,7 +112,7 @@ describe('Error handling in checkAuth', () => {
url: '/bucket', url: '/bucket',
query: {}, query: {},
}; };
auth.server.doAuth(request, logger, err => { auth.doAuth(request, logger, err => {
assert.deepStrictEqual(err, errors.SignatureDoesNotMatch); assert.deepStrictEqual(err, errors.SignatureDoesNotMatch);
done(); done();
}, 's3', request.query); }, 's3', request.query);
@ -133,7 +133,7 @@ describe('Error handling in checkAuth', () => {
url: '/bucket', url: '/bucket',
query: {}, query: {},
}; };
auth.server.doAuth(request, logger, err => { auth.doAuth(request, logger, err => {
assert.deepStrictEqual(err, errors.MissingSecurityHeader); assert.deepStrictEqual(err, errors.MissingSecurityHeader);
done(); done();
}, 's3', request.query); }, 's3', request.query);

View File

@ -3,7 +3,7 @@
const assert = require('assert'); const assert = require('assert');
const errors = require('../../../../lib/errors'); const errors = require('../../../../lib/errors');
const auth = require('../../../../lib/auth/auth').server.doAuth; const auth = require('../../../../lib/auth/auth').doAuth;
const AuthInfo = require('../../../../lib/auth/AuthInfo'); const AuthInfo = require('../../../../lib/auth/AuthInfo');
const constants = require('../../../../lib/constants'); const constants = require('../../../../lib/constants');
const DummyRequestLogger = require('../../helpers.js').DummyRequestLogger; const DummyRequestLogger = require('../../helpers.js').DummyRequestLogger;

View File

@ -199,7 +199,7 @@ describe('v4 headerAuthCheck', () => {
const res = headerAuthCheck(request, log); const res = headerAuthCheck(request, log);
clock.uninstall(); clock.uninstall();
assert.strictEqual(res.err, null); assert.strictEqual(res.err, null);
assert.strictEqual(res.params.version, 4); assert.strictEqual(res.version, 4);
done(); done();
}); });
}); });

View File

@ -167,7 +167,7 @@ describe('v4 queryAuthCheck', () => {
const res = queryAuthCheck(request, log, request.query); const res = queryAuthCheck(request, log, request.query);
clock.uninstall(); clock.uninstall();
assert.deepStrictEqual(res.err, null); assert.deepStrictEqual(res.err, null);
assert.strictEqual(res.params.version, 4); assert.strictEqual(res.version, 4);
done(); done();
}); });
}); });