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 queryString = require('querystring');
const AuthInfo = require('./AuthInfo');
const v2 = require('./v2/authV2');
const v4 = require('./v4/authV4');
const authV2 = require('./v2/authV2');
const authV4 = require('./v4/authV4');
const constants = require('../constants');
const constructStringToSignV4 = require('./v4/constructStringToSign');
const convertUTCtoISO8601 = require('./v4/timeUtils').convertUTCtoISO8601;
@ -13,14 +13,10 @@ const vaultUtilities = require('./in_memory/vaultUtilities');
let vault = null;
const auth = {};
// 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) {
auth.setAuthHandler = handler => {
vault = handler;
return auth;
}
};
/**
* This function will check validity of request parameters to authenticate
@ -30,147 +26,115 @@ function setAuthHandler(handler) {
* @param {string} awsService - Aws service related
* @param {object} data - Parameters from queryString parsing or body of
* POST request
*
* @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
* @return {object} Return object with information to authenticate
*/
function extractParams(request, log, awsService, data) {
log.trace('extracting authentication signature informations');
auth.check = (request, log, awsService, data) => {
log.debug('running auth checks', { method: 'auth' });
const authHeader = request.headers.authorization;
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;
// Identify auth version and method to dispatch to the right check function
// Check whether signature is in header
if (authHeader) {
method = 'headers';
// TODO: Check for security token header to handle temporary security
// credentials
log.trace('authorization header', { authHeader });
// TODO: Check for security token header to
// handle temporary security credentials
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')) {
version = 'v4';
} else {
log.trace('missing authorization security header',
{ header: authHeader });
return { err: errors.MissingSecurityHeader };
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) {
method = 'query';
version = 'v2';
// 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']) {
method = 'query';
version = 'v4';
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 };
};
// 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);
auth.doAuth = (request, log, cb, awsService, data) => {
const res = auth.check(request, log, awsService, data);
if (res.err) {
return cb(res.err);
} else if (res.params instanceof AuthInfo) {
return cb(null, res.params);
} 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);
}
// 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 headerAuthCheck = require('./headerAuthCheck');
const queryAuthCheck = require('./queryAuthCheck');
const headerAuthCheck = require('./headerAuthCheck');
const authV2 = {
header: headerAuthCheck,
query: queryAuthCheck,
headerAuthCheck,
queryAuthCheck,
};
module.exports = authV2;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
'use strict'; // eslint-disable-line strict
const errors = require('../../index').errors;
const AuthInfo = require('./AuthInfo');
const backend = require('./in_memory/backend');
@ -31,55 +33,109 @@ const vault = {};
/**
* authenticateV2Request
*
* @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 {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 {function} callback - callback with either error or user info
* @return {undefined}
*/
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)
);
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));
};
/** authenticateV4Request
* @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 {object} params - contains accessKey (string),
* signatureFromRequest (string), region (string),
* stringToSign (string) and log (object)
* @param {function} callback - callback with either error or user info
* @return {undefined}
*/
vault.authenticateV4Request = (params, callback) => {
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)
);
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);
});
};
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.setHandler(require('../../../../lib/auth/vault'));
auth.setAuthHandler(require('../../../../lib/auth/vault'));
const logger = new DummyRequestLogger();
@ -26,7 +26,7 @@ describe('Error handling in checkAuth', () => {
url: '/bucket',
query: {},
};
auth.server.doAuth(request, logger, err => {
auth.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.server.doAuth(request, logger, err => {
auth.doAuth(request, logger, err => {
assert.deepStrictEqual(err, errors.MissingSecurityHeader);
done();
}, 's3', request.query);
@ -66,7 +66,7 @@ describe('Error handling in checkAuth', () => {
},
headers: {},
};
auth.server.doAuth(request, logger, err => {
auth.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.server.doAuth(request, logger, err => {
auth.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.server.doAuth(request, logger, err => {
auth.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.server.doAuth(request, logger, err => {
auth.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').server.doAuth;
const auth = require('../../../../lib/auth/auth').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.params.version, 4);
assert.strictEqual(res.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.params.version, 4);
assert.strictEqual(res.version, 4);
done();
});
});