Compare commits

...

10 Commits

Author SHA1 Message Date
Guillaume Hivert 8400204d95 Revert rename of validate-auth-config 2022-03-03 16:48:17 +01:00
Guillaume Hivert 0d057fb2d0 Revert rename of vault-utilities 2022-03-03 16:48:07 +01:00
Guillaume Hivert 86c8c77dd2 Type auth/Vault 2022-03-03 16:40:49 +01:00
Guillaume Hivert 52284a871a Type auth/auth 2022-03-03 16:40:43 +01:00
Guillaume Hivert a2d3dfeb21 Type auth/v4 2022-03-03 16:40:33 +01:00
Guillaume Hivert 73e0150612 Type auth/v2 2022-03-03 16:40:18 +01:00
Guillaume Hivert 89d5970aac Type AuthInfo 2022-03-03 14:03:44 +01:00
Guillaume Hivert 8f4870f5a2 Type ChainBackend 2022-03-03 14:03:36 +01:00
Guillaume Hivert 11a9032411 Type BaseBackend 2022-03-03 14:03:27 +01:00
Guillaume Hivert 8e11405cf7 Improve Backend 2022-03-03 14:03:20 +01:00
26 changed files with 549 additions and 372 deletions

View File

@ -6,16 +6,15 @@ import * as constants from '../constants';
* shortid, email, accountDisplayName and IAMdisplayName (if applicable) * shortid, email, accountDisplayName and IAMdisplayName (if applicable)
* @return {AuthInfo} an AuthInfo instance * @return {AuthInfo} an AuthInfo instance
*/ */
export default class AuthInfo { export default class AuthInfo {
arn arn: string;
canonicalID canonicalID: string;
shortid shortid: string;
email email: string;
accountDisplayName accountDisplayName: string;
IAMdisplayName IAMdisplayName: string;
constructor(objectFromVault) { constructor(objectFromVault: any) {
// amazon resource name for IAM user (if applicable) // amazon resource name for IAM user (if applicable)
this.arn = objectFromVault.arn; this.arn = objectFromVault.arn;
// account canonicalID // account canonicalID
@ -57,10 +56,8 @@ export default class AuthInfo {
isRequesterAServiceAccount() { isRequesterAServiceAccount() {
return this.canonicalID.startsWith(`${constants.zenkoServiceAccount}/`); return this.canonicalID.startsWith(`${constants.zenkoServiceAccount}/`);
} }
isRequesterThisServiceAccount(serviceName) { isRequesterThisServiceAccount(serviceName: string) {
return ( const computedCanonicalID = `${constants.zenkoServiceAccount}/${serviceName}`;
this.canonicalID === return this.canonicalID === computedCanonicalID;
`${constants.zenkoServiceAccount}/${serviceName}`
);
} }
} }

View File

@ -1,16 +1,22 @@
import { Logger } from 'werelogs';
import errors from '../errors'; import errors from '../errors';
import AuthInfo from './AuthInfo'; import AuthInfo from './AuthInfo';
/** vaultSignatureCb parses message from Vault and instantiates /** vaultSignatureCb parses message from Vault and instantiates
* @param {object} err - error from vault * @param err - error from vault
* @param {object} authInfo - info from vault * @param authInfo - info from vault
* @param {object} log - log for request * @param log - log for request
* @param {function} callback - callback to authCheck functions * @param callback - callback to authCheck functions
* @param {object} [streamingV4Params] - present if v4 signature; * @param [streamingV4Params] - present if v4 signature;
* items used to calculate signature on chunks if streaming auth * items used to calculate signature on chunks if streaming auth
* @return {undefined}
*/ */
function vaultSignatureCb(err, authInfo, log, callback, streamingV4Params) { function vaultSignatureCb(
err: Error | null,
authInfo: { message: { body: any } },
log: Logger,
callback: (err: Error | null, data?: any, results?: any, params?: any) => void,
streamingV4Params?: any
) {
// vaultclient API guarantees that it returns: // vaultclient API guarantees that it returns:
// - either `err`, an Error object with `code` and `message` properties set // - either `err`, an Error object with `code` and `message` properties set
// - or `err == null` and `info` is an object with `message.code` and // - or `err == null` and `info` is an object with `message.code` and
@ -24,11 +30,13 @@ function vaultSignatureCb(err, authInfo, log, callback, streamingV4Params) {
const info = authInfo.message.body; const info = authInfo.message.body;
const userInfo = new AuthInfo(info.userInfo); const userInfo = new AuthInfo(info.userInfo);
const authorizationResults = info.authorizationResults; const authorizationResults = info.authorizationResults;
const auditLog = { accountDisplayName: userInfo.getAccountDisplayName() }; const auditLog: { accountDisplayName: string, IAMdisplayName?: string } =
{ accountDisplayName: userInfo.getAccountDisplayName() };
const iamDisplayName = userInfo.getIAMdisplayName(); const iamDisplayName = userInfo.getIAMdisplayName();
if (iamDisplayName) { if (iamDisplayName) {
auditLog.IAMdisplayName = iamDisplayName; auditLog.IAMdisplayName = iamDisplayName;
} }
// @ts-ignore
log.addDefaultFields(auditLog); log.addDefaultFields(auditLog);
return callback(null, userInfo, authorizationResults, streamingV4Params); return callback(null, userInfo, authorizationResults, streamingV4Params);
} }
@ -40,45 +48,62 @@ function vaultSignatureCb(err, authInfo, log, callback, streamingV4Params) {
* @class Vault * @class Vault
*/ */
export default class Vault { export default class Vault {
client client: any;
implName implName: string;
/** /**
* @constructor * @constructor
* @param {object} client - authentication backend or vault client * @param {object} client - authentication backend or vault client
* @param {string} implName - implementation name for auth backend * @param {string} implName - implementation name for auth backend
*/ */
constructor(client, implName) { constructor(client: any, implName: string) {
this.client = client; this.client = client;
this.implName = implName; this.implName = implName;
} }
/** /**
* authenticateV2Request * authenticateV2Request
* *
* @param {string} params - the authentication parameters as returned by * @param params - the authentication parameters as returned by
* auth.extractParams * auth.extractParams
* @param {number} params.version - shall equal 2 * @param params.version - shall equal 2
* @param {string} params.data.accessKey - the user's accessKey * @param params.data.accessKey - the user's accessKey
* @param {string} params.data.signatureFromRequest - the signature read * @param params.data.signatureFromRequest - the signature read
* from the request * from the request
* @param {string} params.data.stringToSign - the stringToSign * @param params.data.stringToSign - the stringToSign
* @param {string} params.data.algo - the hashing algorithm used for the * @param params.data.algo - the hashing algorithm used for the
* signature * signature
* @param {string} params.data.authType - the type of authentication (query * @param params.data.authType - the type of authentication (query
* or header) * or header)
* @param {string} params.data.signatureVersion - the version of the * @param params.data.signatureVersion - the version of the
* signature (AWS or AWS4) * signature (AWS or AWS4)
* @param {number} [params.data.signatureAge] - the age of the signature in * @param [params.data.signatureAge] - the age of the signature in
* ms * ms
* @param {string} params.data.log - the logger object * @param params.data.log - the logger object
* @param {RequestContext []} requestContexts - an array of RequestContext * @param {RequestContext []} requestContexts - an array of RequestContext
* instances which contain information for policy authorization check * instances which contain information for policy authorization check
* @param {function} callback - callback with either error or user info * @param callback - callback with either error or user info
* @returns {undefined}
*/ */
authenticateV2Request(params, requestContexts, callback) { authenticateV2Request(
params: {
version: 2;
log: Logger;
data: {
securityToken: string;
accessKey: string;
signatureFromRequest: string;
stringToSign: string;
algo: string;
authType: 'query' | 'header';
signatureVersion: string;
signatureAge?: number;
log: Logger;
};
},
requestContexts: any[],
callback: (err: Error | null, data?: any) => void
) {
params.log.debug('authenticating V2 request'); params.log.debug('authenticating V2 request');
let serializedRCsArr; let serializedRCsArr: any;
if (requestContexts) { if (requestContexts) {
serializedRCsArr = requestContexts.map(rc => rc.serialize()); serializedRCsArr = requestContexts.map(rc => rc.serialize());
} }
@ -88,44 +113,66 @@ export default class Vault {
params.data.accessKey, params.data.accessKey,
{ {
algo: params.data.algo, algo: params.data.algo,
// @ts-ignore
reqUid: params.log.getSerializedUids(), reqUid: params.log.getSerializedUids(),
logger: params.log, logger: params.log,
securityToken: params.data.securityToken, securityToken: params.data.securityToken,
requestContext: serializedRCsArr, requestContext: serializedRCsArr,
}, },
(err, userInfo) => vaultSignatureCb(err, userInfo, (err: Error | null, userInfo?: any) => vaultSignatureCb(err, userInfo,
params.log, callback), params.log, callback),
); );
} }
/** authenticateV4Request /** authenticateV4Request
* @param {object} params - the authentication parameters as returned by * @param params - the authentication parameters as returned by
* auth.extractParams * auth.extractParams
* @param {number} params.version - shall equal 4 * @param params.version - shall equal 4
* @param {string} params.data.log - the logger object * @param params.data.log - the logger object
* @param {string} params.data.accessKey - the user's accessKey * @param params.data.accessKey - the user's accessKey
* @param {string} params.data.signatureFromRequest - the signature read * @param params.data.signatureFromRequest - the signature read
* from the request * from the request
* @param {string} params.data.region - the AWS region * @param params.data.region - the AWS region
* @param {string} params.data.stringToSign - the stringToSign * @param params.data.stringToSign - the stringToSign
* @param {string} params.data.scopeDate - the timespan to allow the request * @param params.data.scopeDate - the timespan to allow the request
* @param {string} params.data.authType - the type of authentication (query * @param params.data.authType - the type of authentication (query
* or header) * or header)
* @param {string} params.data.signatureVersion - the version of the * @param params.data.signatureVersion - the version of the
* signature (AWS or AWS4) * signature (AWS or AWS4)
* @param {number} params.data.signatureAge - the age of the signature in ms * @param params.data.signatureAge - the age of the signature in ms
* @param {number} params.data.timestamp - signaure timestamp * @param params.data.timestamp - signaure timestamp
* @param {string} params.credentialScope - credentialScope for signature * @param params.credentialScope - credentialScope for signature
* @param {RequestContext [] | null} requestContexts - * @param {RequestContext [] | null} requestContexts -
* an array of RequestContext or null if authenticaiton of a chunk * an array of RequestContext or null if authenticaiton of a chunk
* in streamingv4 auth * in streamingv4 auth
* instances which contain information for policy authorization check * instances which contain information for policy authorization check
* @param {function} callback - callback with either error or user info * @param callback - callback with either error or user info
* @return {undefined}
*/ */
authenticateV4Request(params, requestContexts, callback) { authenticateV4Request(
params: {
version: 4;
log: Logger;
data: {
accessKey: string;
signatureFromRequest: string;
region: string;
stringToSign: string;
scopeDate: string;
authType: 'query' | 'header';
signatureVersion: string;
signatureAge?: number;
timestamp: number;
credentialScope: string;
securityToken: string;
algo: string;
log: Logger;
};
},
requestContexts: any[],
callback: (err: Error | null, data?: any) => void
) {
params.log.debug('authenticating V4 request'); params.log.debug('authenticating V4 request');
let serializedRCs; let serializedRCs: any;
if (requestContexts) { if (requestContexts) {
serializedRCs = requestContexts.map(rc => rc.serialize()); serializedRCs = requestContexts.map(rc => rc.serialize());
} }
@ -143,31 +190,39 @@ export default class Vault {
params.data.region, params.data.region,
params.data.scopeDate, params.data.scopeDate,
{ {
// @ts-ignore
reqUid: params.log.getSerializedUids(), reqUid: params.log.getSerializedUids(),
logger: params.log, logger: params.log,
securityToken: params.data.securityToken, securityToken: params.data.securityToken,
requestContext: serializedRCs, requestContext: serializedRCs,
}, },
(err, userInfo) => vaultSignatureCb(err, userInfo, (err: Error | null, userInfo?: any) => vaultSignatureCb(err, userInfo,
params.log, callback, streamingV4Params), params.log, callback, streamingV4Params),
); );
} }
/** getCanonicalIds -- call Vault to get canonicalIDs based on email /** getCanonicalIds -- call Vault to get canonicalIDs based on email
* addresses * addresses
* @param {array} emailAddresses - list of emailAddresses * @param emailAddresses - list of emailAddresses
* @param {object} log - log object * @param log - log object
* @param {function} callback - callback with either error or an array * @param callback - callback with either error or an array
* of objects with each object containing the canonicalID and emailAddress * of objects with each object containing the canonicalID and emailAddress
* of an account as properties * of an account as properties
* @return {undefined}
*/ */
getCanonicalIds(emailAddresses, log, callback) { getCanonicalIds(
emailAddresses: string[],
log: Logger,
callback: (
err: Error | null,
data?: { canonicalID: string; email: string }[]
) => void
) {
log.trace('getting canonicalIDs from Vault based on emailAddresses', log.trace('getting canonicalIDs from Vault based on emailAddresses',
{ emailAddresses }); { emailAddresses });
this.client.getCanonicalIds(emailAddresses, this.client.getCanonicalIds(emailAddresses,
// @ts-ignore
{ reqUid: log.getSerializedUids() }, { reqUid: log.getSerializedUids() },
(err, info) => { (err: Error | null, info?: any) => {
if (err) { if (err) {
log.debug('received error message from auth provider', log.debug('received error message from auth provider',
{ errorMessage: err }); { errorMessage: err });
@ -175,17 +230,17 @@ export default class Vault {
} }
const infoFromVault = info.message.body; const infoFromVault = info.message.body;
log.trace('info received from vault', { infoFromVault }); log.trace('info received from vault', { infoFromVault });
const foundIds = []; const foundIds: { canonicalID: string; email: string }[] = [];
for (let i = 0; i < Object.keys(infoFromVault).length; i++) { for (let i = 0; i < Object.keys(infoFromVault).length; i++) {
const key = Object.keys(infoFromVault)[i]; const key = Object.keys(infoFromVault)[i];
if (infoFromVault[key] === 'WrongFormat' if (infoFromVault[key] === 'WrongFormat'
|| infoFromVault[key] === 'NotFound') { || infoFromVault[key] === 'NotFound') {
return callback(errors.UnresolvableGrantByEmailAddress); return callback(errors.UnresolvableGrantByEmailAddress);
} }
const obj = {}; foundIds.push({
obj.email = key; email: key,
obj.canonicalID = infoFromVault[key]; canonicalID: infoFromVault[key],
foundIds.push(obj); })
} }
return callback(null, foundIds); return callback(null, foundIds);
}); });
@ -193,18 +248,22 @@ export default class Vault {
/** getEmailAddresses -- call Vault to get email addresses based on /** getEmailAddresses -- call Vault to get email addresses based on
* canonicalIDs * canonicalIDs
* @param {array} canonicalIDs - list of canonicalIDs * @param canonicalIDs - list of canonicalIDs
* @param {object} log - log object * @param log - log object
* @param {function} callback - callback with either error or an object * @param callback - callback with either error or an object
* with canonicalID keys and email address values * with canonicalID keys and email address values
* @return {undefined}
*/ */
getEmailAddresses(canonicalIDs, log, callback) { getEmailAddresses(
canonicalIDs: string[],
log: Logger,
callback: (err: Error | null, data?: { [key: string]: any }) => void
) {
log.trace('getting emailAddresses from Vault based on canonicalIDs', log.trace('getting emailAddresses from Vault based on canonicalIDs',
{ canonicalIDs }); { canonicalIDs });
this.client.getEmailAddresses(canonicalIDs, this.client.getEmailAddresses(canonicalIDs,
// @ts-ignore
{ reqUid: log.getSerializedUids() }, { reqUid: log.getSerializedUids() },
(err, info) => { (err: Error | null, info?: any) => {
if (err) { if (err) {
log.debug('received error message from vault', log.debug('received error message from vault',
{ errorMessage: err }); { errorMessage: err });
@ -227,18 +286,22 @@ export default class Vault {
/** getAccountIds -- call Vault to get accountIds based on /** getAccountIds -- call Vault to get accountIds based on
* canonicalIDs * canonicalIDs
* @param {array} canonicalIDs - list of canonicalIDs * @param canonicalIDs - list of canonicalIDs
* @param {object} log - log object * @param log - log object
* @param {function} callback - callback with either error or an object * @param callback - callback with either error or an object
* with canonicalID keys and accountId values * with canonicalID keys and accountId values
* @return {undefined}
*/ */
getAccountIds(canonicalIDs, log, callback) { getAccountIds(
canonicalIDs: string[],
log: Logger,
callback: (err: Error | null, data?: { [key: string]: string }) => void
) {
log.trace('getting accountIds from Vault based on canonicalIDs', log.trace('getting accountIds from Vault based on canonicalIDs',
{ canonicalIDs }); { canonicalIDs });
this.client.getAccountIds(canonicalIDs, this.client.getAccountIds(canonicalIDs,
// @ts-ignore
{ reqUid: log.getSerializedUids() }, { reqUid: log.getSerializedUids() },
(err, info) => { (err: Error | null, info?: any) => {
if (err) { if (err) {
log.debug('received error message from vault', log.debug('received error message from vault',
{ errorMessage: err }); { errorMessage: err });
@ -271,14 +334,19 @@ export default class Vault {
* @param {object} log - log object * @param {object} log - log object
* @param {function} callback - callback with either error or an array * @param {function} callback - callback with either error or an array
* of authorization results * of authorization results
* @return {undefined}
*/ */
checkPolicies(requestContextParams, userArn, log, callback) { checkPolicies(
requestContextParams: any[],
userArn: string,
log: Logger,
callback: (err: Error | null, data?: any[]) => void
) {
log.trace('sending request context params to vault to evaluate' + log.trace('sending request context params to vault to evaluate' +
'policies'); 'policies');
this.client.checkPolicies(requestContextParams, userArn, { this.client.checkPolicies(requestContextParams, userArn, {
// @ts-ignore
reqUid: log.getSerializedUids(), reqUid: log.getSerializedUids(),
}, (err, info) => { }, (err: Error | null, info?: any) => {
if (err) { if (err) {
log.debug('received error message from auth provider', log.debug('received error message from auth provider',
{ error: err }); { error: err });
@ -289,13 +357,14 @@ export default class Vault {
}); });
} }
checkHealth(log, callback) { checkHealth(log: Logger, callback: (err: Error | null, data?: any) => void) {
if (!this.client.healthcheck) { if (!this.client.healthcheck) {
const defResp = {}; const defResp = {};
defResp[this.implName] = { code: 200, message: 'OK' }; defResp[this.implName] = { code: 200, message: 'OK' };
return callback(null, defResp); return callback(null, defResp);
} }
return this.client.healthcheck(log.getSerializedUids(), (err, obj) => { // @ts-ignore
return this.client.healthcheck(log.getSerializedUids(), (err: Error | null, obj?: any) => {
const respBody = {}; const respBody = {};
if (err) { if (err) {
log.debug(`error from ${this.implName}`, { error: err }); log.debug(`error from ${this.implName}`, { error: err });

View File

@ -1,4 +1,5 @@
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import { Logger } from 'werelogs';
import errors from '../errors'; import errors from '../errors';
import * as queryString from 'querystring'; import * as queryString from 'querystring';
import AuthInfo from './AuthInfo'; import AuthInfo from './AuthInfo';
@ -8,15 +9,15 @@ import * as constants from '../constants';
import constructStringToSignV2 from './v2/constructStringToSign'; import constructStringToSignV2 from './v2/constructStringToSign';
import constructStringToSignV4 from './v4/constructStringToSign'; import constructStringToSignV4 from './v4/constructStringToSign';
import { convertUTCtoISO8601 } from './v4/timeUtils'; import { convertUTCtoISO8601 } from './v4/timeUtils';
import * as vaultUtilities from './backends/in_memory/vault-utilities'; import * as vaultUtilities from './backends/in_memory/vaultUtilities';
import * as inMemoryBackend from './backends/in_memory/Backend'; import * as inMemoryBackend from './backends/in_memory/Backend';
import validateAuthConfig from './backends/in_memory/validate-auth-config'; import validateAuthConfig from './backends/in_memory/validateAuthConfig';
import AuthLoader from './backends/in_memory/AuthLoader'; import AuthLoader from './backends/in_memory/AuthLoader';
import Vault from './Vault'; import Vault from './Vault';
import baseBackend from './backends/BaseBackend'; import baseBackend from './backends/BaseBackend';
import chainBackend from './backends/ChainBackend'; import chainBackend from './backends/ChainBackend';
let vault = null; let vault: Vault | null = null;
const auth = {}; const auth = {};
const checkFunctions = { const checkFunctions = {
v2: { v2: {
@ -33,7 +34,7 @@ const checkFunctions = {
// 'All Users Group' so use this group as the canonicalID for the publicUser // 'All Users Group' so use this group as the canonicalID for the publicUser
const publicUserInfo = new AuthInfo({ canonicalID: constants.publicId }); const publicUserInfo = new AuthInfo({ canonicalID: constants.publicId });
function setAuthHandler(handler) { function setAuthHandler(handler: Vault) {
vault = handler; vault = handler;
return auth; return auth;
} }
@ -41,25 +42,30 @@ function setAuthHandler(handler) {
/** /**
* This function will check validity of request parameters to authenticate * This function will check validity of request parameters to authenticate
* *
* @param {Http.Request} request - Http request object * @param request - Http request object
* @param {object} log - Logger object * @param log - Logger object
* @param {string} awsService - Aws service related * @param awsService - Aws service related
* @param {object} data - Parameters from queryString parsing or body of * @param data - Parameters from queryString parsing or body of
* POST request * POST request
* *
* @return {object} ret * @return ret
* @return {object} ret.err - arsenal.errors object if any error was found * @return ret.err - arsenal.errors object if any error was found
* @return {object} ret.params - auth parameters to use later on for signature * @return ret.params - auth parameters to use later on for signature
* computation and check * computation and check
* @return {object} ret.params.version - the auth scheme version * @return ret.params.version - the auth scheme version
* (undefined, 2, 4) * (undefined, 2, 4)
* @return {object} ret.params.data - the auth scheme's specific data * @return ret.params.data - the auth scheme's specific data
*/ */
function extractParams(request, log, awsService, data) { function extractParams(
request: any,
log: Logger,
awsService: string,
data: { [key: string]: string }
) {
log.trace('entered', { method: 'Arsenal.auth.server.extractParams' }); log.trace('entered', { method: 'Arsenal.auth.server.extractParams' });
const authHeader = request.headers.authorization; const authHeader = request.headers.authorization;
let version = null; let version: 'v2' |'v4' | null = null;
let method = null; let method: 'query' | 'headers' | null = null;
// Identify auth version and method to dispatch to the right check function // Identify auth version and method to dispatch to the right check function
if (authHeader) { if (authHeader) {
@ -105,16 +111,21 @@ function extractParams(request, log, awsService, data) {
/** /**
* This function will check validity of request parameters to authenticate * This function will check validity of request parameters to authenticate
* *
* @param {Http.Request} request - Http request object * @param request - Http request object
* @param {object} log - Logger object * @param log - Logger object
* @param {function} cb - the callback * @param cb - the callback
* @param {string} awsService - Aws service related * @param awsService - Aws service related
* @param {RequestContext[] | null} requestContexts - array of RequestContext * @param {RequestContext[] | null} requestContexts - array of RequestContext
* or null if no requestContexts to be sent to Vault (for instance, * or null if no requestContexts to be sent to Vault (for instance,
* in multi-object delete request) * in multi-object delete request)
* @return {undefined}
*/ */
function doAuth(request, log, cb, awsService, requestContexts) { function doAuth(
request: any,
log: Logger,
cb: (err: Error | null, data?: any) => void,
awsService: string,
requestContexts: any[] | null
) {
const res = extractParams(request, log, awsService, request.query); const res = extractParams(request, log, awsService, request.query);
if (res.err) { if (res.err) {
return cb(res.err); return cb(res.err);
@ -123,27 +134,30 @@ function doAuth(request, log, cb, awsService, requestContexts) {
} }
if (requestContexts) { if (requestContexts) {
requestContexts.forEach((requestContext) => { requestContexts.forEach((requestContext) => {
requestContext.setAuthType(res.params.data.authType); const { params } = res
requestContext.setSignatureVersion( if ('data' in params) {
res.params.data.signatureVersion const { data } = params
); requestContext.setAuthType(data.authType);
requestContext.setSignatureAge(res.params.data.signatureAge); requestContext.setSignatureVersion(data.signatureVersion);
requestContext.setSecurityToken(res.params.data.securityToken); requestContext.setSecurityToken(data.securityToken);
if ('signatureAge' in data) {
requestContext.setSignatureAge(data.signatureAge);
}
}
}); });
} }
// Corner cases managed, we're left with normal auth // Corner cases managed, we're left with normal auth
// TODO What's happening here?
// @ts-ignore
res.params.log = log; res.params.log = log;
if (res.params.version === 2) { if (res.params.version === 2) {
return vault.authenticateV2Request(res.params, requestContexts, cb); // @ts-ignore
return vault!.authenticateV2Request(res.params, requestContexts, cb);
} }
if (res.params.version === 4) { if (res.params.version === 4) {
return vault.authenticateV4Request( // @ts-ignore
res.params, return vault!.authenticateV4Request(res.params, requestContexts, cb);
requestContexts,
cb,
awsService
);
} }
log.error('authentication method not found', { log.error('authentication method not found', {
@ -155,25 +169,24 @@ function doAuth(request, log, cb, awsService, requestContexts) {
/** /**
* This function will generate a version 4 header * This function will generate a version 4 header
* *
* @param {Http.Request} request - Http request object * @param request - Http request object
* @param {object} data - Parameters from queryString parsing or body of * @param data - Parameters from queryString parsing or body of
* POST request * POST request
* @param {string} accessKey - the accessKey * @param accessKey - the accessKey
* @param {string} secretKeyValue - the secretKey * @param secretKeyValue - the secretKey
* @param {string} awsService - Aws service related * @param awsService - Aws service related
* @param {sting} [proxyPath] - path that gets proxied by reverse proxy * @param [proxyPath] - path that gets proxied by reverse proxy
* @param {string} [sessionToken] - security token if the access/secret keys * @param [sessionToken] - security token if the access/secret keys
* are temporary credentials from STS * are temporary credentials from STS
* @return {undefined}
*/ */
function generateV4Headers( function generateV4Headers(
request, request: any,
data, data: { [key: string]: string },
accessKey, accessKey: string,
secretKeyValue, secretKeyValue: string,
awsService, awsService: string,
proxyPath, proxyPath: string,
sessionToken sessionToken: string
) { ) {
Object.assign(request, { headers: {} }); Object.assign(request, { headers: {} });
const amzDate = convertUTCtoISO8601(Date.now()); const amzDate = convertUTCtoISO8601(Date.now());
@ -187,7 +200,7 @@ function generateV4Headers(
let payload = ''; let payload = '';
if (request.method === 'POST') { if (request.method === 'POST') {
payload = queryString.stringify(data, null, null, { payload = queryString.stringify(data, undefined, undefined, {
encodeURIComponent, encodeURIComponent,
}); });
} }

View File

@ -1,84 +1,70 @@
import errors from '../../errors'; import errors from '../../errors';
import { Callback } from './in_memory/types';
/** /** Base backend class */
* Base backend class
*
* @class BaseBackend
*/
export default class BaseBackend { export default class BaseBackend {
service service: string;
/** constructor(service: string) {
* @constructor
* @param {string} service - service identifer for construction arn
*/
constructor(service) {
this.service = service; this.service = service;
} }
/** verifySignatureV2 verifySignatureV2(
* @param {string} stringToSign - string to sign built per AWS rules _stringToSign: string,
* @param {string} signatureFromRequest - signature sent with request _signatureFromRequest: string,
* @param {string} accessKey - account accessKey _accessKey: string,
* @param {object} options - contains algorithm (SHA1 or SHA256) _options: { algo: 'SHA1' | 'SHA256' },
* @param {function} callback - callback with either error or user info callback: Callback
* @return {function} calls callback ) {
*/
verifySignatureV2(stringToSign, signatureFromRequest,
accessKey, options, callback) {
return callback(errors.AuthMethodNotImplemented); return callback(errors.AuthMethodNotImplemented);
} }
verifySignatureV4(
/** verifySignatureV4 _stringToSign: string,
* @param {string} stringToSign - string to sign built per AWS rules _signatureFromRequest: string,
* @param {string} signatureFromRequest - signature sent with request _accessKey: string,
* @param {string} accessKey - account accessKey _region: string,
* @param {string} region - region specified in request credential _scopeDate: string,
* @param {string} scopeDate - date specified in request credential _options: any,
* @param {object} options - options to send to Vault callback: Callback
* (just contains reqUid for logging in Vault) ) {
* @param {function} callback - callback with either error or user info
* @return {function} calls callback
*/
verifySignatureV4(stringToSign, signatureFromRequest, accessKey,
region, scopeDate, options, callback) {
return callback(errors.AuthMethodNotImplemented); return callback(errors.AuthMethodNotImplemented);
} }
/** /**
* Gets canonical ID's for a list of accounts * Gets canonical ID's for a list of accounts based on email associated
* based on email associated with account * with account. The callback will be called with either error or object
* @param {array} emails - list of email addresses * with email addresses as keys and canonical IDs as values.
* @param {object} options - to send log id to vault
* @param {function} callback - callback to calling function
* @returns {function} callback with either error or
* object with email addresses as keys and canonical IDs
* as values
*/ */
getCanonicalIds(emails, options, callback) { getCanonicalIds(_emails: string[], _options: any, callback: Callback) {
return callback(errors.AuthMethodNotImplemented); return callback(errors.AuthMethodNotImplemented);
} }
/** /**
* Gets email addresses (referred to as diplay names for getACL's) * Gets email addresses (referred to as diplay names for getACL's) for a
* for a list of accounts based on canonical IDs associated with account * list of accounts based on canonical IDs associated with account.
* @param {array} canonicalIDs - list of canonicalIDs * The callback will be called with either error or an object from Vault
* @param {object} options - to send log id to vault * containing account canonicalID as each object key and an email address
* @param {function} callback - callback to calling function * as the value (or "NotFound").
* @returns {function} callback with either error or
* an object from Vault containing account canonicalID
* as each object key and an email address as the value (or "NotFound")
*/ */
getEmailAddresses(canonicalIDs, options, callback) { getEmailAddresses(
_canonicalIDs: string[],
_options: any,
callback: Callback
) {
return callback(errors.AuthMethodNotImplemented); return callback(errors.AuthMethodNotImplemented);
} }
checkPolicies(requestContextParams, userArn, options, callback) { checkPolicies(
_requestContextParams: any,
_userArn: string,
_options: any,
callback: Callback
) {
return callback(null, { message: { body: [] } }); return callback(null, { message: { body: [] } });
} }
healthcheck(reqUid, callback) { healthcheck(_reqUid: string, callback: Callback) {
return callback(null, { code: 200, message: 'OK' }); return callback(null, { code: 200, message: 'OK' });
} }
} }

View File

@ -1,25 +1,31 @@
import assert from 'assert'; import assert from 'assert';
import async from 'async'; import async from 'async';
import { Callback } from './in_memory/types';
import errors from '../../errors'; import errors from '../../errors';
import BaseBackend from './BaseBackend'; import BaseBackend from './BaseBackend';
export type Policy = {
[key: string]: any;
arn?: string;
versionId?: string;
isAllowed: boolean;
}
/** /**
* Class that provides an authentication backend that will verify signatures * Class that provides an authentication backend that will verify signatures
* and retrieve emails and canonical ids associated with an account using a * and retrieve emails and canonical ids associated with an account using a
* given list of authentication backends and vault clients. * given list of authentication backends and vault clients.
*
* @class ChainBackend
*/ */
export default class ChainBackend extends BaseBackend { export default class ChainBackend extends BaseBackend {
_clients: any[]; #clients: BaseBackend[];
/** /**
* @constructor * @constructor
* @param {string} service - service id * @param {string} service - service id
* @param {object[]} clients - list of authentication backends or vault clients * @param {object[]} clients - list of authentication backends or vault clients
*/ */
constructor(service: string, clients: any[]) { constructor(service: string, clients: BaseBackend[]) {
super(service); super(service);
assert(Array.isArray(clients) && clients.length > 0, 'invalid client list'); assert(Array.isArray(clients) && clients.length > 0, 'invalid client list');
@ -31,25 +37,28 @@ export default class ChainBackend extends BaseBackend {
typeof client.checkPolicies === 'function' && typeof client.checkPolicies === 'function' &&
typeof client.healthcheck === 'function' typeof client.healthcheck === 'function'
), 'invalid client: missing required auth backend methods'); ), 'invalid client: missing required auth backend methods');
this._clients = clients; this.#clients = clients;
} }
/* /** try task against each client for one to be successful */
* try task against each client for one to be successful #tryEachClient(task: (client: BaseBackend, done?: any) => void, cb: Callback) {
*/ // @ts-ignore
_tryEachClient(task, cb) { async.tryEach(this.#clients.map(client => (done: any) => task(client, done)), cb);
async.tryEach(this._clients.map(client => done => task(client, done)), cb);
} }
/* /** apply task to all clients */
* apply task to all clients #forEachClient(task: (client: BaseBackend, done?: any) => void, cb: Callback) {
*/ async.map(this.#clients, task, cb);
_forEachClient(task, cb) {
async.map(this._clients, task, cb);
} }
verifySignatureV2(stringToSign, signatureFromRequest, accessKey, options, callback) { verifySignatureV2(
this._tryEachClient((client, done) => client.verifySignatureV2( stringToSign: string,
signatureFromRequest: string,
accessKey: string,
options: any,
callback: Callback
) {
this.#tryEachClient((client, done) => client.verifySignatureV2(
stringToSign, stringToSign,
signatureFromRequest, signatureFromRequest,
accessKey, accessKey,
@ -58,27 +67,39 @@ export default class ChainBackend extends BaseBackend {
), callback); ), callback);
} }
verifySignatureV4(stringToSign, signatureFromRequest, accessKey, region, scopeDate, options, callback) { verifySignatureV4(
this._tryEachClient((client, done) => client.verifySignatureV4( stringToSign: string,
signatureFromRequest: string,
accessKey: string,
region: string,
scopeDate: string,
options: any,
callback: Callback
) {
this.#tryEachClient((client, done) => client.verifySignatureV4(
stringToSign, stringToSign,
signatureFromRequest, signatureFromRequest,
accessKey, accessKey,
region, region,
scopeDate, scopeDate,
options, options,
done done
), callback); ), callback);
} }
static _mergeObjects(objectResponses) { static _mergeObjects(objectResponses: any[]) {
return objectResponses.reduce( return objectResponses.reduce(
(retObj, resObj) => Object.assign(retObj, resObj.message.body), (retObj, resObj) => Object.assign(retObj, resObj.message.body),
{} {}
); );
} }
getCanonicalIds(emailAddresses, options, callback) { getCanonicalIds(
this._forEachClient( emailAddresses: string[],
options: any,
callback: Callback<{ message: { body: any } }>
) {
this.#forEachClient(
(client, done) => client.getCanonicalIds(emailAddresses, options, done), (client, done) => client.getCanonicalIds(emailAddresses, options, done),
(err, res) => { (err, res) => {
if (err) { if (err) {
@ -94,8 +115,12 @@ export default class ChainBackend extends BaseBackend {
); );
} }
getEmailAddresses(canonicalIDs, options, callback) { getEmailAddresses(
this._forEachClient( canonicalIDs: string[],
options: any,
callback: Callback<{ message: { body: any } }>
) {
this.#forEachClient(
(client, done) => client.getEmailAddresses(canonicalIDs, options, done), (client, done) => client.getEmailAddresses(canonicalIDs, options, done),
(err, res) => { (err, res) => {
if (err) { if (err) {
@ -110,11 +135,9 @@ export default class ChainBackend extends BaseBackend {
); );
} }
/* /** merge policy responses into a single message */
* merge policy responses into a single message static _mergePolicies(policyResponses: { message: { body: any[] } }[]) {
*/ const policyMap: { [key: string]: Policy } = {};
static _mergePolicies(policyResponses) {
const policyMap = {};
policyResponses.forEach(resp => { policyResponses.forEach(resp => {
if (!resp.message || !Array.isArray(resp.message.body)) { if (!resp.message || !Array.isArray(resp.message.body)) {
@ -131,8 +154,8 @@ export default class ChainBackend extends BaseBackend {
}); });
return Object.keys(policyMap).map((key) => { return Object.keys(policyMap).map((key) => {
const policyRes = { isAllowed: policyMap[key].isAllowed }; const policyRes: Policy = { isAllowed: policyMap[key].isAllowed };
if (policyMap[key].arn !== '') { if (policyMap[key].arn && policyMap[key].arn !== '') {
policyRes.arn = policyMap[key].arn; policyRes.arn = policyMap[key].arn;
} }
if (policyMap[key].versionId) { if (policyMap[key].versionId) {
@ -142,7 +165,7 @@ export default class ChainBackend extends BaseBackend {
}); });
} }
/* /**
response format: response format:
{ message: { { message: {
body: [{}], body: [{}],
@ -150,8 +173,13 @@ export default class ChainBackend extends BaseBackend {
message: string, message: string,
} } } }
*/ */
checkPolicies(requestContextParams, userArn, options, callback) { checkPolicies(
this._forEachClient((client, done) => client.checkPolicies( requestContextParams: any,
userArn: string,
options: any,
callback: Callback<{ message: { body: any } }>
) {
this.#forEachClient((client, done) => client.checkPolicies(
requestContextParams, requestContextParams,
userArn, userArn,
options, options,
@ -168,8 +196,8 @@ export default class ChainBackend extends BaseBackend {
}); });
} }
healthcheck(reqUid, callback) { healthcheck(reqUid: string, callback: Callback) {
this._forEachClient((client, done) => this.#forEachClient((client, done) =>
client.healthcheck(reqUid, (err, res) => done(null, { client.healthcheck(reqUid, (err, res) => done(null, {
error: !!err ? err : null, error: !!err ? err : null,
status: res, status: res,
@ -179,7 +207,7 @@ export default class ChainBackend extends BaseBackend {
return callback(err); return callback(err);
} }
const isError = res.some((results) => !!results.error); const isError = res.some((results: any) => !!results.error);
if (isError) { if (isError) {
return callback(errors.InternalError, res); return callback(errors.InternalError, res);
} }

View File

@ -1,11 +1,11 @@
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import errors from '../../../errors'; import errors from '../../../errors';
import { calculateSigningKey, hashSignature } from './vault-utilities'; import { calculateSigningKey, hashSignature } from './vaultUtilities';
import Indexer from './Indexer'; import Indexer from './Indexer';
import BaseBackend from '../BaseBackend'; import BaseBackend from '../BaseBackend';
import { Accounts } from './types'; import { Accounts } from './types';
function _formatResponse(userInfoToSend) { function _formatResponse(userInfoToSend: any) {
return { return {
message: { message: {
body: { userInfo: userInfoToSend }, body: { userInfo: userInfoToSend },
@ -58,6 +58,7 @@ class InMemoryBackend extends BaseBackend {
accountDisplayName: this.indexer.getAcctDisplayName(entity), accountDisplayName: this.indexer.getAcctDisplayName(entity),
canonicalID: entity.canonicalID, canonicalID: entity.canonicalID,
arn: entity.arn, arn: entity.arn,
// @ts-ignore TODO why ?
IAMdisplayName: entity.IAMdisplayName, IAMdisplayName: entity.IAMdisplayName,
}; };
const vaultReturnObject = _formatResponse(userInfoToSend); const vaultReturnObject = _formatResponse(userInfoToSend);
@ -72,7 +73,7 @@ class InMemoryBackend extends BaseBackend {
accessKey: string, accessKey: string,
region: string, region: string,
scopeDate: string, scopeDate: string,
options: { algo: 'SHA256' | 'SHA1' }, _options: { algo: 'SHA256' | 'SHA1' },
callback: ( callback: (
err: Error | null, err: Error | null,
data?: ReturnType<typeof _formatResponse> data?: ReturnType<typeof _formatResponse>
@ -95,6 +96,7 @@ class InMemoryBackend extends BaseBackend {
accountDisplayName: this.indexer.getAcctDisplayName(entity), accountDisplayName: this.indexer.getAcctDisplayName(entity),
canonicalID: entity.canonicalID, canonicalID: entity.canonicalID,
arn: entity.arn, arn: entity.arn,
// @ts-ignore TODO why ?
IAMdisplayName: entity.IAMdisplayName, IAMdisplayName: entity.IAMdisplayName,
}; };
const vaultReturnObject = _formatResponse(userInfoToSend); const vaultReturnObject = _formatResponse(userInfoToSend);
@ -105,7 +107,7 @@ class InMemoryBackend extends BaseBackend {
// CODEQUALITY-TODO-SYNC Should be synchronous // CODEQUALITY-TODO-SYNC Should be synchronous
getCanonicalIds( getCanonicalIds(
emails: string[], emails: string[],
log: any, _log: any,
cb: (err: null, data: { message: { body: any } }) => void cb: (err: null, data: { message: { body: any } }) => void
) { ) {
const results = {}; const results = {};
@ -130,7 +132,7 @@ class InMemoryBackend extends BaseBackend {
// CODEQUALITY-TODO-SYNC Should be synchronous // CODEQUALITY-TODO-SYNC Should be synchronous
getEmailAddresses( getEmailAddresses(
canonicalIDs: string[], canonicalIDs: string[],
options: any, _options: any,
cb: (err: null, data: { message: { body: any } }) => void cb: (err: null, data: { message: { body: any } }) => void
) { ) {
const results = {}; const results = {};
@ -158,14 +160,14 @@ class InMemoryBackend extends BaseBackend {
* @param canonicalIDs - list of canonicalIDs * @param canonicalIDs - list of canonicalIDs
* @param options - to send log id to vault * @param options - to send log id to vault
* @param cb - callback to calling function * @param cb - callback to calling function
* @returns The next is wrong. Here to keep archives. * @return The next is wrong. Here to keep archives.
* callback with either error or * callback with either error or
* an object from Vault containing account canonicalID * an object from Vault containing account canonicalID
* as each object key and an accountId as the value (or "NotFound") * as each object key and an accountId as the value (or "NotFound")
*/ */
getAccountIds( getAccountIds(
canonicalIDs: string[], canonicalIDs: string[],
options: any, _options: any,
cb: (err: null, data: { message: { body: any } }) => void cb: (err: null, data: { message: { body: any } }) => void
) { ) {
const results = {}; const results = {};

View File

@ -1,5 +1,5 @@
export default function algoCheck(signatureLength) { export default function algoCheck(signatureLength: number) {
let algo; let algo: 'sha256' | 'sha1';
// If the signature sent is 44 characters, // If the signature sent is 44 characters,
// this means that sha256 was used: // this means that sha256 was used:
// 44 characters in base64 // 44 characters in base64
@ -11,5 +11,6 @@ export default function algoCheck(signatureLength) {
if (signatureLength === SHA1LEN) { if (signatureLength === SHA1LEN) {
algo = 'sha1'; algo = 'sha1';
} }
// @ts-ignore
return algo; return algo;
} }

View File

@ -1,8 +1,9 @@
import { Logger } from 'werelogs';
import errors from '../../errors'; import errors from '../../errors';
const epochTime = new Date('1970-01-01').getTime(); const epochTime = new Date('1970-01-01').getTime();
export default function checkRequestExpiry(timestamp, log) { export default function checkRequestExpiry(timestamp: number, log: Logger) {
// If timestamp is before epochTime, the request is invalid and return // If timestamp is before epochTime, the request is invalid and return
// errors.AccessDenied // errors.AccessDenied
if (timestamp < epochTime) { if (timestamp < epochTime) {

View File

@ -1,8 +1,14 @@
import { Logger } from 'werelogs';
import utf8 from 'utf8'; import utf8 from 'utf8';
import getCanonicalizedAmzHeaders from './getCanonicalizedAmzHeaders'; import getCanonicalizedAmzHeaders from './getCanonicalizedAmzHeaders';
import getCanonicalizedResource from './getCanonicalizedResource'; import getCanonicalizedResource from './getCanonicalizedResource';
export default function constructStringToSign(request, data, log, clientType?: any) { export default function constructStringToSign(
request: any,
data: { [key: string]: string },
log: Logger,
clientType?: any
) {
/* /*
Build signature per AWS requirements: Build signature per AWS requirements:
StringToSign = HTTP-Verb + '\n' + StringToSign = HTTP-Verb + '\n' +

View File

@ -1,12 +1,12 @@
export default function getCanonicalizedAmzHeaders(headers, clientType) { export default function getCanonicalizedAmzHeaders(headers: Headers, clientType: string) {
/* /*
Iterate through headers and pull any headers that are x-amz headers. Iterate through headers and pull any headers that are x-amz headers.
Need to include 'x-amz-date' here even though AWS docs Need to include 'x-amz-date' here even though AWS docs
ambiguous on this. ambiguous on this.
*/ */
const filterFn = clientType === 'GCP' ? const filterFn = clientType === 'GCP' ?
val => val.substr(0, 7) === 'x-goog-' : (val: string) => val.substr(0, 7) === 'x-goog-' :
val => val.substr(0, 6) === 'x-amz-'; (val: string) => val.substr(0, 6) === 'x-amz-';
const amzHeaders = Object.keys(headers) const amzHeaders = Object.keys(headers)
.filter(filterFn) .filter(filterFn)
.map(val => [val.trim(), headers[val].trim()]); .map(val => [val.trim(), headers[val].trim()]);

View File

@ -39,7 +39,7 @@ const awsSubresources = [
'website', 'website',
]; ];
export default function getCanonicalizedResource(request, clientType) { export default function getCanonicalizedResource(request: any, clientType: string) {
/* /*
This variable is used to determine whether to insert This variable is used to determine whether to insert
a '?' or '&'. Once a query parameter is added to the resourceString, a '?' or '&'. Once a query parameter is added to the resourceString,

View File

@ -1,10 +1,11 @@
import { Logger } from 'werelogs';
import errors from '../../errors'; import errors from '../../errors';
import * as constants from '../../constants'; import * as constants from '../../constants';
import constructStringToSign from './constructStringToSign'; import constructStringToSign from './constructStringToSign';
import checkRequestExpiry from './checkRequestExpiry'; import checkRequestExpiry from './checkRequestExpiry';
import algoCheck from './algoCheck'; import algoCheck from './algoCheck';
export function check(request, log, data) { export function check(request: any, log: Logger, data: { [key: string]: string }) {
log.trace('running header auth check'); log.trace('running header auth check');
const headers = request.headers; const headers = request.headers;
@ -56,6 +57,7 @@ export function check(request, log, data) {
log.trace('invalid authorization header', { authInfo }); log.trace('invalid authorization header', { authInfo });
return { err: errors.MissingSecurityHeader }; return { err: errors.MissingSecurityHeader };
} }
// @ts-ignore
log.addDefaultFields({ accessKey }); log.addDefaultFields({ accessKey });
const signatureFromRequest = authInfo.substring(semicolonIndex + 1).trim(); const signatureFromRequest = authInfo.substring(semicolonIndex + 1).trim();

View File

@ -1,9 +1,10 @@
import { Logger } from 'werelogs';
import errors from '../../errors'; import errors from '../../errors';
import * as constants from '../../constants'; import * as constants from '../../constants';
import algoCheck from './algoCheck'; import algoCheck from './algoCheck';
import constructStringToSign from './constructStringToSign'; import constructStringToSign from './constructStringToSign';
export function check(request, log, data) { export function check(request: any, log: Logger, data: { [key: string]: string }) {
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');
@ -51,6 +52,7 @@ export function check(request, log, data) {
return { err: errors.RequestTimeTooSkewed }; return { err: errors.RequestTimeTooSkewed };
} }
const accessKey = data.AWSAccessKeyId; const accessKey = data.AWSAccessKeyId;
// @ts-ignore
log.addDefaultFields({ accessKey }); log.addDefaultFields({ accessKey });
const signatureFromRequest = decodeURIComponent(data.Signature); const signatureFromRequest = decodeURIComponent(data.Signature);

View File

@ -17,7 +17,7 @@ See http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
*/ */
// converts utf8 character to hex and pads "%" before every two hex digits // converts utf8 character to hex and pads "%" before every two hex digits
function _toHexUTF8(char) { function _toHexUTF8(char: string) {
const hexRep = Buffer.from(char, 'utf8').toString('hex').toUpperCase(); const hexRep = Buffer.from(char, 'utf8').toString('hex').toUpperCase();
let res = ''; let res = '';
hexRep.split('').forEach((v, n) => { hexRep.split('').forEach((v, n) => {
@ -30,7 +30,11 @@ function _toHexUTF8(char) {
return res; return res;
} }
export default function awsURIencode(input, encodeSlash?: any, noEncodeStar?: any) { export default function awsURIencode(
input: string,
encodeSlash?: boolean,
noEncodeStar?: boolean
) {
const encSlash = encodeSlash === undefined ? true : encodeSlash; const encSlash = encodeSlash === undefined ? true : encodeSlash;
let encoded = ''; let encoded = '';
/** /**

View File

@ -1,4 +1,5 @@
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import { Logger } from 'werelogs';
import createCanonicalRequest from './createCanonicalRequest'; import createCanonicalRequest from './createCanonicalRequest';
/** /**
@ -6,7 +7,17 @@ import createCanonicalRequest from './createCanonicalRequest';
* @param {object} params - params object * @param {object} params - params object
* @returns {string} - stringToSign * @returns {string} - stringToSign
*/ */
export default function constructStringToSign(params): string { export default function constructStringToSign(params: {
request: any;
signedHeaders: any;
payloadChecksum: any;
credentialScope: string;
timestamp: string;
query: { [key: string]: string };
log?: Logger;
proxyPath: string;
awsService: string;
}): string {
const { const {
request, request,
signedHeaders, signedHeaders,
@ -29,6 +40,8 @@ export default function constructStringToSign(params): string {
service: params.awsService, service: params.awsService,
}); });
// TODO Why that line?
// @ts-ignore
if (canonicalReqResult instanceof Error) { if (canonicalReqResult instanceof Error) {
if (log) { if (log) {
log.error('error creating canonicalRequest'); log.error('error creating canonicalRequest');

View File

@ -4,22 +4,30 @@ import * as queryString from 'querystring';
/** /**
* createCanonicalRequest - creates V4 canonical request * createCanonicalRequest - creates V4 canonical request
* @param {object} params - contains pHttpVerb (request type), * @param params - contains pHttpVerb (request type),
* pResource (parsed from URL), pQuery (request query), * pResource (parsed from URL), pQuery (request query),
* pHeaders (request headers), pSignedHeaders (signed headers from request), * pHeaders (request headers), pSignedHeaders (signed headers from request),
* payloadChecksum (from request) * payloadChecksum (from request)
* @returns {string} - canonicalRequest * @returns - canonicalRequest
*/ */
export default function createCanonicalRequest(params) { export default function createCanonicalRequest(
params: {
pHttpVerb: string;
pResource: string;
pQuery: { [key: string]: string };
pHeaders: any;
pSignedHeaders: any;
service: string;
payloadChecksum: string;
}
) {
const pHttpVerb = params.pHttpVerb; const pHttpVerb = params.pHttpVerb;
const pResource = params.pResource; const pResource = params.pResource;
const pQuery = params.pQuery; const pQuery = params.pQuery;
const pHeaders = params.pHeaders; const pHeaders = params.pHeaders;
const pSignedHeaders = params.pSignedHeaders; const pSignedHeaders = params.pSignedHeaders;
const service = params.service; const service = params.service;
let payloadChecksum = params.payloadChecksum; let payloadChecksum = params.payloadChecksum;
if (!payloadChecksum) { if (!payloadChecksum) {
if (pHttpVerb === 'GET') { if (pHttpVerb === 'GET') {
payloadChecksum = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b' + payloadChecksum = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b' +
@ -32,7 +40,7 @@ export default function createCanonicalRequest(params) {
if (/aws-sdk-java\/[0-9.]+/.test(pHeaders['user-agent'])) { if (/aws-sdk-java\/[0-9.]+/.test(pHeaders['user-agent'])) {
notEncodeStar = true; notEncodeStar = true;
} }
let payload = queryString.stringify(pQuery, null, null, { let payload = queryString.stringify(pQuery, undefined, undefined, {
encodeURIComponent: input => awsURIencode(input, true, encodeURIComponent: input => awsURIencode(input, true,
notEncodeStar), notEncodeStar),
}); });
@ -59,11 +67,11 @@ export default function createCanonicalRequest(params) {
// signed headers // signed headers
const signedHeadersList = pSignedHeaders.split(';'); const signedHeadersList = pSignedHeaders.split(';');
signedHeadersList.sort((a, b) => a.localeCompare(b)); signedHeadersList.sort((a: any, b: any) => a.localeCompare(b));
const signedHeaders = signedHeadersList.join(';'); const signedHeaders = signedHeadersList.join(';');
// canonical headers // canonical headers
const canonicalHeadersList = signedHeadersList.map(signedHeader => { const canonicalHeadersList = signedHeadersList.map((signedHeader: any) => {
if (pHeaders[signedHeader] !== undefined) { if (pHeaders[signedHeader] !== undefined) {
const trimmedHeader = pHeaders[signedHeader] const trimmedHeader = pHeaders[signedHeader]
.trim().replace(/\s+/g, ' '); .trim().replace(/\s+/g, ' ');

View File

@ -1,3 +1,4 @@
import { Logger } from 'werelogs';
import errors from '../../../lib/errors'; import errors from '../../../lib/errors';
import * as constants from '../../constants'; import * as constants from '../../constants';
import constructStringToSign from './constructStringToSign'; import constructStringToSign from './constructStringToSign';
@ -14,14 +15,18 @@ import {
/** /**
* V4 header auth check * V4 header auth check
* @param {object} request - HTTP request object * @param request - HTTP request object
* @param {object} log - logging object * @param log - logging object
* @param {object} data - Parameters from queryString parsing or body of * @param data - Parameters from queryString parsing or body of
* POST request * POST request
* @param {string} awsService - Aws service ('iam' or 's3') * @param awsService - Aws service ('iam' or 's3')
* @return {callback} calls callback
*/ */
export function check(request, log, data, awsService) { export function check(
request: any,
log: Logger,
data: { [key: string]: string },
awsService: string
) {
log.trace('running header auth check'); log.trace('running header auth check');
const token = request.headers['x-amz-security-token']; const token = request.headers['x-amz-security-token'];
@ -63,16 +68,16 @@ export function check(request, log, data, awsService) {
log.trace('authorization header from request', { authHeader }); log.trace('authorization header from request', { authHeader });
const signatureFromRequest = authHeaderItems.signatureFromRequest; const signatureFromRequest = authHeaderItems.signatureFromRequest!;
const credentialsArr = authHeaderItems.credentialsArr; const credentialsArr = authHeaderItems.credentialsArr!;
const signedHeaders = authHeaderItems.signedHeaders; const signedHeaders = authHeaderItems.signedHeaders!;
if (!areSignedHeadersComplete(signedHeaders, request.headers)) { if (!areSignedHeadersComplete(signedHeaders, request.headers)) {
log.debug('signedHeaders are incomplete', { signedHeaders }); log.debug('signedHeaders are incomplete', { signedHeaders });
return { err: errors.AccessDenied }; return { err: errors.AccessDenied };
} }
let timestamp; let timestamp: string | undefined;
// check request timestamp // check request timestamp
const xAmzDate = request.headers['x-amz-date']; const xAmzDate = request.headers['x-amz-date'];
if (xAmzDate) { if (xAmzDate) {
@ -140,7 +145,7 @@ export function check(request, log, data, awsService) {
return { err: errors.RequestTimeTooSkewed }; return { err: errors.RequestTimeTooSkewed };
} }
let proxyPath = null; let proxyPath: string | null = null;
if (request.headers.proxy_path) { if (request.headers.proxy_path) {
try { try {
proxyPath = decodeURIComponent(request.headers.proxy_path); proxyPath = decodeURIComponent(request.headers.proxy_path);
@ -163,9 +168,11 @@ export function check(request, log, data, awsService) {
timestamp, timestamp,
payloadChecksum, payloadChecksum,
awsService: service, awsService: service,
proxyPath, proxyPath: proxyPath!,
}); });
log.trace('constructed stringToSign', { stringToSign }); log.trace('constructed stringToSign', { stringToSign });
// TODO Why?
// @ts-ignore
if (stringToSign instanceof Error) { if (stringToSign instanceof Error) {
return { err: stringToSign }; return { err: stringToSign };
} }

View File

@ -1,3 +1,4 @@
import { Logger } from 'werelogs';
import * as constants from '../../constants'; import * as constants from '../../constants';
import errors from '../../errors'; import errors from '../../errors';
@ -8,12 +9,11 @@ import { areSignedHeadersComplete } from './validateInputs';
/** /**
* V4 query auth check * V4 query auth check
* @param {object} request - HTTP request object * @param request - HTTP request object
* @param {object} log - logging object * @param log - logging object
* @param {object} data - Contain authentification params (GET or POST data) * @param data - Contain authentification params (GET or POST data)
* @return {callback} calls callback
*/ */
export function check(request, log, data) { export function check(request: any, log: Logger, data: { [key: string]: string }) {
const authParams = extractQueryParams(data, log); const authParams = extractQueryParams(data, log);
if (Object.keys(authParams).length !== 5) { if (Object.keys(authParams).length !== 5) {
@ -28,11 +28,11 @@ export function check(request, log, data) {
return { err: errors.InvalidToken }; return { err: errors.InvalidToken };
} }
const signedHeaders = authParams.signedHeaders; const signedHeaders = authParams.signedHeaders!;
const signatureFromRequest = authParams.signatureFromRequest; const signatureFromRequest = authParams.signatureFromRequest!;
const timestamp = authParams.timestamp; const timestamp = authParams.timestamp!;
const expiry = authParams.expiry; const expiry = authParams.expiry!;
const credential = authParams.credential; const credential = authParams.credential!;
if (!areSignedHeadersComplete(signedHeaders, request.headers)) { if (!areSignedHeadersComplete(signedHeaders, request.headers)) {
log.debug('signedHeaders are incomplete', { signedHeaders }); log.debug('signedHeaders are incomplete', { signedHeaders });
@ -59,7 +59,7 @@ export function check(request, log, data) {
return { err: errors.RequestTimeTooSkewed }; return { err: errors.RequestTimeTooSkewed };
} }
let proxyPath = null; let proxyPath: string | null = null;
if (request.headers.proxy_path) { if (request.headers.proxy_path) {
try { try {
proxyPath = decodeURIComponent(request.headers.proxy_path); proxyPath = decodeURIComponent(request.headers.proxy_path);
@ -97,8 +97,10 @@ export function check(request, log, data) {
timestamp, timestamp,
credentialScope: `${scopeDate}/${region}/${service}/${requestType}`, credentialScope: `${scopeDate}/${region}/${service}/${requestType}`,
awsService: service, awsService: service,
proxyPath, proxyPath: proxyPath!,
}); });
// TODO Why?
// @ts-ignore
if (stringToSign instanceof Error) { if (stringToSign instanceof Error) {
return { err: stringToSign }; return { err: stringToSign };
} }

View File

@ -1,5 +1,8 @@
import { Transform } from 'stream'; import { Transform } from 'stream';
import async from 'async'; import async from 'async';
import { Logger } from 'werelogs';
import { Callback } from '../../backends/in_memory/types';
import Vault from '../../Vault';
import errors from '../../../errors'; import errors from '../../../errors';
import constructChunkStringToSign from './constructChunkStringToSign'; import constructChunkStringToSign from './constructChunkStringToSign';
@ -8,26 +11,28 @@ import constructChunkStringToSign from './constructChunkStringToSign';
* v4 Auth request * v4 Auth request
*/ */
export default class V4Transform extends Transform { export default class V4Transform extends Transform {
log; log: Logger;
cb; cb: Callback;
accessKey; accessKey: string;
region; region: string;
scopeDate; /** Date parsed from headers in ISO8601. */
timestamp; scopeDate: string;
credentialScope; /** Date parsed from headers in ISO8601. */
lastSignature; timestamp: string;
currentSignature; /** Items from auth header, plus the string 'aws4_request' joined with '/': timestamp/region/aws-service/aws4_request */
haveMetadata; credentialScope: string;
seekingDataSize; lastSignature?: string;
currentData; currentSignature?: string;
dataCursor; haveMetadata: boolean;
currentMetadata; seekingDataSize: number;
lastPieceDone; currentData?: any;
lastChunk; dataCursor: number;
vault; currentMetadata: Buffer[];
lastPieceDone: boolean;
lastChunk: boolean;
vault: Vault;
/** /**
* @constructor
* @param {object} streamingV4Params - info for chunk authentication * @param {object} streamingV4Params - info for chunk authentication
* @param {string} streamingV4Params.accessKey - requester's accessKey * @param {string} streamingV4Params.accessKey - requester's accessKey
* @param {string} streamingV4Params.signatureFromRequest - signature * @param {string} streamingV4Params.signatureFromRequest - signature
@ -43,7 +48,19 @@ export default class V4Transform extends Transform {
* @param {object} log - logger object * @param {object} log - logger object
* @param {function} cb - callback to api * @param {function} cb - callback to api
*/ */
constructor(streamingV4Params, vault, log, cb) { constructor(
streamingV4Params: {
accessKey: string,
signatureFromRequest: string,
region: string,
scopeDate: string,
timestamp: string,
credentialScope: string
},
vault: Vault,
log: Logger,
cb: Callback
) {
const { const {
accessKey, accessKey,
signatureFromRequest, signatureFromRequest,
@ -77,8 +94,8 @@ export default class V4Transform extends Transform {
/** /**
* This function will parse the metadata portion of the chunk * This function will parse the metadata portion of the chunk
* @param {Buffer} remainingChunk - chunk sent from _transform * @param remainingChunk - chunk sent from _transform
* @return {object} response - if error, will return 'err' key with * @return response - if error, will return 'err' key with
* arsenal error value. * arsenal error value.
* if incomplete metadata, will return 'completeMetadata' key with * if incomplete metadata, will return 'completeMetadata' key with
* value false * value false
@ -86,7 +103,7 @@ export default class V4Transform extends Transform {
* value true and the key 'unparsedChunk' with the remaining chunk without * value true and the key 'unparsedChunk' with the remaining chunk without
* the parsed metadata piece * the parsed metadata piece
*/ */
_parseMetadata(remainingChunk) { _parseMetadata(remainingChunk: Buffer) {
let remainingPlusStoredMetadata = remainingChunk; let remainingPlusStoredMetadata = remainingChunk;
// have metadata pieces so need to add to the front of // have metadata pieces so need to add to the front of
// remainingChunk // remainingChunk
@ -127,9 +144,8 @@ export default class V4Transform extends Transform {
); );
return { err: errors.InvalidArgument }; return { err: errors.InvalidArgument };
} }
let dataSize = splitMeta[0];
// chunk-size is sent in hex // chunk-size is sent in hex
dataSize = Number.parseInt(dataSize, 16); let dataSize = Number.parseInt(splitMeta[0], 16);
if (Number.isNaN(dataSize)) { if (Number.isNaN(dataSize)) {
this.log.trace('chunk body did not contain valid size'); this.log.trace('chunk body did not contain valid size');
return { err: errors.InvalidArgument }; return { err: errors.InvalidArgument };
@ -164,17 +180,17 @@ export default class V4Transform extends Transform {
/** /**
* Build the stringToSign and authenticate the chunk * Build the stringToSign and authenticate the chunk
* @param {Buffer} dataToSend - chunk sent from _transform or null * @param dataToSend - chunk sent from _transform or null
* if last chunk without data * if last chunk without data
* @param {function} done - callback to _transform * @param done - callback to _transform
* @return {function} executes callback with err if applicable * @return executes callback with err if applicable
*/ */
_authenticate(dataToSend, done) { _authenticate(dataToSend: Buffer | null, done: (err?: Error) => void) {
// use prior sig to construct new string to sign // use prior sig to construct new string to sign
const stringToSign = constructChunkStringToSign( const stringToSign = constructChunkStringToSign(
this.timestamp, this.timestamp,
this.credentialScope, this.credentialScope,
this.lastSignature, this.lastSignature!,
dataToSend dataToSend
); );
this.log.trace('constructed chunk string to sign', { stringToSign }); this.log.trace('constructed chunk string to sign', { stringToSign });
@ -193,7 +209,7 @@ export default class V4Transform extends Transform {
credentialScope: this.credentialScope, credentialScope: this.credentialScope,
}, },
}; };
return this.vault.authenticateV4Request(vaultParams, null, (err) => { return this.vault.authenticateV4Request(vaultParams, null, (err: Error) => {
if (err) { if (err) {
this.log.trace('err from vault on streaming v4 auth', { this.log.trace('err from vault on streaming v4 auth', {
error: err, error: err,
@ -205,17 +221,18 @@ export default class V4Transform extends Transform {
}); });
} }
// TODO encoding unused. Why?
/** /**
* This function will parse the chunk into metadata and data, * This function will parse the chunk into metadata and data,
* use the metadata to authenticate with vault and send the * use the metadata to authenticate with vault and send the
* data on to be stored if authentication passes * data on to be stored if authentication passes
* *
* @param {Buffer} chunk - chunk from request body * @param chunk - chunk from request body
* @param {string} encoding - Data encoding * @param encoding - Data encoding
* @param {function} callback - Callback(err, justDataChunk, encoding) * @param callback - Callback(err, justDataChunk, encoding)
* @return {function }executes callback with err if applicable * @return executes callback with err if applicable
*/ */
_transform(chunk, encoding, callback) { _transform(chunk: Buffer, _encoding: string, callback: (err?: Error) => void) {
// 'chunk' here is the node streaming chunk // 'chunk' here is the node streaming chunk
// transfer-encoding chunks should be of the format: // transfer-encoding chunks should be of the format:
// string(IntHexBase(chunk-size)) + ";chunk-signature=" + // string(IntHexBase(chunk-size)) + ";chunk-signature=" +
@ -254,7 +271,7 @@ export default class V4Transform extends Transform {
} }
// have metadata so reset unparsedChunk to remaining // have metadata so reset unparsedChunk to remaining
// without metadata piece // without metadata piece
unparsedChunk = parsedMetadataResults.unparsedChunk; unparsedChunk = parsedMetadataResults.unparsedChunk!;
} }
if (this.lastChunk) { if (this.lastChunk) {
this.log.trace('authenticating final chunk with no data'); this.log.trace('authenticating final chunk with no data');
@ -301,7 +318,7 @@ export default class V4Transform extends Transform {
// final callback // final callback
(err) => { (err) => {
if (err) { if (err) {
return this.cb(err); return this.cb(err as any);
} }
// get next chunk // get next chunk
return callback(); return callback();

View File

@ -3,30 +3,30 @@ import * as constants from '../../../constants';
/** /**
* Constructs stringToSign for chunk * Constructs stringToSign for chunk
* @param {string} timestamp - date parsed from headers * @param timestamp - date parsed from headers in ISO 8601 format: YYYYMMDDTHHMMSSZ
* in ISO 8601 format: YYYYMMDDTHHMMSSZ * @param credentialScope - items from auth header plus the string
* @param {string} credentialScope - items from auth * 'aws4_request' joined with '/':
* header plus the string 'aws4_request' joined with '/':
* timestamp/region/aws-service/aws4_request * timestamp/region/aws-service/aws4_request
* @param {string} lastSignature - signature from headers or prior chunk * @param lastSignature - signature from headers or prior chunk
* @param {string} justDataChunk - data portion of chunk * @param justDataChunk - data portion of chunk
* @returns {string} stringToSign
*/ */
export default function constructChunkStringToSign( export default function constructChunkStringToSign(
timestamp: string, timestamp: string,
credentialScope: string, credentialScope: string,
lastSignature: string, lastSignature: string,
justDataChunk: string justDataChunk: string | Buffer | null
): string { ): string {
let currentChunkHash; let currentChunkHash: string;
// for last chunk, there will be no data, so use emptyStringHash // for last chunk, there will be no data, so use emptyStringHash
if (!justDataChunk) { if (!justDataChunk) {
currentChunkHash = constants.emptyStringHash; currentChunkHash = constants.emptyStringHash;
} else { } else {
currentChunkHash = crypto.createHash('sha256'); let hash = crypto.createHash('sha256');
currentChunkHash = currentChunkHash currentChunkHash = (
.update(justDataChunk, 'binary') typeof justDataChunk === 'string'
.digest('hex'); ? hash.update(justDataChunk, 'binary')
: hash.update(justDataChunk)
).digest('hex');
} }
return ( return (
`AWS4-HMAC-SHA256-PAYLOAD\n${timestamp}\n` + `AWS4-HMAC-SHA256-PAYLOAD\n${timestamp}\n` +

View File

@ -1,10 +1,11 @@
import { Logger } from 'werelogs';
/** /**
* Convert timestamp to milliseconds since Unix Epoch * Convert timestamp to milliseconds since Unix Epoch
* @param {string} timestamp of ISO8601Timestamp format without * @param timestamp of ISO8601Timestamp format without
* dashes or colons, e.g. 20160202T220410Z * dashes or colons, e.g. 20160202T220410Z
* @return {number} number of milliseconds since Unix Epoch
*/ */
export function convertAmzTimeToMs(timestamp) { export function convertAmzTimeToMs(timestamp: string) {
const arr = timestamp.split(''); const arr = timestamp.split('');
// Convert to YYYY-MM-DDTHH:mm:ss.sssZ // Convert to YYYY-MM-DDTHH:mm:ss.sssZ
const ISO8601time = `${arr.slice(0, 4).join('')}-${arr[4]}${arr[5]}` + const ISO8601time = `${arr.slice(0, 4).join('')}-${arr[4]}${arr[5]}` +
@ -13,13 +14,12 @@ export function convertAmzTimeToMs(timestamp) {
return Date.parse(ISO8601time); return Date.parse(ISO8601time);
} }
/** /**
* Convert UTC timestamp to ISO 8601 timestamp * Convert UTC timestamp to ISO 8601 timestamp
* @param {string} timestamp of UTC form: Fri, 10 Feb 2012 21:34:55 GMT * @param timestamp of UTC form: Fri, 10 Feb 2012 21:34:55 GMT
* @return {string} ISO8601 timestamp of form: YYYYMMDDTHHMMSSZ * @return ISO8601 timestamp of form: YYYYMMDDTHHMMSSZ
*/ */
export function convertUTCtoISO8601(timestamp) { export function convertUTCtoISO8601(timestamp: string | number) {
// convert to ISO string: YYYY-MM-DDTHH:mm:ss.sssZ. // convert to ISO string: YYYY-MM-DDTHH:mm:ss.sssZ.
const converted = new Date(timestamp).toISOString(); const converted = new Date(timestamp).toISOString();
// Remove "-"s and "."s and milliseconds // Remove "-"s and "."s and milliseconds
@ -28,13 +28,13 @@ export function convertUTCtoISO8601(timestamp) {
/** /**
* Check whether timestamp predates request or is too old * Check whether timestamp predates request or is too old
* @param {string} timestamp of ISO8601Timestamp format without * @param timestamp of ISO8601Timestamp format without
* dashes or colons, e.g. 20160202T220410Z * dashes or colons, e.g. 20160202T220410Z
* @param {number} expiry - number of seconds signature should be valid * @param expiry - number of seconds signature should be valid
* @param {object} log - log for request * @param log - log for request
* @return {boolean} true if there is a time problem * @return true if there is a time problem
*/ */
export function checkTimeSkew(timestamp, expiry, log) { export function checkTimeSkew(timestamp: string, expiry: number, log: Logger) {
const currentTime = Date.now(); const currentTime = Date.now();
const fifteenMinutes = (15 * 60 * 1000); const fifteenMinutes = (15 * 60 * 1000);
const parsedTimestamp = convertAmzTimeToMs(timestamp); const parsedTimestamp = convertAmzTimeToMs(timestamp);

View File

@ -1,15 +1,19 @@
import { Logger } from 'werelogs';
import errors from '../../../lib/errors'; import errors from '../../../lib/errors';
/** /**
* Validate Credentials * Validate Credentials
* @param {array} credentials - contains accessKey, scopeDate, * @param credentials - contains accessKey, scopeDate,
* region, service, requestType * region, service, requestType
* @param {string} timestamp - timestamp from request in * @param timestamp - timestamp from request in
* the format of ISO 8601: YYYYMMDDTHHMMSSZ * the format of ISO 8601: YYYYMMDDTHHMMSSZ
* @param {object} log - logging object * @param log - logging object
* @return {boolean} true if credentials are correct format, false if not
*/ */
export function validateCredentials(credentials, timestamp, log) { export function validateCredentials(
credentials: [string, string, string, string, string],
timestamp: string,
log: Logger
): Error | {} {
if (!Array.isArray(credentials) || credentials.length !== 5) { if (!Array.isArray(credentials) || credentials.length !== 5) {
log.warn('credentials in improper format', { credentials }); log.warn('credentials in improper format', { credentials });
return errors.InvalidArgument; return errors.InvalidArgument;
@ -63,12 +67,21 @@ export function validateCredentials(credentials, timestamp, log) {
/** /**
* Extract and validate components from query object * Extract and validate components from query object
* @param {object} queryObj - query object from request * @param queryObj - query object from request
* @param {object} log - logging object * @param log - logging object
* @return {object} object containing extracted query params for authV4 * @return object containing extracted query params for authV4
*/ */
export function extractQueryParams(queryObj, log) { export function extractQueryParams(
const authParams = {}; queryObj: { [key: string]: string | undefined },
log: Logger
) {
const authParams: {
signedHeaders?: string;
signatureFromRequest?: string;
timestamp?: string;
expiry?: number;
credential?: [string, string, string, string, string];
} = {};
// Do not need the algorithm sent back // Do not need the algorithm sent back
if (queryObj['X-Amz-Algorithm'] !== 'AWS4-HMAC-SHA256') { if (queryObj['X-Amz-Algorithm'] !== 'AWS4-HMAC-SHA256') {
@ -105,7 +118,7 @@ export function extractQueryParams(queryObj, log) {
return authParams; return authParams;
} }
const expiry = Number.parseInt(queryObj['X-Amz-Expires'], 10); const expiry = Number.parseInt(queryObj['X-Amz-Expires'] ?? 'nope', 10);
const sevenDays = 604800; const sevenDays = 604800;
if (expiry && expiry > 0 && expiry <= sevenDays) { if (expiry && expiry > 0 && expiry <= sevenDays) {
authParams.expiry = expiry; authParams.expiry = expiry;
@ -116,6 +129,7 @@ export function extractQueryParams(queryObj, log) {
const credential = queryObj['X-Amz-Credential']; const credential = queryObj['X-Amz-Credential'];
if (credential && credential.length > 28 && credential.indexOf('/') > -1) { if (credential && credential.length > 28 && credential.indexOf('/') > -1) {
// @ts-ignore
authParams.credential = credential.split('/'); authParams.credential = credential.split('/');
} else { } else {
log.warn('invalid credential param', { credential }); log.warn('invalid credential param', { credential });
@ -126,12 +140,16 @@ export function extractQueryParams(queryObj, log) {
/** /**
* Extract and validate components from auth header * Extract and validate components from auth header
* @param {string} authHeader - authorization header from request * @param authHeader - authorization header from request
* @param {object} log - logging object * @param log - logging object
* @return {object} object containing extracted auth header items for authV4 * @return object containing extracted auth header items for authV4
*/ */
export function extractAuthItems(authHeader, log) { export function extractAuthItems(authHeader: string, log: Logger) {
const authItems = {}; const authItems: {
credentialsArr?: [string, string, string, string, string];
signedHeaders?: string;
signatureFromRequest?: string;
} = {};
const authArray = authHeader.replace('AWS4-HMAC-SHA256 ', '').split(','); const authArray = authHeader.replace('AWS4-HMAC-SHA256 ', '').split(',');
if (authArray.length < 3) { if (authArray.length < 3) {
@ -147,6 +165,7 @@ export function extractAuthItems(authHeader, log) {
credentialStr.trim().startsWith('Credential=') && credentialStr.trim().startsWith('Credential=') &&
credentialStr.indexOf('/') > -1 credentialStr.indexOf('/') > -1
) { ) {
// @ts-ignore
authItems.credentialsArr = credentialStr authItems.credentialsArr = credentialStr
.trim() .trim()
.replace('Credential=', '') .replace('Credential=', '')
@ -179,11 +198,11 @@ export function extractAuthItems(authHeader, log) {
/** /**
* Checks whether the signed headers include the host header * Checks whether the signed headers include the host header
* and all x-amz- and x-scal- headers in request * and all x-amz- and x-scal- headers in request
* @param {string} signedHeaders - signed headers sent with request * @param signedHeaders - signed headers sent with request
* @param {object} allHeaders - request.headers * @param allHeaders - request.headers
* @return {boolean} true if all x-amz-headers included and false if not * @return true if all x-amz-headers included and false if not
*/ */
export function areSignedHeadersComplete(signedHeaders, allHeaders) { export function areSignedHeadersComplete(signedHeaders: string, allHeaders: Headers) {
const signedHeadersList = signedHeaders.split(';'); const signedHeadersList = signedHeaders.split(';');
if (signedHeadersList.indexOf('host') === -1) { if (signedHeadersList.indexOf('host') === -1) {
return false; return false;

View File

@ -5,7 +5,7 @@ const assert = require('assert');
const constructStringToSign = const constructStringToSign =
require('../../../../lib/auth/v2/constructStringToSign').default; require('../../../../lib/auth/v2/constructStringToSign').default;
const hashSignature = const hashSignature =
require('../../../../lib/auth/backends/in_memory/vault-utilities').hashSignature; require('../../../../lib/auth/backends/in_memory/vaultUtilities').hashSignature;
const DummyRequestLogger = require('../../helpers').DummyRequestLogger; const DummyRequestLogger = require('../../helpers').DummyRequestLogger;
const log = new DummyRequestLogger(); const log = new DummyRequestLogger();

View File

@ -3,7 +3,7 @@
const assert = require('assert'); const assert = require('assert');
const calculateSigningKey = const calculateSigningKey =
require('../../../../lib/auth/backends/in_memory/vault-utilities') require('../../../../lib/auth/backends/in_memory/vaultUtilities')
.calculateSigningKey; .calculateSigningKey;
describe('v4 signing key calculation', () => { describe('v4 signing key calculation', () => {