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 queryString = require('querystring');
const AuthInfo = require('./AuthInfo');
const authV2 = require('./v2/authV2');
const authV4 = require('./v4/authV4');
const v2 = require('./v2/authV2');
const v4 = require('./v4/authV4');
const constants = require('../constants');
const constructStringToSignV4 = require('./v4/constructStringToSign');
const convertUTCtoISO8601 = require('./v4/timeUtils').convertUTCtoISO8601;
@ -13,10 +13,14 @@ const vaultUtilities = require('./in_memory/vaultUtilities');
let vault = null;
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;
return auth;
};
}
/**
* This function will check validity of request parameters to authenticate
@ -26,115 +30,147 @@ auth.setAuthHandler = handler => {
* @param {string} awsService - Aws service related
* @param {object} data - Parameters from queryString parsing or body of
* 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) => {
log.debug('running auth checks', { method: 'auth' });
function extractParams(request, log, awsService, data) {
log.trace('extracting authentication signature informations');
const authHeader = request.headers.authorization;
// Check whether signature is in header
if (authHeader) {
log.trace('authorization header', { authHeader });
// TODO: Check for security token header to
// handle temporary security credentials
if (authHeader.startsWith('AWS ')) {
log.trace('authenticating request with auth v2 using headers');
return authV2.headerAuthCheck.check(request, log, data);
} 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 };
};
const checkFunctions = {
v2: {
headers: v2.header.check,
query: v2.query.check,
},
v4: {
headers: v4.header.check,
query: v4.query.check,
},
};
let version = null;
let method = null;
auth.doAuth = (request, log, cb, awsService, data) => {
const res = auth.check(request, log, awsService, data);
// Identify auth version and method to dispatch to the right check function
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) {
return cb(res.err);
} else if (res.version === 2) {
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);
});
} else if (res.version === 4) {
res.data.log = log;
return vault.authenticateV4Request(res.data, cb, awsService);
} else if (res.data instanceof AuthInfo) {
return cb(null, res.data);
} else if (res.params instanceof AuthInfo) {
return cb(null, res.params);
}
// Corner cases managed, we're left with normal auth
res.params.log = log;
if (res.params.version === 2) {
return vault.authenticateV2Request(res.params, cb);
} else if (res.params.version === 4) {
return vault.authenticateV4Request(res.params, cb, awsService);
}
log.error('authentication method not found', {
method: 'Arsenal.auth.doAuth',
});
return cb(errors.InternalError);
}
function generateV4Headers(request, data, accessKey, secretKeyValue,
awsService) {
Object.assign(request, { headers: {} });
const amzDate = convertUTCtoISO8601(Date.now());
// get date without time
const scopeDate = amzDate.slice(0, amzDate.indexOf('T'));
const signedHeaders = 'host;x-amz-date;x-amz-content-sha256';
const region = 'us-east-1';
const service = awsService || 'iam';
const credentialScope =
`${scopeDate}/${region}/${service}/aws4_request`;
const timestamp = amzDate;
const algorithm = 'AWS4-HMAC-SHA256';
let payload = '';
if (request.method === 'POST') {
payload = queryString.stringify(data, null, null, {
encodeURIComponent,
});
}
const payloadChecksum = crypto.createHash('sha256').update(payload)
.digest('hex');
const path = request.path;
Object.assign(request, { path: '/' });
request.setHeader('host', request._headers.host);
request.setHeader('x-amz-date', amzDate);
request.setHeader('x-amz-content-sha256', payloadChecksum);
Object.assign(request.headers, { host: request.getHeader('host') });
Object.assign(request.headers, { 'x-amz-date': amzDate });
Object.assign(request.headers,
{ 'x-amz-content-sha256': payloadChecksum });
const params = { request, signedHeaders, payloadChecksum,
credentialScope, timestamp, query: data,
awsService: service };
const stringToSign = constructStringToSignV4(params);
const signingKey = vaultUtilities.calculateSigningKey(secretKeyValue,
region,
scopeDate,
service);
const signature = crypto.createHmac('sha256', signingKey)
.update(stringToSign).digest('hex');
const authorizationHeader = `${algorithm} Credential=${accessKey}` +
`/${credentialScope}, SignedHeaders=${signedHeaders}, ` +
`Signature=${signature}`;
request.setHeader('authorization', authorizationHeader);
Object.assign(request, { headers: {} });
Object.assign(request, { path });
}
module.exports = {
setHandler: setAuthHandler,
server: {
extractParams,
doAuth,
},
client: {
generateV4Headers,
},
};
auth.generateV4Headers =
(request, data, accessKey, secretKeyValue, awsService) => {
Object.assign(request, { headers: {} });
const amzDate = convertUTCtoISO8601(Date.now());
// get date without time
const scopeDate = amzDate.slice(0, amzDate.indexOf('T'));
const signedHeaders = 'host;x-amz-date;x-amz-content-sha256';
const region = 'us-east-1';
const service = awsService || 'iam';
const credentialScope =
`${scopeDate}/${region}/${service}/aws4_request`;
const timestamp = amzDate;
const algorithm = 'AWS4-HMAC-SHA256';
let payload = '';
if (request.method === 'POST') {
payload = queryString.stringify(data, null, null, {
encodeURIComponent,
});
}
const payloadChecksum = crypto.createHash('sha256').update(payload)
.digest('hex');
const path = request.path;
Object.assign(request, { path: '/' });
request.setHeader('host', request._headers.host);
request.setHeader('x-amz-date', amzDate);
request.setHeader('x-amz-content-sha256', payloadChecksum);
Object.assign(request.headers, { host: request.getHeader('host') });
Object.assign(request.headers, { 'x-amz-date': amzDate });
Object.assign(request.headers,
{ 'x-amz-content-sha256': payloadChecksum });
const params = { request, signedHeaders, payloadChecksum,
credentialScope, timestamp, query: data,
awsService: service };
const stringToSign = constructStringToSignV4(params);
const signingKey = vaultUtilities.calculateSigningKey(secretKeyValue,
region,
scopeDate,
service);
const signature = crypto.createHmac('sha256', signingKey)
.update(stringToSign).digest('hex');
const authorizationHeader = `${algorithm} Credential=${accessKey}` +
`/${credentialScope}, SignedHeaders=${signedHeaders}, ` +
`Signature=${signature}`;
request.setHeader('authorization', authorizationHeader);
Object.assign(request, { headers: {} });
Object.assign(request, { path });
};
module.exports = auth;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,5 @@
'use strict'; // eslint-disable-line strict
const errors = require('../../index').errors;
const AuthInfo = require('./AuthInfo');
const backend = require('./in_memory/backend');
@ -33,109 +31,55 @@ const vault = {};
/**
* authenticateV2Request
*
* @param {string} accessKey - user's accessKey
* @param {string} signatureFromRequest - signature sent with request
* @param {string} stringToSign - string to sign built per AWS rules
* @param {string} algo - either SHA256 or SHA1
* @param {object} log - Werelogs logger
* @param {string} params - the authentication parameters as returned by
* auth.extractParams
* @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.stringToSign - the stringToSign
* @param {string} params.data.log - the logger object
* @param {function} callback - callback with either error or user info
* @return {undefined}
*/
vault.authenticateV2Request = (accessKey, signatureFromRequest,
stringToSign, algo, log, callback) => {
log.debug('authenticating V2 request');
client.verifySignatureV2(stringToSign, signatureFromRequest, accessKey,
{ algo, reqUid: log.getSerializedUids() },
(err, userInfo) => vaultSignatureCb(err, userInfo, log, callback));
vault.authenticateV2Request = (params, callback) => {
params.log.debug('authenticating V2 request');
client.verifySignatureV2(
params.data.stringToSign,
params.data.signatureFromRequest,
params.data.accessKey,
{ algo: params.data.algo, reqUid: params.log.getSerializedUids() },
(err, userInfo) => vaultSignatureCb(err, userInfo,
params.log, callback)
);
};
/** authenticateV4Request
* @param {object} params - contains accessKey (string),
* signatureFromRequest (string), region (string),
* stringToSign (string) and log (object)
* @param {object} params - the authentication parameters as returned by
* auth.extractParams
* @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
* @return {undefined}
*/
vault.authenticateV4Request = (params, callback) => {
const accessKey = params.accessKey;
const signatureFromRequest = params.signatureFromRequest;
const region = params.region;
const scopeDate = params.scopeDate;
const stringToSign = params.stringToSign;
const log = params.log;
log.debug('authenticating V4 request');
client.verifySignatureV4(stringToSign, signatureFromRequest,
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);
});
params.log.debug('authenticating V4 request');
client.verifySignatureV4(
params.data.stringToSign,
params.data.signatureFromRequest,
params.data.accessKey,
params.data.region,
params.data.scopeDate,
{ reqUid: params.log.getSerializedUids() },
(err, userInfo) => vaultSignatureCb(err, userInfo,
params.log, callback)
);
};
module.exports = vault;

View File

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

View File

@ -3,7 +3,7 @@
const assert = require('assert');
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 constants = require('../../../../lib/constants');
const DummyRequestLogger = require('../../helpers.js').DummyRequestLogger;

View File

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

View File

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