Compare commits

..

3 Commits

Author SHA1 Message Date
David Pineau 9a5fe5a9f6 Rename checkSignature into more relevant name
- Renames checkSignature into extractParams, as this function mostly extracts
the parameters from either the headers or the querystring to identify what kind
of auth to use
- Renames the data field of the returned data by extractParams into 'params',
as those are the real parameters for the signature check function that will
soon be extracted from the auth code.
- Simplifies the doAuth function as a first step towards its disappearance
2016-07-05 16:57:05 +02:00
David Pineau 52c717545e Cleanup checkSignature as prep step for API cleanup
Provides the following:
 - Reduces redundancy by removing known information from module and function
 names
 - Simplifies the way the check functions are exported in either auth scheme
 - Rework the code flow in checkSignature:
   - Now easier to follow (logic now appears through the code as a two-step
                           operation)
   - Improved logging (now only provides a few common messages with variable
                       parameters)
2016-07-05 12:48:58 +02:00
David Pineau 4531ed1ac8 [ci-skip] Re-structure the API into client/server 2016-07-05 11:33:14 +02:00
12 changed files with 241 additions and 261 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 authV2 = require('./v2/authV2'); const v2 = require('./v2/authV2');
const authV4 = require('./v4/authV4'); const v4 = 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,10 +13,14 @@ const vaultUtilities = require('./in_memory/vaultUtilities');
let vault = null; let vault = null;
const auth = {}; const auth = {};
auth.setAuthHandler = handler => { // If no auth information is provided in request, then user is part of
// '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
@ -26,69 +30,92 @@ auth.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
*/ */
auth.check = (request, log, awsService, data) => { function extractParams(request, log, awsService, data) {
log.debug('running auth checks', { method: 'auth' }); log.trace('extracting authentication signature informations');
const authHeader = request.headers.authorization; const authHeader = request.headers.authorization;
// Check whether signature is in header const checkFunctions = {
if (authHeader) { v2: {
log.trace('authorization header', { authHeader }); headers: v2.header.check,
// TODO: Check for security token header to query: v2.query.check,
// handle temporary security credentials },
if (authHeader.startsWith('AWS ')) { v4: {
log.trace('authenticating request with auth v2 using headers'); headers: v4.header.check,
return authV2.headerAuthCheck.check(request, log, data); query: v4.query.check,
} else if (authHeader.startsWith('AWS4')) { },
log.trace('authenticating request with Auth V4 using headers');
return authV4.headerAuthCheck.check(request, log, data, awsService);
}
log.debug('missing authorization security header');
return { err: errors.MissingSecurityHeader };
} else if (data.Signature) {
// Check whether signature is in query string
log.trace('authenticating request with auth v2 using query string');
return authV2.queryAuthCheck.check(request, log, data);
} else if (data['X-Amz-Algorithm']) {
log.trace('authenticating request with Auth v4 using query string');
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 };
}; };
let version = null;
let method = null;
auth.doAuth = (request, log, cb, awsService, data) => { // Identify auth version and method to dispatch to the right check function
const res = auth.check(request, log, awsService, data); if (authHeader) {
method = 'headers';
// TODO: Check for security token header to handle temporary security
// credentials
if (authHeader.startsWith('AWS ')) {
version = 'v2';
} else if (authHeader.startsWith('AWS4')) {
version = 'v4';
} else {
log.trace('missing authorization security header',
{ header: authHeader });
return { err: errors.MissingSecurityHeader };
}
} else if (data.Signature) {
method = 'query';
version = 'v2';
} else if (data['X-Amz-Algorithm']) {
method = 'query';
version = 'v4';
}
// Here, either both values are set, or none is set
if (version !== null && method !== null) {
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.version === 2) { } else if (res.params instanceof AuthInfo) {
return vault.authenticateV2Request(res.data.accessKey, return cb(null, res.params);
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
} else if (res.version === 4) { res.params.log = log;
res.data.log = log; if (res.params.version === 2) {
return vault.authenticateV4Request(res.data, cb, awsService); return vault.authenticateV2Request(res.params, cb);
} else if (res.data instanceof AuthInfo) { } else if (res.params.version === 4) {
return cb(null, res.data); return vault.authenticateV4Request(res.params, cb, awsService);
} }
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);
}; }
auth.generateV4Headers = function generateV4Headers(request, data, accessKey, secretKeyValue,
(request, data, accessKey, secretKeyValue, awsService) => { 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
@ -135,6 +162,15 @@ auth.generateV4Headers =
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 = auth; module.exports = {
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 queryAuthCheck = require('./queryAuthCheck');
const headerAuthCheck = require('./headerAuthCheck'); const headerAuthCheck = require('./headerAuthCheck');
const queryAuthCheck = require('./queryAuthCheck');
const authV2 = { const authV2 = {
headerAuthCheck, header: headerAuthCheck,
queryAuthCheck, query: queryAuthCheck,
}; };
module.exports = authV2; module.exports = authV2;

View File

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

View File

@ -6,9 +6,7 @@ const algoCheck = require('./algoCheck');
const constructStringToSign = require('./constructStringToSign'); const constructStringToSign = require('./constructStringToSign');
const checkRequestExpiry = require('./checkRequestExpiry'); const checkRequestExpiry = require('./checkRequestExpiry');
const queryAuthCheck = {}; function check(request, log, data) {
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');
@ -49,6 +47,7 @@ queryAuthCheck.check = (request, log, data) => {
} }
return { return {
err: null, err: null,
params: {
version: 2, version: 2,
data: { data: {
accessKey, accessKey,
@ -56,7 +55,8 @@ queryAuthCheck.check = (request, log, data) => {
stringToSign, stringToSign,
algo, algo,
}, },
},
}; };
}; }
module.exports = queryAuthCheck; module.exports = { check };

View File

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

View File

@ -8,8 +8,6 @@ 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
@ -19,7 +17,7 @@ const headerAuthCheck = {};
* @param {string} awsService - Aws service ('iam' or 's3') * @param {string} awsService - Aws service ('iam' or 's3')
* @return {callback} calls callback * @return {callback} calls callback
*/ */
headerAuthCheck.check = (request, log, data, awsService) => { function 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;
@ -108,6 +106,7 @@ headerAuthCheck.check = (request, log, data, awsService) => {
} }
return { return {
err: null, err: null,
params: {
version: 4, version: 4,
data: { data: {
accessKey, accessKey,
@ -116,7 +115,8 @@ headerAuthCheck.check = (request, log, data, awsService) => {
scopeDate, scopeDate,
stringToSign, stringToSign,
}, },
},
}; };
}; }
module.exports = headerAuthCheck; module.exports = { check };

View File

@ -7,8 +7,6 @@ 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
@ -16,7 +14,7 @@ const queryAuthCheck = {};
* @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
*/ */
queryAuthCheck.check = (request, log, data) => { function 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) {
@ -75,6 +73,7 @@ queryAuthCheck.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,
@ -83,7 +82,8 @@ queryAuthCheck.check = (request, log, data) => {
scopeDate, scopeDate,
stringToSign, stringToSign,
}, },
},
}; };
}; }
module.exports = queryAuthCheck; module.exports = { check };

View File

@ -1,7 +1,5 @@
'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');
@ -33,109 +31,55 @@ const vault = {};
/** /**
* authenticateV2Request * authenticateV2Request
* *
* @param {string} accessKey - user's accessKey * @param {string} params - the authentication parameters as returned by
* @param {string} signatureFromRequest - signature sent with request * auth.extractParams
* @param {string} stringToSign - string to sign built per AWS rules * @param {number} params.version - shall equal 4
* @param {string} algo - either SHA256 or SHA1 * @param {string} params.data.accessKey - the user's accessKey
* @param {object} log - Werelogs logger * @param {string} params.data.signatureFromRequest - the signature read from
* 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 = (accessKey, signatureFromRequest, vault.authenticateV2Request = (params, callback) => {
stringToSign, algo, log, callback) => { params.log.debug('authenticating V2 request');
log.debug('authenticating V2 request'); client.verifySignatureV2(
client.verifySignatureV2(stringToSign, signatureFromRequest, accessKey, params.data.stringToSign,
{ algo, reqUid: log.getSerializedUids() }, params.data.signatureFromRequest,
(err, userInfo) => vaultSignatureCb(err, userInfo, log, callback)); params.data.accessKey,
{ algo: params.data.algo, reqUid: params.log.getSerializedUids() },
(err, userInfo) => vaultSignatureCb(err, userInfo,
params.log, callback)
);
}; };
/** authenticateV4Request /** authenticateV4Request
* @param {object} params - contains accessKey (string), * @param {object} params - the authentication parameters as returned by
* signatureFromRequest (string), region (string), * auth.extractParams
* stringToSign (string) and log (object) * @param {number} params.version - shall equal 4
* @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) => {
const accessKey = params.accessKey; params.log.debug('authenticating V4 request');
const signatureFromRequest = params.signatureFromRequest; client.verifySignatureV4(
const region = params.region; params.data.stringToSign,
const scopeDate = params.scopeDate; params.data.signatureFromRequest,
const stringToSign = params.stringToSign; params.data.accessKey,
const log = params.log; params.data.region,
params.data.scopeDate,
log.debug('authenticating V4 request'); { reqUid: params.log.getSerializedUids() },
client.verifySignatureV4(stringToSign, signatureFromRequest, (err, userInfo) => vaultSignatureCb(err, userInfo,
accessKey, region, scopeDate, { reqUid: log.getSerializedUids() }, params.log, callback)
(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.setAuthHandler(require('../../../../lib/auth/vault')); auth.setHandler(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.doAuth(request, logger, err => { auth.server.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.doAuth(request, logger, err => { auth.server.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.doAuth(request, logger, err => { auth.server.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.doAuth(request, logger, err => { auth.server.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.doAuth(request, logger, err => { auth.server.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.doAuth(request, logger, err => { auth.server.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').doAuth; const auth = require('../../../../lib/auth/auth').server.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.version, 4); assert.strictEqual(res.params.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.version, 4); assert.strictEqual(res.params.version, 4);
done(); done();
}); });
}); });