Compare commits

...

10 Commits

Author SHA1 Message Date
Anurag Mittal d90bc4f741
updated data message 2024-07-04 00:51:11 +02:00
Anurag Mittal b9dd7139ad
updated events 2024-07-03 14:14:08 +02:00
Anurag Mittal 6543d9f88d
updated events for active span 2024-07-01 14:22:51 +02:00
Anurag Mittal 44efcd625c
updated authv4 2024-07-01 14:18:20 +02:00
Anurag Mittal c6bb489ade
updated signed headers request 2024-07-01 14:06:20 +02:00
Anurag Mittal 76c4c2b2bb
added details about headers 2024-07-01 13:51:44 +02:00
Anurag Mittal d15d6f8a06
updated authv4 spans 2024-07-01 13:37:06 +02:00
Anurag Mittal 8d40bab08f
updated more auth details 2024-07-01 00:58:41 +02:00
Anurag Mittal 24f6d8374e
added more details to authv4 2024-07-01 00:44:00 +02:00
Anurag Mittal bb3b448757
added auth spans 2024-07-01 00:31:12 +02:00
9 changed files with 503 additions and 508 deletions

View File

@ -68,6 +68,12 @@ function extractParams(
} = oTel; } = oTel;
activeSpan?.addEvent('Arsenal:: entered Arsenal.auth.server.extractParams'); activeSpan?.addEvent('Arsenal:: entered Arsenal.auth.server.extractParams');
return tracer.startActiveSpan('Check validity of request parameters to authenticate using Arsenal', undefined, activeTracerContext, extractParamsSpan => { return tracer.startActiveSpan('Check validity of request parameters to authenticate using Arsenal', undefined, activeTracerContext, extractParamsSpan => {
extractParamsSpan.setAttributes({
'code.lineno': 75,
'code.filename': 'lib/auth/auth.ts',
'code.function': 'extractParams',
'code.url': 'https://github.com/scality/arsenal/blob/892dee6c1333fcc25c88333ee991f02830cb3c51/lib/auth/auth.ts',
});
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: 'v2' |'v4' | null = null; let version: 'v2' |'v4' | null = null;
@ -108,8 +114,7 @@ function extractParams(
activeSpan?.addEvent(`Arsenal:: Identified auth method version: ${version} and method: ${method}`); activeSpan?.addEvent(`Arsenal:: Identified auth method version: ${version} and method: ${method}`);
activeSpan?.addEvent('Arsenal:: Checking if valid request headers and query are used to make request to vault'); activeSpan?.addEvent('Arsenal:: Checking if valid request headers and query are used to make request to vault');
log.trace('identified auth method', { version, authMethod: method }); log.trace('identified auth method', { version, authMethod: method });
extractParamsSpan.end(); return checkFunctions[version][method](request, log, data, awsService, { activeSpan, extractParamsSpan, activeTracerContext, tracer });
return checkFunctions[version][method](request, log, data, awsService, { activeSpan, activeTracerContext, tracer });
} }
// no auth info identified // no auth info identified

View File

@ -6,24 +6,20 @@ import checkRequestExpiry from './checkRequestExpiry';
import algoCheck from './algoCheck'; import algoCheck from './algoCheck';
export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) { export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) {
const { activeSpan, activeTracerContext, tracer } = oTel; const { activeSpan, extractParamsSpan, activeTracerContext, tracer } = oTel;
activeSpan?.addEvent('Entered V2 header auth check'); activeSpan?.addEvent('Entered V2 header auth check');
return tracer.startActiveSpan('V2 Header Auth Check', undefined, activeTracerContext, authCheckSpan => {
log.trace('running header auth check'); log.trace('running header auth check');
activeSpan?.addEvent('Running header auth check'); activeSpan?.addEvent('Running header auth check');
const headers = request.headers; const headers = request.headers;
activeSpan?.addEvent('Extracting security token'); activeSpan?.addEvent('Extracting security token');
const token = headers['x-amz-security-token']; const token = headers['x-amz-security-token'];
if (token && !constants.iamSecurityToken.pattern.test(token)) { if (token && !constants.iamSecurityToken.pattern.test(token)) {
log.debug('invalid security token', { token }); log.debug('invalid security token', { token });
activeSpan.recordException(errors.InvalidToken); activeSpan.recordException(errors.InvalidToken);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.InvalidToken }; return { err: errors.InvalidToken };
} }
activeSpan?.addEvent('Extracted security token'); activeSpan?.addEvent('Extracted security token');
activeSpan?.addEvent('Checking timestamp'); activeSpan?.addEvent('Checking timestamp');
// Check to make sure timestamp is within 15 minutes of current time // Check to make sure timestamp is within 15 minutes of current time
let timestamp = headers['x-amz-date'] ? headers['x-amz-date'] : headers.date; let timestamp = headers['x-amz-date'] ? headers['x-amz-date'] : headers.date;
@ -31,68 +27,61 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
if (!timestamp) { if (!timestamp) {
log.debug('missing or invalid date header', { method: 'auth/v2/headerAuthCheck.check' }); log.debug('missing or invalid date header', { method: 'auth/v2/headerAuthCheck.check' });
activeSpan.recordException(errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header')); activeSpan.recordException(errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header'));
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header') }; return { err: errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header') };
} }
activeSpan?.addEvent('Checked timestamp'); activeSpan?.addEvent('Checked timestamp');
activeSpan?.addEvent('Checking request expiry'); activeSpan?.addEvent('Checking request expiry');
const err = checkRequestExpiry(timestamp, log); const err = checkRequestExpiry(timestamp, log);
if (err) { if (err) {
activeSpan.recordException(err); activeSpan.recordException(err);
authCheckSpan.end(); extractParamsSpan.end();
return { err }; return { err };
} }
activeSpan?.addEvent('Checked request expiry'); activeSpan?.addEvent('Checked request expiry');
activeSpan?.addEvent('Extracting authorization header'); activeSpan?.addEvent('Extracting authorization header');
// Authorization Header should be in the format of 'AWS AccessKey:Signature' // Authorization Header should be in the format of 'AWS AccessKey:Signature'
const authInfo = headers.authorization; const authInfo = headers.authorization;
activeSpan?.addEvent('Extracted authorization header'); activeSpan?.addEvent('Extracted authorization header');
if (!authInfo) { if (!authInfo) {
log.debug('missing authorization security header'); log.debug('missing authorization security header');
activeSpan.recordException(errors.MissingSecurityHeader); activeSpan.recordException(errors.MissingSecurityHeader);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.MissingSecurityHeader }; return { err: errors.MissingSecurityHeader };
} }
const semicolonIndex = authInfo.indexOf(':'); const semicolonIndex = authInfo.indexOf(':');
if (semicolonIndex < 0) { if (semicolonIndex < 0) {
log.debug('invalid authorization header', { authInfo }); log.debug('invalid authorization header', { authInfo });
activeSpan.recordException(errors.InvalidArgument); activeSpan.recordException(errors.InvalidArgument);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.InvalidArgument }; return { err: errors.InvalidArgument };
} }
const accessKey = semicolonIndex > 4 ? authInfo.substring(4, semicolonIndex).trim() : undefined; const accessKey = semicolonIndex > 4 ? authInfo.substring(4, semicolonIndex).trim() : undefined;
if (typeof accessKey !== 'string' || accessKey.length === 0) { if (typeof accessKey !== 'string' || accessKey.length === 0) {
log.trace('invalid authorization header', { authInfo }); log.trace('invalid authorization header', { authInfo });
activeSpan.recordException(errors.MissingSecurityHeader); activeSpan.recordException(errors.MissingSecurityHeader);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.MissingSecurityHeader }; return { err: errors.MissingSecurityHeader };
} }
// @ts-ignore // @ts-ignore
log.addDefaultFields({ accessKey }); log.addDefaultFields({ accessKey });
const signatureFromRequest = authInfo.substring(semicolonIndex + 1).trim(); const signatureFromRequest = authInfo.substring(semicolonIndex + 1).trim();
log.trace('signature from request', { signatureFromRequest }); log.trace('signature from request', { signatureFromRequest });
activeSpan?.addEvent('Extracting signature from request'); activeSpan?.addEvent('Extracting signature from request');
activeSpan?.addEvent('Constructing string to sign');
const stringToSign = constructStringToSign(request, data, log); const stringToSign = constructStringToSign(request, data, log);
log.trace('constructed string to sign', { stringToSign }); log.trace('constructed string to sign', { stringToSign });
activeSpan?.addEvent('Constructed string to sign'); activeSpan?.addEvent('Constructed string to sign v2 headers');
const algo = algoCheck(signatureFromRequest.length); const algo = algoCheck(signatureFromRequest.length);
log.trace('algo for calculating signature', { algo }); log.trace('algo for calculating signature', { algo });
activeSpan?.addEvent('Checked algorithm for calculating signature'); activeSpan?.addEvent('Checked algorithm for calculating signature');
if (algo === undefined) { if (algo === undefined) {
activeSpan.recordException(errors.InvalidArgument); activeSpan.recordException(errors.InvalidArgument);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.InvalidArgument }; return { err: errors.InvalidArgument };
} }
activeSpan?.addEvent('Exiting V2 header auth check'); activeSpan?.addEvent('Exiting V2 header auth check');
authCheckSpan.end(); extractParamsSpan.end();
return { return {
err: null, err: null,
params: { params: {
@ -109,6 +98,5 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
}, },
}, },
}; };
});
} }

View File

@ -5,29 +5,25 @@ import algoCheck from './algoCheck';
import constructStringToSign from './constructStringToSign'; import constructStringToSign from './constructStringToSign';
export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) { export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) {
const { activeSpan, activeTracerContext, tracer } = oTel; const { activeSpan, extractParamsSpan, activeTracerContext, tracer } = oTel;
activeSpan?.addEvent('Entered query auth check'); activeSpan?.addEvent('Entered query auth check');
return tracer.startActiveSpan('Query Auth Check', undefined, activeTracerContext, authCheckSpan => {
log.trace('running query auth check'); log.trace('running query auth check');
activeSpan?.addEvent('Running query auth check'); activeSpan?.addEvent('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');
activeSpan.recordException(errors.NotImplemented); activeSpan.recordException(errors.NotImplemented);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.NotImplemented }; return { err: errors.NotImplemented };
} }
const token = data.SecurityToken; const token = data.SecurityToken;
activeSpan?.addEvent('Extracting security token'); activeSpan?.addEvent('Extracting security token');
if (token && !constants.iamSecurityToken.pattern.test(token)) { if (token && !constants.iamSecurityToken.pattern.test(token)) {
log.debug('invalid security token', { token }); log.debug('invalid security token', { token });
activeSpan.recordException(errors.InvalidToken); activeSpan.recordException(errors.InvalidToken);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.InvalidToken }; return { err: errors.InvalidToken };
} }
activeSpan?.addEvent('Extracted security token'); activeSpan?.addEvent('Extracted security token');
/* /*
Check whether request has expired or if Check whether request has expired or if
expires parameter is more than 604800000 milliseconds expires parameter is more than 604800000 milliseconds
@ -41,62 +37,52 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
if (Number.isNaN(expirationTime)) { if (Number.isNaN(expirationTime)) {
log.debug('invalid expires parameter', { expires: data.Expires }); log.debug('invalid expires parameter', { expires: data.Expires });
activeSpan.recordException(errors.MissingSecurityHeader); activeSpan.recordException(errors.MissingSecurityHeader);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.MissingSecurityHeader }; return { err: errors.MissingSecurityHeader };
} }
activeSpan?.addEvent('Checked expiration time'); activeSpan?.addEvent('Checked expiration time');
const currentTime = Date.now(); const currentTime = Date.now();
const preSignedURLExpiry = process.env.PRE_SIGN_URL_EXPIRY const preSignedURLExpiry = process.env.PRE_SIGN_URL_EXPIRY
&& !Number.isNaN(process.env.PRE_SIGN_URL_EXPIRY) && !Number.isNaN(process.env.PRE_SIGN_URL_EXPIRY)
? Number.parseInt(process.env.PRE_SIGN_URL_EXPIRY, 10) ? Number.parseInt(process.env.PRE_SIGN_URL_EXPIRY, 10)
: constants.defaultPreSignedURLExpiry * 1000; : constants.defaultPreSignedURLExpiry * 1000;
if (expirationTime > currentTime + preSignedURLExpiry) { if (expirationTime > currentTime + preSignedURLExpiry) {
log.debug('expires parameter too far in future', { expires: request.query.Expires }); log.debug('expires parameter too far in future', { expires: request.query.Expires });
activeSpan.recordException(errors.AccessDenied); activeSpan.recordException(errors.AccessDenied);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.AccessDenied }; return { err: errors.AccessDenied };
} }
if (currentTime > expirationTime) { if (currentTime > expirationTime) {
log.debug('current time exceeds expires time', { expires: request.query.Expires }); log.debug('current time exceeds expires time', { expires: request.query.Expires });
activeSpan.recordException(errors.RequestTimeTooSkewed); activeSpan.recordException(errors.RequestTimeTooSkewed);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.RequestTimeTooSkewed }; return { err: errors.RequestTimeTooSkewed };
} }
const accessKey = data.AWSAccessKeyId; const accessKey = data.AWSAccessKeyId;
// @ts-ignore // @ts-ignore
log.addDefaultFields({ accessKey }); log.addDefaultFields({ accessKey });
const signatureFromRequest = decodeURIComponent(data.Signature); const signatureFromRequest = decodeURIComponent(data.Signature);
log.trace('signature from request', { signatureFromRequest }); log.trace('signature from request', { signatureFromRequest });
activeSpan?.addEvent('Extracting signature from request'); activeSpan?.addEvent('Extracting signature from request');
if (!accessKey || !signatureFromRequest) { if (!accessKey || !signatureFromRequest) {
log.debug('invalid access key/signature parameters'); log.debug('invalid access key/signature parameters');
activeSpan.recordException(errors.MissingSecurityHeader); activeSpan.recordException(errors.MissingSecurityHeader);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.MissingSecurityHeader }; return { err: errors.MissingSecurityHeader };
} }
const stringToSign = constructStringToSign(request, data, log); const stringToSign = constructStringToSign(request, data, log);
log.trace('constructed string to sign', { stringToSign }); log.trace('constructed string to sign', { stringToSign });
activeSpan?.addEvent('Constructed string to sign'); activeSpan?.addEvent('Constructed string to sign v2 query');
const algo = algoCheck(signatureFromRequest.length); const algo = algoCheck(signatureFromRequest.length);
log.trace('algo for calculating signature', { algo }); log.trace('algo for calculating signature', { algo });
activeSpan?.addEvent('Checked algorithm for calculating signature'); activeSpan?.addEvent('Checked algorithm for calculating signature');
if (algo === undefined) { if (algo === undefined) {
activeSpan.recordException(errors.InvalidArgument); activeSpan.recordException(errors.InvalidArgument);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.InvalidArgument }; return { err: errors.InvalidArgument };
} }
activeSpan?.addEvent('Exiting query auth check'); activeSpan?.addEvent('Exiting query auth check');
authCheckSpan.end(); extractParamsSpan.end();
return { return {
err: null, err: null,
params: { params: {
@ -112,6 +98,5 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
}, },
}, },
}; };
});
} }

View File

@ -17,7 +17,7 @@ export default function constructStringToSign(params: {
log?: Logger; log?: Logger;
proxyPath?: string; proxyPath?: string;
awsService: string; awsService: string;
}): string | Error { }, oTel?: any,): string | Error {
const { const {
request, request,
signedHeaders, signedHeaders,
@ -29,7 +29,12 @@ export default function constructStringToSign(params: {
proxyPath, proxyPath,
} = params; } = params;
const path = proxyPath || request.path; const path = proxyPath || request.path;
const {
activeSpan,
activeTracerContext,
tracer,
} = oTel;
activeSpan?.addEvent('Constructing canonical request for Authv4');
const canonicalReqResult = createCanonicalRequest({ const canonicalReqResult = createCanonicalRequest({
pHttpVerb: request.method, pHttpVerb: request.method,
pResource: path, pResource: path,
@ -38,8 +43,7 @@ export default function constructStringToSign(params: {
pSignedHeaders: signedHeaders, pSignedHeaders: signedHeaders,
payloadChecksum, payloadChecksum,
service: params.awsService, service: params.awsService,
}); }, oTel);
// TODO Why that line? // TODO Why that line?
// @ts-ignore // @ts-ignore
if (canonicalReqResult instanceof Error) { if (canonicalReqResult instanceof Error) {
@ -51,9 +55,13 @@ export default function constructStringToSign(params: {
if (log) { if (log) {
log.debug('constructed canonicalRequest', { canonicalReqResult }); log.debug('constructed canonicalRequest', { canonicalReqResult });
} }
const createSignatureSpan = tracer.startSpan('Creating signature hash for AuthV4 using crypto sha256');
activeSpan?.addEvent('Creating signature hash for AuthV4 using crypto sha256');
const sha256 = crypto.createHash('sha256'); const sha256 = crypto.createHash('sha256');
const canonicalHex = sha256.update(canonicalReqResult, 'binary') const canonicalHex = sha256.update(canonicalReqResult, 'binary')
.digest('hex'); .digest('hex');
activeSpan?.addEvent('Created signature hash for AuthV4 using crypto sha256');
createSignatureSpan.end();
const stringToSign = `AWS4-HMAC-SHA256\n${timestamp}\n` + const stringToSign = `AWS4-HMAC-SHA256\n${timestamp}\n` +
`${credentialScope}\n${canonicalHex}`; `${credentialScope}\n${canonicalHex}`;
return stringToSign; return stringToSign;

View File

@ -19,8 +19,16 @@ export default function createCanonicalRequest(
pSignedHeaders: any; pSignedHeaders: any;
service: string; service: string;
payloadChecksum: string; payloadChecksum: string;
} },
oTel?: any,
) { ) {
const {
activeSpan,
activeTracerContext,
tracer,
} = oTel;
activeSpan?.addEvent('Entered createCanonicalRequest');
const pHttpVerb = params.pHttpVerb; const pHttpVerb = params.pHttpVerb;
const pResource = params.pResource; const pResource = params.pResource;
const pQuery = params.pQuery; const pQuery = params.pQuery;
@ -28,35 +36,34 @@ export default function createCanonicalRequest(
const pSignedHeaders = params.pSignedHeaders; const pSignedHeaders = params.pSignedHeaders;
const service = params.service; const service = params.service;
let payloadChecksum = params.payloadChecksum; let payloadChecksum = params.payloadChecksum;
const payloadChecksumSpan = tracer.startSpan('ComputePayloadChecksum');
if (!payloadChecksum) { if (!payloadChecksum) {
if (pHttpVerb === 'GET') { if (pHttpVerb === 'GET') {
payloadChecksum = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b' + payloadChecksum = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b' +
'934ca495991b7852b855'; '934ca495991b7852b855';
} else if (pHttpVerb === 'POST') { } else if (pHttpVerb === 'POST') {
let notEncodeStar = false; let notEncodeStar = false;
// The java sdk does not encode the '*' parameter to compute the
// signature, if the user-agent is recognized, we need to keep
// the plain '*' as well.
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, undefined, undefined, { let payload = queryString.stringify(pQuery, undefined, undefined, {
encodeURIComponent: input => awsURIencode(input, true, encodeURIComponent: input => awsURIencode(input, true, notEncodeStar),
notEncodeStar),
}); });
payload = payload.replace(/%20/g, '+'); payload = payload.replace(/%20/g, '+');
payloadChecksum = crypto.createHash('sha256') payloadChecksum = crypto.createHash('sha256')
.update(payload, 'binary').digest('hex').toLowerCase(); .update(payload, 'binary').digest('hex').toLowerCase();
} }
} }
payloadChecksumSpan.end();
const canonicalURISpan = tracer.startSpan('ComputeCanonicalURI');
const canonicalURI = !!pResource ? awsURIencode(pResource, false) : '/'; const canonicalURI = !!pResource ? awsURIencode(pResource, false) : '/';
canonicalURISpan.end();
// canonical query string const canonicalQueryStrSpan = tracer.startSpan('ComputeCanonicalQueryStr');
let canonicalQueryStr = ''; let canonicalQueryStr = '';
if (pQuery && !((service === 'iam' || service === 'ring' || if (pQuery && !((service === 'iam' || service === 'ring' || service === 'sts') && pHttpVerb === 'POST')) {
service === 'sts') &&
pHttpVerb === 'POST')) {
const sortedQueryParams = Object.keys(pQuery).sort().map(key => { const sortedQueryParams = Object.keys(pQuery).sort().map(key => {
const encodedKey = awsURIencode(key); const encodedKey = awsURIencode(key);
const value = pQuery[key] ? awsURIencode(pQuery[key]) : ''; const value = pQuery[key] ? awsURIencode(pQuery[key]) : '';
@ -64,32 +71,54 @@ export default function createCanonicalRequest(
}); });
canonicalQueryStr = sortedQueryParams.join('&'); canonicalQueryStr = sortedQueryParams.join('&');
} }
canonicalQueryStrSpan.end();
// signed headers const signedHeadersSpan = tracer.startSpan('SortSignedHeadersAlphabetically');
activeSpan?.addEvent('Splitting signed headers using deliminator: ;');
const signedHeadersList = pSignedHeaders.split(';'); const signedHeadersList = pSignedHeaders.split(';');
activeSpan?.addEvent('Split signed headers using ; as deliminator');
activeSpan?.addEvent('Sorting signed headers alphabetically');
signedHeadersList.sort((a: any, b: any) => a.localeCompare(b)); signedHeadersList.sort((a: any, b: any) => a.localeCompare(b));
activeSpan?.addEvent('Sorted signed headers alphabetically');
activeSpan?.addEvent('Joining signed headers using deliminator: ;');
const signedHeaders = signedHeadersList.join(';'); const signedHeaders = signedHeadersList.join(';');
activeSpan?.addEvent('Joined signed headers using ; as deliminator');
activeSpan.setAttributes({
'signedHeaders.request': pSignedHeaders,
'signedHeaders.request.authv4': signedHeaders,
});
signedHeadersSpan.setAttributes({
'signedHeaders.request': pSignedHeaders,
'signedHeaders.request.authv4': signedHeaders,
'code.url': 'https://github.com/scality/arsenal/blob/c6bb489adeb7419fdbcdf01db2b46a593747530d/lib/auth/v4/createCanonicalRequest.ts#L76',
'code.function': 'createCanonicalRequest',
'code.lineno': 76,
'code.filename': 'lib/auth/v4/createCanonicalRequest.ts',
});
signedHeadersSpan.end();
// canonical headers const canonicalHeadersListSpan = tracer.startSpan('FormatHeadersToMatch CanonicalHeadersList');
const canonicalHeadersList = signedHeadersList.map((signedHeader: any) => { 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, ' ');
return `${signedHeader}:${trimmedHeader}\n`; return `${signedHeader}:${trimmedHeader}\n`;
} }
// nginx will strip the actual expect header so add value of
// header back here if it was included as a signed header
if (signedHeader === 'expect') { if (signedHeader === 'expect') {
return `${signedHeader}:100-continue\n`; return `${signedHeader}:100-continue\n`;
} }
// handle case where signed 'header' is actually query param
return `${signedHeader}:${pQuery[signedHeader]}\n`; return `${signedHeader}:${pQuery[signedHeader]}\n`;
}); });
canonicalHeadersListSpan.end();
const canonicalHeadersSpan = tracer.startSpan('JoinAllCanonicalHeaders using no deliminator');
const canonicalHeaders = canonicalHeadersList.join(''); const canonicalHeaders = canonicalHeadersList.join('');
canonicalHeadersSpan.end();
const canonicalRequestSpan = tracer.startSpan('ConstructCanonicalRequest');
const canonicalRequest = `${pHttpVerb}\n${canonicalURI}\n` + const canonicalRequest = `${pHttpVerb}\n${canonicalURI}\n` +
`${canonicalQueryStr}\n${canonicalHeaders}\n` + `${canonicalQueryStr}\n${canonicalHeaders}\n` +
`${signedHeaders}\n${payloadChecksum}`; `${signedHeaders}\n${payloadChecksum}`;
canonicalRequestSpan.end();
return canonicalRequest; return canonicalRequest;
} }

View File

@ -22,46 +22,41 @@ import {
* @param awsService - Aws service ('iam' or 's3') * @param awsService - Aws service ('iam' or 's3')
*/ */
export function check(request: any, log: Logger, data: { [key: string]: string }, awsService: string, oTel: any) { export function check(request: any, log: Logger, data: { [key: string]: string }, awsService: string, oTel: any) {
const { activeSpan, activeTracerContext, tracer } = oTel; const { activeSpan, extractParamsSpan, activeTracerContext, tracer } = oTel;
activeSpan?.addEvent('Entered V4 header auth check'); activeSpan?.addEvent('Entered V4 header auth check');
return tracer.startActiveSpan('V4 Header Auth Check', undefined, activeTracerContext, authCheckSpan => {
log.trace('running header auth check'); log.trace('running header auth check');
activeSpan?.addEvent('Extracting security token'); activeSpan?.addEvent('Extracting security token');
const token = request.headers['x-amz-security-token']; const token = request.headers['x-amz-security-token'];
if (token && !constants.iamSecurityToken.pattern.test(token)) { if (token && !constants.iamSecurityToken.pattern.test(token)) {
log.debug('invalid security token', { token }); log.debug('invalid security token', { token });
activeSpan.recordException(errors.InvalidToken); activeSpan.recordException(errors.InvalidToken);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.InvalidToken }; return { err: errors.InvalidToken };
} }
activeSpan?.addEvent('Extracted security token'); activeSpan?.addEvent('Extracted security token');
activeSpan?.addEvent('Extracting authorization header'); activeSpan?.addEvent('Extracting authorization header');
const authHeader = request.headers.authorization; const authHeader = request.headers.authorization;
if (!authHeader) { if (!authHeader) {
log.debug('missing authorization header'); log.debug('missing authorization header');
activeSpan.recordException(errors.MissingSecurityHeader); activeSpan.recordException(errors.MissingSecurityHeader);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.MissingSecurityHeader }; return { err: errors.MissingSecurityHeader };
} }
activeSpan?.addEvent('Extracted authorization header'); activeSpan?.addEvent('Extracted authorization header');
activeSpan?.addEvent('Extracting auth header items'); activeSpan?.addEvent('Extracting auth header items');
const authHeaderItems = extractAuthItems(authHeader, log); const authHeaderItems = extractAuthItems(authHeader, log);
if (Object.keys(authHeaderItems).length < 3) { if (Object.keys(authHeaderItems).length < 3) {
log.debug('invalid authorization header', { authHeader }); log.debug('invalid authorization header', { authHeader });
activeSpan.recordException(errors.InvalidArgument); activeSpan.recordException(errors.InvalidArgument);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.InvalidArgument }; return { err: errors.InvalidArgument };
} }
activeSpan?.addEvent('Extracted auth header items'); activeSpan?.addEvent('Extracted auth header items');
const payloadChecksum = request.headers['x-amz-content-sha256']; const payloadChecksum = request.headers['x-amz-content-sha256'];
if (!payloadChecksum && awsService !== 'iam') { if (!payloadChecksum && awsService !== 'iam') {
log.debug('missing payload checksum'); log.debug('missing payload checksum');
activeSpan.recordException(errors.MissingSecurityHeader); activeSpan.recordException(errors.MissingSecurityHeader);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.MissingSecurityHeader }; return { err: errors.MissingSecurityHeader };
} }
if (payloadChecksum === 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD') { if (payloadChecksum === 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD') {
@ -69,31 +64,27 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
if (request.method !== 'PUT') { if (request.method !== 'PUT') {
log.debug('streaming v4 auth for put only', { method: 'auth/v4/headerAuthCheck.check' }); log.debug('streaming v4 auth for put only', { method: 'auth/v4/headerAuthCheck.check' });
activeSpan.recordException(errors.InvalidArgument); activeSpan.recordException(errors.InvalidArgument);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.InvalidArgument }; return { err: errors.InvalidArgument };
} }
if (!request.headers['x-amz-decoded-content-length']) { if (!request.headers['x-amz-decoded-content-length']) {
activeSpan.recordException(errors.MissingSecurityHeader); activeSpan.recordException(errors.MissingSecurityHeader);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.MissingSecurityHeader }; return { err: errors.MissingSecurityHeader };
} }
} }
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!;
activeSpan.addEvent('Checking if signed headers are complete'); activeSpan.addEvent('Checking if signed headers are complete');
if (!areSignedHeadersComplete(signedHeaders, request.headers)) { if (!areSignedHeadersComplete(signedHeaders, request.headers)) {
log.debug('signedHeaders are incomplete', { signedHeaders }); log.debug('signedHeaders are incomplete', { signedHeaders });
activeSpan.recordException(errors.AccessDenied); activeSpan.recordException(errors.AccessDenied);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.AccessDenied }; return { err: errors.AccessDenied };
} }
activeSpan.addEvent('Signed headers are complete'); activeSpan.addEvent('Signed headers are complete');
let timestamp: string | undefined; let timestamp: string | undefined;
// check request timestamp // check request timestamp
activeSpan.addEvent('Checking request timestamp'); activeSpan.addEvent('Checking request timestamp');
@ -111,17 +102,16 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
if (!timestamp) { if (!timestamp) {
log.debug('missing or invalid date header', { method: 'auth/v4/headerAuthCheck.check' }); log.debug('missing or invalid date header', { method: 'auth/v4/headerAuthCheck.check' });
activeSpan.recordException(errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header')); activeSpan.recordException(errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header'));
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header') }; return { err: errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header') };
} }
activeSpan.addEvent('Request timestamp is valid'); activeSpan.addEvent('Request timestamp is valid');
activeSpan.addEvent('Validating credentials'); activeSpan.addEvent('Validating credentials');
const validationResult = validateCredentials(credentialsArr, timestamp, log); const validationResult = validateCredentials(credentialsArr, timestamp, log);
if (validationResult instanceof Error) { if (validationResult instanceof Error) {
log.debug('credentials in improper format', { credentialsArr, timestamp, validationResult }); log.debug('credentials in improper format', { credentialsArr, timestamp, validationResult });
activeSpan.recordException(validationResult); activeSpan.recordException(validationResult);
authCheckSpan.end(); extractParamsSpan.end();
return { err: validationResult }; return { err: validationResult };
} }
activeSpan.addEvent('Credentials are valid'); activeSpan.addEvent('Credentials are valid');
@ -131,7 +121,6 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
const service = credentialsArr[3]; const service = credentialsArr[3];
const accessKey = credentialsArr.shift(); const accessKey = credentialsArr.shift();
const credentialScope = credentialsArr.join('/'); const credentialScope = credentialsArr.join('/');
// In AWS Signature Version 4, the signing key is valid for up to seven days // In AWS Signature Version 4, the signing key is valid for up to seven days
// (see Introduction to Signing Requests. // (see Introduction to Signing Requests.
// Therefore, a signature is also valid for up to seven days or // Therefore, a signature is also valid for up to seven days or
@ -143,18 +132,16 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
// TODO: When implementing bucket policies, // TODO: When implementing bucket policies,
// note that expiration can be shortened so // note that expiration can be shortened so
// expiry is as set out in the policy. // expiry is as set out in the policy.
// 15 minutes in seconds // 15 minutes in seconds
activeSpan.addEvent('checking if signature is expired') activeSpan.addEvent('checking if signature is expired')
const expiry = (15 * 60); const expiry = (15 * 60);
const isTimeSkewed = checkTimeSkew(timestamp, expiry, log); const isTimeSkewed = checkTimeSkew(timestamp, expiry, log);
if (isTimeSkewed) { if (isTimeSkewed) {
activeSpan.recordException(errors.RequestTimeTooSkewed); activeSpan.recordException(errors.RequestTimeTooSkewed);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.RequestTimeTooSkewed }; return { err: errors.RequestTimeTooSkewed };
} }
activeSpan.addEvent('signature is not expired'); activeSpan.addEvent('signature is not expired');
activeSpan.addEvent('Constructing string to sign'); activeSpan.addEvent('Constructing string to sign');
const stringToSign = constructStringToSign({ const stringToSign = constructStringToSign({
log, log,
@ -165,18 +152,16 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
timestamp, timestamp,
payloadChecksum, payloadChecksum,
awsService: service, awsService: service,
}); }, oTel);
log.trace('constructed stringToSign', { stringToSign }); log.trace('constructed stringToSign', { stringToSign });
if (stringToSign instanceof Error) { if (stringToSign instanceof Error) {
activeSpan.recordException(stringToSign); activeSpan.recordException(stringToSign);
authCheckSpan.end(); extractParamsSpan.end();
return { err: stringToSign }; return { err: stringToSign };
} }
activeSpan.addEvent('Constructed string to sign v4 headers');
activeSpan.addEvent('Constructed string to sign');
activeSpan.addEvent('Exiting V4 header auth check'); activeSpan.addEvent('Exiting V4 header auth check');
authCheckSpan.end(); extractParamsSpan.end();
return { return {
err: null, err: null,
params: { params: {
@ -197,6 +182,5 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
}, },
}, },
}; };
});
} }

View File

@ -15,17 +15,17 @@ import { areSignedHeadersComplete } from './validateInputs';
export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) { export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) {
const { const {
activeSpan, activeSpan,
extractParamsSpan,
activeTracerContext, activeTracerContext,
tracer, tracer,
} = oTel; } = oTel;
activeSpan?.addEvent('Arsenal:: entered Arsenal.auth.v4.queryAuthCheck'); activeSpan?.addEvent('Arsenal:: entered Arsenal.auth.v4.queryAuthCheck');
return tracer.startActiveSpan('Arsenal::Arsenal.auth.v4.queryAuthCheck', undefined, activeTracerContext, authCheckSpan => {
activeSpan?.addEvent('Arsenal:: extracting query parameters') activeSpan?.addEvent('Arsenal:: extracting query parameters')
const authParams = extractQueryParams(data, log); const authParams = extractQueryParams(data, log);
activeSpan?.addEvent('Arsenal:: extracting query params'); activeSpan?.addEvent('Arsenal:: extracting query params');
if (Object.keys(authParams).length !== 5) { if (Object.keys(authParams).length !== 5) {
activeSpan.recordException(errors.InvalidArgument); activeSpan.recordException(errors.InvalidArgument);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.InvalidArgument }; return { err: errors.InvalidArgument };
} }
@ -35,7 +35,7 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
if (token && !constants.iamSecurityToken.pattern.test(token)) { if (token && !constants.iamSecurityToken.pattern.test(token)) {
log.debug('invalid security token', { token }); log.debug('invalid security token', { token });
activeSpan.recordException(errors.InvalidToken); activeSpan.recordException(errors.InvalidToken);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.InvalidToken }; return { err: errors.InvalidToken };
} }
@ -48,7 +48,7 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
if (!areSignedHeadersComplete(signedHeaders, request.headers)) { if (!areSignedHeadersComplete(signedHeaders, request.headers)) {
log.debug('signedHeaders are incomplete', { signedHeaders }); log.debug('signedHeaders are incomplete', { signedHeaders });
activeSpan.recordException(errors.AccessDenied); activeSpan.recordException(errors.AccessDenied);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.AccessDenied }; return { err: errors.AccessDenied };
} }
@ -58,7 +58,7 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
log.debug('credentials in improper format', { credential, log.debug('credentials in improper format', { credential,
timestamp, validationResult }); timestamp, validationResult });
activeSpan.recordException(validationResult); activeSpan.recordException(validationResult);
authCheckSpan.end(); extractParamsSpan.end();
return { err: validationResult }; return { err: validationResult };
} }
const accessKey = credential[0]; const accessKey = credential[0];
@ -70,7 +70,7 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
const isTimeSkewed = checkTimeSkew(timestamp, expiry, log); const isTimeSkewed = checkTimeSkew(timestamp, expiry, log);
if (isTimeSkewed) { if (isTimeSkewed) {
activeSpan.recordException(errors.RequestTimeTooSkewed); activeSpan.recordException(errors.RequestTimeTooSkewed);
authCheckSpan.end(); extractParamsSpan.end();
return { err: errors.RequestTimeTooSkewed }; return { err: errors.RequestTimeTooSkewed };
} }
@ -89,6 +89,7 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
// building string to sign // building string to sign
const payloadChecksum = 'UNSIGNED-PAYLOAD'; const payloadChecksum = 'UNSIGNED-PAYLOAD';
activeSpan?.addEvent('Constructing string to sign');
const stringToSign = constructStringToSign({ const stringToSign = constructStringToSign({
log, log,
request, request,
@ -99,15 +100,16 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
credentialScope: credentialScope:
`${scopeDate}/${region}/${service}/${requestType}`, `${scopeDate}/${region}/${service}/${requestType}`,
awsService: service, awsService: service,
}); }, oTel);
activeSpan?.addEvent('Constructed string to sign v4 query');
if (stringToSign instanceof Error) { if (stringToSign instanceof Error) {
activeSpan.recordException(stringToSign); activeSpan.recordException(stringToSign);
authCheckSpan.end(); extractParamsSpan.end();
return { err: stringToSign }; return { err: stringToSign };
} }
log.trace('constructed stringToSign', { stringToSign }); log.trace('constructed stringToSign', { stringToSign });
activeSpan.addEvent('Arsenal:: exiting Arsenal.auth.v4.queryAuthCheck'); activeSpan.addEvent('Arsenal:: exiting Arsenal.auth.v4.queryAuthCheck');
authCheckSpan.end(); extractParamsSpan.end();
return { return {
err: null, err: null,
params: { params: {
@ -125,5 +127,4 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
}, },
}, },
}; };
});
} }

View File

@ -23,11 +23,6 @@ export default function routerGET(
} = dataRetrievalParams; } = dataRetrievalParams;
return tracer.startActiveSpan('Arsenal:: Performing Get API related operations using Cloudserver, Vault and Metadata', undefined, activeTracerContext, cloudserverApiSpan => { return tracer.startActiveSpan('Arsenal:: Performing Get API related operations using Cloudserver, Vault and Metadata', undefined, activeTracerContext, cloudserverApiSpan => {
activeSpan.addEvent('Request validated, routing request using routeGET() in arsenal'); activeSpan.addEvent('Request validated, routing request using routeGET() in arsenal');
cloudserverApiSpan.setAttributes({
'code.lineno': 9,
'code.filename': 'lib/s3routes/routes/routeGET.ts',
'code.function': 'routerGET()',
})
activeSpan.addEvent('Detecting which API to route to using arsenal routeGET()') activeSpan.addEvent('Detecting which API to route to using arsenal routeGET()')
log.debug('routing request', { method: 'routerGET' }); log.debug('routing request', { method: 'routerGET' });

View File

@ -638,7 +638,7 @@ export function responseStreamData(
}, },
} = retrieveDataParams; } = retrieveDataParams;
activeSpan.addEvent('Request processed, getting Data from sproxyd'); activeSpan.addEvent('Request processed, getting Data from sproxyd');
return tracer.startActiveSpan('Getting Data From RING', undefined, activeTracerContext, sproxydSpan => { return tracer.startActiveSpan('Getting Object Data from RING', undefined, activeTracerContext, sproxydSpan => {
sproxydSpan.setAttributes({ sproxydSpan.setAttributes({
'code.function': 'Arsenal:: responseStreamData()', 'code.function': 'Arsenal:: responseStreamData()',
'code.filepath': 'lib/s3routes/routesUtils.js', 'code.filepath': 'lib/s3routes/routesUtils.js',