Compare commits
10 Commits
4b76407edd
...
8400204d95
Author | SHA1 | Date |
---|---|---|
Guillaume Hivert | 8400204d95 | |
Guillaume Hivert | 0d057fb2d0 | |
Guillaume Hivert | 86c8c77dd2 | |
Guillaume Hivert | 52284a871a | |
Guillaume Hivert | a2d3dfeb21 | |
Guillaume Hivert | 73e0150612 | |
Guillaume Hivert | 89d5970aac | |
Guillaume Hivert | 8f4870f5a2 | |
Guillaume Hivert | 11a9032411 | |
Guillaume Hivert | 8e11405cf7 |
|
@ -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}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
115
lib/auth/auth.ts
115
lib/auth/auth.ts
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = {};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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' +
|
||||||
|
|
|
@ -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()]);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 = '';
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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, ' ');
|
||||||
|
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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` +
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
Loading…
Reference in New Issue