Compare commits

...

27 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
Anurag Mittal 892dee6c13
fixup 2024-06-28 18:06:48 +02:00
Anurag Mittal 6876861b5d
expanded auth 2024-06-28 17:58:35 +02:00
Anurag Mittal ff66b13a1a
updated vault method 2024-06-28 17:06:29 +02:00
Anurag Mittal a1ac267b48
fixup 2024-06-28 05:49:52 +02:00
Anurag Mittal a12716ffe3
updated s3 span names 2024-06-27 19:55:49 +02:00
Anurag Mittal 05173de018
updated getting data spans 2024-06-26 00:35:22 +02:00
Anurag Mittal b75d73fe40
updated parent span name 2024-06-25 22:57:30 +02:00
Anurag Mittal 052113c0ff
deleteroute fixup for rb bucket 2024-06-19 01:08:09 +02:00
Anurag Mittal 5af62b174d
removed extra, set sttributed 2024-06-19 00:00:38 +02:00
Anurag Mittal 4e158a25b6
routes.js attribute fixup 2024-06-18 23:59:17 +02:00
Anurag Mittal 8fd1c42d8d
reveted debug statements 2024-06-18 17:17:49 +02:00
Anurag Mittal f77da8a8a2
added Metadata list objects instrumentation 2024-06-18 17:09:10 +02:00
Anurag Mittal a291fbc10b
fixup 2024-06-18 16:49:49 +02:00
Anurag Mittal 4a76a9c5f6
S3C-8896-add-instrumentation-for-list-object-md-api 2024-06-18 16:45:15 +02:00
Anurag Mittal 068570bc26
S3C-8896: added events in doAuth 2024-06-18 14:17:42 +02:00
Anurag Mittal b7122681c2
S3C-8893: instrumented all routes 2024-06-18 09:47:52 +02:00
Anurag Mittal 3f7eb4c31d
S3C-8896-add-tracing-for-get-route 2024-06-17 20:34:54 +02:00
16 changed files with 1661 additions and 792 deletions

View File

@ -58,49 +58,71 @@ function extractParams(
request: any, request: any,
log: Logger, log: Logger,
awsService: string, awsService: string,
data: { [key: string]: string } data: { [key: string]: string },
oTel?: any,
) { ) {
log.trace('entered', { method: 'Arsenal.auth.server.extractParams' }); const {
const authHeader = request.headers.authorization; activeSpan,
let version: 'v2' |'v4' | null = null; activeTracerContext,
let method: 'query' | 'headers' | null = null; tracer,
} = oTel;
// Identify auth version and method to dispatch to the right check function activeSpan?.addEvent('Arsenal:: entered Arsenal.auth.server.extractParams');
if (authHeader) { return tracer.startActiveSpan('Check validity of request parameters to authenticate using Arsenal', undefined, activeTracerContext, extractParamsSpan => {
method = 'headers'; extractParamsSpan.setAttributes({
// TODO: Check for security token header to handle temporary security 'code.lineno': 75,
// credentials 'code.filename': 'lib/auth/auth.ts',
if (authHeader.startsWith('AWS ')) { 'code.function': 'extractParams',
'code.url': 'https://github.com/scality/arsenal/blob/892dee6c1333fcc25c88333ee991f02830cb3c51/lib/auth/auth.ts',
});
log.trace('entered', { method: 'Arsenal.auth.server.extractParams' });
const authHeader = request.headers.authorization;
let version: 'v2' |'v4' | null = null;
let method: 'query' | 'headers' | null = null;
activeSpan?.addEvent('Arsenal:: Identifying auth version from authentication header');
// Identify auth version and method to dispatch to the right check function
if (authHeader) {
method = 'headers';
// TODO: Check for security token header to handle temporary security
// credentials
if (authHeader.startsWith('AWS ')) {
version = 'v2';
} else if (authHeader.startsWith('AWS4')) {
version = 'v4';
} else {
log.trace('invalid authorization security header',
{ header: authHeader });
extractParamsSpan.end();
return { err: errors.AccessDenied };
}
} else if (data.Signature) {
method = 'query';
version = 'v2'; version = 'v2';
} else if (authHeader.startsWith('AWS4')) { } else if (data['X-Amz-Algorithm']) {
method = 'query';
version = 'v4'; version = 'v4';
} else {
log.trace('invalid authorization security header',
{ header: authHeader });
return { err: errors.AccessDenied };
} }
} else if (data.Signature) { activeSpan?.addEvent(`Arsenal::Auth versions identified: ${version}`);
method = 'query'; // Here, either both values are set, or none is set
version = 'v2'; if (version !== null && method !== null) {
} else if (data['X-Amz-Algorithm']) { if (!checkFunctions[version] || !checkFunctions[version][method]) {
method = 'query'; activeSpan?.recordException(errors.NotImplemented);
version = 'v4'; log.trace('invalid auth version or method',
} { version, authMethod: method });
extractParamsSpan.end();
// Here, either both values are set, or none is set return { err: errors.NotImplemented };
if (version !== null && method !== null) { }
if (!checkFunctions[version] || !checkFunctions[version][method]) { activeSpan?.addEvent(`Arsenal:: Identified auth method version: ${version} and method: ${method}`);
log.trace('invalid auth version or method', activeSpan?.addEvent('Arsenal:: Checking if valid request headers and query are used to make request to vault');
{ version, authMethod: method }); log.trace('identified auth method', { version, authMethod: method });
return { err: errors.NotImplemented }; return checkFunctions[version][method](request, log, data, awsService, { activeSpan, extractParamsSpan, activeTracerContext, tracer });
} }
log.trace('identified auth method', { version, authMethod: method });
return checkFunctions[version][method](request, log, data, awsService);
}
// no auth info identified // no auth info identified
log.debug('assuming public user'); log.debug('assuming public user');
return { err: null, params: publicUserInfo }; extractParamsSpan.end();
activeSpan?.addEvent(`Arsenal:: Identified as public user`);
return { err: null, params: publicUserInfo };
});
} }
/** /**
@ -119,15 +141,30 @@ function doAuth(
log: Logger, log: Logger,
cb: (err: Error | null, data?: any) => void, cb: (err: Error | null, data?: any) => void,
awsService: string, awsService: string,
requestContexts: any[] | null requestContexts: any[] | null,
oTel?: any,
) { ) {
const res = extractParams(request, log, awsService, request.query); const {
activeSpan,
activeTracerContext,
tracer,
} = oTel;
activeSpan?.addEvent('Arsenal:: Routing request using doAuth() in arsenal');
activeSpan?.addEvent('Arsenal:: Extracting auth parameters and check validity of request parameters to authenticate');
const start = process.hrtime.bigint();
const res = extractParams(request, log, awsService, request.query, oTel);
const end = process.hrtime.bigint();
const duration = Number(end - start) / 1e6;
activeSpan?.addEvent(`Arsenal:: It took ${duration.toFixed(3)} ms to extract auth parameters and to check validity of request parameters to authenticate`);
if (res.err) { if (res.err) {
activeSpan?.recordException(res.err);
return cb(res.err); return cb(res.err);
} else if (res.params instanceof AuthInfo) { } else if (res.params instanceof AuthInfo) {
activeSpan?.addEvent('Arsenal:: Auth info already in the params, do not need to make a request to cloudserver');
return cb(null, res.params); return cb(null, res.params);
} }
if (requestContexts) { if (requestContexts) {
activeSpan?.addEvent('Arsenal:: Setting auth info in requestContexts');
requestContexts.forEach((requestContext) => { requestContexts.forEach((requestContext) => {
const { params } = res const { params } = res
if ('data' in params) { if ('data' in params) {
@ -140,6 +177,7 @@ function doAuth(
} }
} }
}); });
activeSpan?.addEvent('Arsenal:: Auth info set in requestContexts');
} }
// Corner cases managed, we're left with normal auth // Corner cases managed, we're left with normal auth
@ -147,10 +185,12 @@ function doAuth(
// @ts-ignore // @ts-ignore
res.params.log = log; res.params.log = log;
if (res.params.version === 2) { if (res.params.version === 2) {
activeSpan?.addEvent('Arsenal:: Sending AuthV2 call to vault');
// @ts-ignore // @ts-ignore
return vault!.authenticateV2Request(res.params, requestContexts, cb); return vault!.authenticateV2Request(res.params, requestContexts, cb);
} }
if (res.params.version === 4) { if (res.params.version === 4) {
activeSpan?.addEvent('Arsenal:: Sending AuthV4 call to vault');
// @ts-ignore // @ts-ignore
return vault!.authenticateV4Request(res.params, requestContexts, cb); return vault!.authenticateV4Request(res.params, requestContexts, cb);
} }
@ -158,6 +198,7 @@ function doAuth(
log.error('authentication method not found', { log.error('authentication method not found', {
method: 'Arsenal.auth.doAuth', method: 'Arsenal.auth.doAuth',
}); });
activeSpan?.recordException(errors.InternalError);
return cb(errors.InternalError); return cb(errors.InternalError);
} }

View File

@ -5,64 +5,83 @@ import constructStringToSign from './constructStringToSign';
import checkRequestExpiry from './checkRequestExpiry'; import checkRequestExpiry from './checkRequestExpiry';
import algoCheck from './algoCheck'; import algoCheck from './algoCheck';
export function check(request: any, log: Logger, data: { [key: string]: string }) { export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) {
const { activeSpan, extractParamsSpan, activeTracerContext, tracer } = oTel;
activeSpan?.addEvent('Entered V2 header auth check');
log.trace('running header auth check'); log.trace('running header auth check');
activeSpan?.addEvent('Running header auth check');
const headers = request.headers; const headers = request.headers;
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);
extractParamsSpan.end();
return { err: errors.InvalidToken }; return { err: errors.InvalidToken };
} }
activeSpan?.addEvent('Extracted security token');
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'] ? let timestamp = headers['x-amz-date'] ? headers['x-amz-date'] : headers.date;
headers['x-amz-date'] : headers.date;
timestamp = Date.parse(timestamp); timestamp = Date.parse(timestamp);
if (!timestamp) { if (!timestamp) {
log.debug('missing or invalid date header', log.debug('missing or invalid date header', { method: 'auth/v2/headerAuthCheck.check' });
{ method: 'auth/v2/headerAuthCheck.check' }); activeSpan.recordException(errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header'));
return { err: errors.AccessDenied. extractParamsSpan.end();
customizeDescription('Authentication requires a valid Date or ' + return { err: errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header') };
'x-amz-date header') };
} }
activeSpan?.addEvent('Checked timestamp');
activeSpan?.addEvent('Checking request expiry');
const err = checkRequestExpiry(timestamp, log); const err = checkRequestExpiry(timestamp, log);
if (err) { if (err) {
activeSpan.recordException(err);
extractParamsSpan.end();
return { err }; return { err };
} }
activeSpan?.addEvent('Checked request expiry');
// Authorization Header should be activeSpan?.addEvent('Extracting authorization header');
// 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');
if (!authInfo) { if (!authInfo) {
log.debug('missing authorization security header'); log.debug('missing authorization security header');
activeSpan.recordException(errors.MissingSecurityHeader);
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);
extractParamsSpan.end();
return { err: errors.InvalidArgument }; return { err: errors.InvalidArgument };
} }
const accessKey = semicolonIndex > 4 ? const accessKey = semicolonIndex > 4 ? authInfo.substring(4, semicolonIndex).trim() : undefined;
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);
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('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 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');
if (algo === undefined) { if (algo === undefined) {
activeSpan.recordException(errors.InvalidArgument);
extractParamsSpan.end();
return { err: errors.InvalidArgument }; return { err: errors.InvalidArgument };
} }
activeSpan?.addEvent('Exiting V2 header auth check');
extractParamsSpan.end();
return { return {
err: null, err: null,
params: { params: {
@ -80,3 +99,4 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
}, },
}; };
} }

View File

@ -4,19 +4,26 @@ 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: any, log: Logger, data: { [key: string]: string }) { export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) {
const { activeSpan, extractParamsSpan, activeTracerContext, tracer } = oTel;
activeSpan?.addEvent('Entered query auth check');
log.trace('running query auth check'); log.trace('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);
extractParamsSpan.end();
return { err: errors.NotImplemented }; return { err: errors.NotImplemented };
} }
const token = data.SecurityToken; const token = data.SecurityToken;
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);
extractParamsSpan.end();
return { err: errors.InvalidToken }; return { err: errors.InvalidToken };
} }
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
@ -25,47 +32,57 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
multiply by 1000 to obtain multiply by 1000 to obtain
milliseconds to compare to Date.now() milliseconds to compare to Date.now()
*/ */
activeSpan?.addEvent('Checking expiration time');
const expirationTime = parseInt(data.Expires, 10) * 1000; const expirationTime = parseInt(data.Expires, 10) * 1000;
if (Number.isNaN(expirationTime)) { if (Number.isNaN(expirationTime)) {
log.debug('invalid expires parameter', log.debug('invalid expires parameter', { expires: data.Expires });
{ expires: data.Expires }); activeSpan.recordException(errors.MissingSecurityHeader);
extractParamsSpan.end();
return { err: errors.MissingSecurityHeader }; return { err: errors.MissingSecurityHeader };
} }
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', log.debug('expires parameter too far in future', { expires: request.query.Expires });
{ expires: request.query.Expires }); activeSpan.recordException(errors.AccessDenied);
extractParamsSpan.end();
return { err: errors.AccessDenied }; return { err: errors.AccessDenied };
} }
if (currentTime > expirationTime) { if (currentTime > expirationTime) {
log.debug('current time exceeds expires time', log.debug('current time exceeds expires time', { expires: request.query.Expires });
{ expires: request.query.Expires }); activeSpan.recordException(errors.RequestTimeTooSkewed);
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');
if (!accessKey || !signatureFromRequest) { if (!accessKey || !signatureFromRequest) {
log.debug('invalid access key/signature parameters'); log.debug('invalid access key/signature parameters');
activeSpan.recordException(errors.MissingSecurityHeader);
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 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');
if (algo === undefined) { if (algo === undefined) {
activeSpan.recordException(errors.InvalidArgument);
extractParamsSpan.end();
return { err: errors.InvalidArgument }; return { err: errors.InvalidArgument };
} }
activeSpan?.addEvent('Exiting query auth check');
extractParamsSpan.end();
return { return {
err: null, err: null,
params: { params: {
@ -82,3 +99,4 @@ 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

@ -21,70 +21,78 @@ import {
* POST request * POST request
* @param awsService - Aws service ('iam' or 's3') * @param awsService - Aws service ('iam' or 's3')
*/ */
export function check( export function check(request: any, log: Logger, data: { [key: string]: string }, awsService: string, oTel: any) {
request: any, const { activeSpan, extractParamsSpan, activeTracerContext, tracer } = oTel;
log: Logger, activeSpan?.addEvent('Entered V4 header auth check');
data: { [key: string]: string },
awsService: string
) {
log.trace('running header auth check'); log.trace('running header auth check');
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);
extractParamsSpan.end();
return { err: errors.InvalidToken }; return { err: errors.InvalidToken };
} }
activeSpan?.addEvent('Extracted security token');
// 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);
extractParamsSpan.end();
return { err: errors.MissingSecurityHeader }; return { err: errors.MissingSecurityHeader };
} }
activeSpan?.addEvent('Extracted authorization header');
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);
extractParamsSpan.end();
return { err: errors.InvalidArgument }; return { err: errors.InvalidArgument };
} }
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);
extractParamsSpan.end();
return { err: errors.MissingSecurityHeader }; return { err: errors.MissingSecurityHeader };
} }
if (payloadChecksum === 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD') { if (payloadChecksum === 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD') {
log.trace('requesting streaming v4 auth'); log.trace('requesting streaming v4 auth');
if (request.method !== 'PUT') { if (request.method !== 'PUT') {
log.debug('streaming v4 auth for put only', log.debug('streaming v4 auth for put only', { method: 'auth/v4/headerAuthCheck.check' });
{ method: 'auth/v4/headerAuthCheck.check' }); activeSpan.recordException(errors.InvalidArgument);
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);
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');
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);
extractParamsSpan.end();
return { err: errors.AccessDenied }; return { err: errors.AccessDenied };
} }
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');
const xAmzDate = request.headers['x-amz-date']; const xAmzDate = request.headers['x-amz-date'];
if (xAmzDate) { if (xAmzDate) {
const xAmzDateArr = xAmzDate.split('T'); const xAmzDateArr = xAmzDate.split('T');
// check that x-amz- date has the correct format and after epochTime // check that x-amz- date has the correct format and after epochTime
if (xAmzDateArr.length === 2 && xAmzDateArr[0].length === 8 if (xAmzDateArr.length === 2 && xAmzDateArr[0].length === 8 && xAmzDateArr[1].length === 7 && Number.parseInt(xAmzDateArr[0], 10) > 19700101) {
&& xAmzDateArr[1].length === 7
&& Number.parseInt(xAmzDateArr[0], 10) > 19700101) {
// format of x-amz- date is ISO 8601: YYYYMMDDTHHMMSSZ // format of x-amz- date is ISO 8601: YYYYMMDDTHHMMSSZ
timestamp = request.headers['x-amz-date']; timestamp = request.headers['x-amz-date'];
} }
@ -92,27 +100,27 @@ export function check(
timestamp = convertUTCtoISO8601(request.headers.date); timestamp = convertUTCtoISO8601(request.headers.date);
} }
if (!timestamp) { if (!timestamp) {
log.debug('missing or invalid date header', log.debug('missing or invalid date header', { method: 'auth/v4/headerAuthCheck.check' });
{ method: 'auth/v4/headerAuthCheck.check' }); activeSpan.recordException(errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header'));
return { err: errors.AccessDenied. extractParamsSpan.end();
customizeDescription('Authentication requires a valid Date or ' + return { err: errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header') };
'x-amz-date header') };
} }
activeSpan.addEvent('Request timestamp is valid');
const validationResult = validateCredentials(credentialsArr, timestamp, activeSpan.addEvent('Validating credentials');
log); const validationResult = validateCredentials(credentialsArr, timestamp, log);
if (validationResult instanceof Error) { if (validationResult instanceof Error) {
log.debug('credentials in improper format', { credentialsArr, log.debug('credentials in improper format', { credentialsArr, timestamp, validationResult });
timestamp, validationResult }); activeSpan.recordException(validationResult);
extractParamsSpan.end();
return { err: validationResult }; return { err: validationResult };
} }
activeSpan.addEvent('Credentials are valid');
// credentialsArr is [accessKey, date, region, aws-service, aws4_request] // credentialsArr is [accessKey, date, region, aws-service, aws4_request]
const scopeDate = credentialsArr[1]; const scopeDate = credentialsArr[1];
const region = credentialsArr[2]; const region = credentialsArr[2];
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
@ -124,14 +132,17 @@ export function check(
// 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')
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);
extractParamsSpan.end();
return { err: errors.RequestTimeTooSkewed }; return { err: errors.RequestTimeTooSkewed };
} }
activeSpan.addEvent('signature is not expired');
activeSpan.addEvent('Constructing string to sign');
const stringToSign = constructStringToSign({ const stringToSign = constructStringToSign({
log, log,
request, request,
@ -141,13 +152,16 @@ export function check(
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);
extractParamsSpan.end();
return { err: stringToSign }; return { err: stringToSign };
} }
activeSpan.addEvent('Constructed string to sign v4 headers');
activeSpan.addEvent('Exiting V4 header auth check');
extractParamsSpan.end();
return { return {
err: null, err: null,
params: { params: {
@ -162,8 +176,6 @@ export function check(
authType: 'REST-HEADER', authType: 'REST-HEADER',
signatureVersion: 'AWS4-HMAC-SHA256', signatureVersion: 'AWS4-HMAC-SHA256',
signatureAge: Date.now() - convertAmzTimeToMs(timestamp), signatureAge: Date.now() - convertAmzTimeToMs(timestamp),
// credentialScope and timestamp needed for streaming V4
// chunk evaluation
credentialScope, credentialScope,
timestamp, timestamp,
securityToken: token, securityToken: token,
@ -171,3 +183,4 @@ export function check(
}, },
}; };
} }

View File

@ -12,10 +12,20 @@ import { areSignedHeadersComplete } from './validateInputs';
* @param log - logging object * @param log - logging object
* @param data - Contain authentification params (GET or POST data) * @param data - Contain authentification params (GET or POST data)
*/ */
export function check(request: any, log: Logger, data: { [key: string]: string }) { export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) {
const {
activeSpan,
extractParamsSpan,
activeTracerContext,
tracer,
} = oTel;
activeSpan?.addEvent('Arsenal:: entered Arsenal.auth.v4.queryAuthCheck');
activeSpan?.addEvent('Arsenal:: extracting query parameters')
const authParams = extractQueryParams(data, log); const authParams = extractQueryParams(data, log);
activeSpan?.addEvent('Arsenal:: extracting query params');
if (Object.keys(authParams).length !== 5) { if (Object.keys(authParams).length !== 5) {
activeSpan.recordException(errors.InvalidArgument);
extractParamsSpan.end();
return { err: errors.InvalidArgument }; return { err: errors.InvalidArgument };
} }
@ -24,6 +34,8 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
const token = data['X-Amz-Security-Token']; const token = data['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);
extractParamsSpan.end();
return { err: errors.InvalidToken }; return { err: errors.InvalidToken };
} }
@ -35,6 +47,8 @@ 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);
extractParamsSpan.end();
return { err: errors.AccessDenied }; return { err: errors.AccessDenied };
} }
@ -43,6 +57,8 @@ export function check(request: any, log: Logger, data: { [key: string]: string }
if (validationResult instanceof Error) { if (validationResult instanceof Error) {
log.debug('credentials in improper format', { credential, log.debug('credentials in improper format', { credential,
timestamp, validationResult }); timestamp, validationResult });
activeSpan.recordException(validationResult);
extractParamsSpan.end();
return { err: validationResult }; return { err: validationResult };
} }
const accessKey = credential[0]; const accessKey = credential[0];
@ -53,6 +69,8 @@ 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);
extractParamsSpan.end();
return { err: errors.RequestTimeTooSkewed }; return { err: errors.RequestTimeTooSkewed };
} }
@ -71,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,
@ -81,11 +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);
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');
extractParamsSpan.end();
return { return {
err: null, err: null,
params: { params: {

View File

@ -150,6 +150,7 @@ export type Params = {
}; };
unsupportedQueries: any; unsupportedQueries: any;
api: { callApiMethod: routesUtils.CallApiMethod }; api: { callApiMethod: routesUtils.CallApiMethod };
oTel?: any,
} }
/** routes - route request to appropriate method /** routes - route request to appropriate method
@ -180,111 +181,142 @@ export default function routes(
logger: RequestLogger, logger: RequestLogger,
s3config?: any, s3config?: any,
) { ) {
checkTypes(req, res, params, logger);
const { const {
api, dataRetrievalParams: {
internalHandlers, oTel: {
statsClient, tracer,
allEndpoints, activeSpan,
websiteEndpoints, activeTracerContext,
blacklistedPrefixes, }
dataRetrievalParams, }
} = params; } = params;
return tracer.startActiveSpan('Validating Request Parameters with Arsenal', undefined, activeTracerContext, arsenalValidatorSpan => {
arsenalValidatorSpan.setAttributes({
'code.lineno': 176,
'code.filename': 'lib/s3routes/routes.ts',
'code.function': 'routes()',
'code.url': 'https://github.com/scality/Arsenal/blob/development/7.70/lib/s3routes/routes.ts#L176'
});
checkTypes(req, res, params, logger);
const clientInfo = { const {
clientIP: requestUtils.getClientIp(req, s3config), api,
clientPort: req.socket.remotePort, internalHandlers,
httpMethod: req.method, statsClient,
httpURL: req.url, allEndpoints,
// @ts-ignore websiteEndpoints,
endpoint: req.endpoint, blacklistedPrefixes,
}; dataRetrievalParams,
} = params;
let reqUids = req.headers['x-scal-request-uids']; const clientInfo = {
if (reqUids !== undefined && !isValidReqUids(reqUids)) { clientIP: requestUtils.getClientIp(req, s3config),
// simply ignore invalid id (any user can provide an clientPort: req.socket.remotePort,
// invalid request ID through a crafted header) httpMethod: req.method,
reqUids = undefined; httpURL: req.url,
} // @ts-ignore
const log = (reqUids !== undefined ? endpoint: req.endpoint,
// @ts-ignore };
logger.newRequestLoggerFromSerializedUids(reqUids) :
// @ts-ignore
logger.newRequestLogger());
if (!req.url!.startsWith('/_/healthcheck')) { let reqUids = req.headers['x-scal-request-uids'];
log.info('received request', clientInfo); if (reqUids !== undefined && !isValidReqUids(reqUids)) {
} // simply ignore invalid id (any user can provide an
// invalid request ID through a crafted header)
log.end().addDefaultFields(clientInfo); reqUids = undefined;
if (req.url!.startsWith('/_/')) {
let internalServiceName = req.url!.slice(3);
const serviceDelim = internalServiceName.indexOf('/');
if (serviceDelim !== -1) {
internalServiceName = internalServiceName.slice(0, serviceDelim);
} }
if (internalHandlers[internalServiceName] === undefined) { const log = (reqUids !== undefined ?
// @ts-ignore
logger.newRequestLoggerFromSerializedUids(reqUids) :
// @ts-ignore
logger.newRequestLogger());
if (!req.url!.startsWith('/_/healthcheck')) {
log.info('received request', clientInfo);
}
log.end().addDefaultFields(clientInfo);
if (req.url!.startsWith('/_/')) {
let internalServiceName = req.url!.slice(3);
const serviceDelim = internalServiceName.indexOf('/');
if (serviceDelim !== -1) {
internalServiceName = internalServiceName.slice(0, serviceDelim);
}
if (internalHandlers[internalServiceName] === undefined) {
activeSpan.recordException(errors.InvalidURI);
return routesUtils.responseXMLBody(
errors.InvalidURI, null, res, log);
}
return internalHandlers[internalServiceName](
clientInfo.clientIP, req, res, log, statsClient);
}
if (statsClient) {
// report new request for stats
statsClient.reportNewRequest('s3');
}
try {
const validHosts = allEndpoints.concat(websiteEndpoints);
routesUtils.normalizeRequest(req, validHosts);
} catch (err: any) {
activeSpan.recordException(errors.InvalidURI.customizeDescription('Could not parse the ' +
'specified URI. Check your restEndpoints configuration.'));
log.debug('could not normalize request', { error: err.stack });
return routesUtils.responseXMLBody( return routesUtils.responseXMLBody(
errors.InvalidURI, null, res, log); errors.InvalidURI.customizeDescription('Could not parse the ' +
'specified URI. Check your restEndpoints configuration.'),
null, res, log);
} }
return internalHandlers[internalServiceName](
clientInfo.clientIP, req, res, log, statsClient);
}
if (statsClient) { log.addDefaultFields({
// report new request for stats // @ts-ignore
statsClient.reportNewRequest('s3'); bucketName: req.bucketName,
} // @ts-ignore
objectKey: req.objectKey,
// @ts-ignore
bytesReceived: req.parsedContentLength || 0,
// @ts-ignore
bodyLength: parseInt(req.headers['content-length'], 10) || 0,
});
activeSpan.setAttributes({
// @ts-ignore
'aws.s3.bucket': req.bucketName,
// @ts-ignore
'aws.s3.key': req.objectKey,
// @ts-ignore
'aws.request_id': log.getUids().join(':'),
'rpc.service': 'S3',
});
try { // @ts-ignore
const validHosts = allEndpoints.concat(websiteEndpoints); const { error, method } = checkUnsupportedRoutes(req.method, req.query);
routesUtils.normalizeRequest(req, validHosts);
} catch (err: any) { if (error) {
log.debug('could not normalize request', { error: err.stack }); activeSpan.recordException(error);
return routesUtils.responseXMLBody( log.trace('error validating route or uri params', { error });
errors.InvalidURI.customizeDescription('Could not parse the ' + // @ts-ignore
'specified URI. Check your restEndpoints configuration.'), return routesUtils.responseXMLBody(error, '', res, log);
null, res, log); }
}
log.addDefaultFields({
// @ts-ignore // @ts-ignore
bucketName: req.bucketName, const bucketOrKeyError = checkBucketAndKey(req.bucketName, req.objectKey,
// @ts-ignore
req.method, req.query, blacklistedPrefixes, log);
if (bucketOrKeyError) {
activeSpan.recordException(bucketOrKeyError)
log.trace('error with bucket or key value',
{ error: bucketOrKeyError });
return routesUtils.responseXMLBody(bucketOrKeyError, null, res, log);
}
// bucket website request
// @ts-ignore // @ts-ignore
objectKey: req.objectKey, if (websiteEndpoints && websiteEndpoints.indexOf(req.parsedHost) > -1) {
// @ts-ignore return routeWebsite(req, res, api, log, statsClient, dataRetrievalParams);
bytesReceived: req.parsedContentLength || 0, }
// @ts-ignore arsenalValidatorSpan.end();
bodyLength: parseInt(req.headers['content-length'], 10) || 0, return method(req, res, api, log, statsClient, dataRetrievalParams);
}); });
// @ts-ignore
const { error, method } = checkUnsupportedRoutes(req.method, req.query);
if (error) {
log.trace('error validating route or uri params', { error });
// @ts-ignore
return routesUtils.responseXMLBody(error, '', res, log);
}
// @ts-ignore
const bucketOrKeyError = checkBucketAndKey(req.bucketName, req.objectKey,
// @ts-ignore
req.method, req.query, blacklistedPrefixes, log);
if (bucketOrKeyError) {
log.trace('error with bucket or key value',
{ error: bucketOrKeyError });
return routesUtils.responseXMLBody(bucketOrKeyError, null, res, log);
}
// bucket website request
// @ts-ignore
if (websiteEndpoints && websiteEndpoints.indexOf(req.parsedHost) > -1) {
return routeWebsite(req, res, api, log, statsClient, dataRetrievalParams);
}
return method(req, res, api, log, statsClient, dataRetrievalParams);
} }

View File

@ -4,6 +4,7 @@ import * as routesUtils from '../routesUtils';
import errors from '../../errors'; import errors from '../../errors';
import StatsClient from '../../metrics/StatsClient'; import StatsClient from '../../metrics/StatsClient';
import * as http from 'http'; import * as http from 'http';
import { actionMonitoringMapS3 } from '../../policyEvaluator/utils/actionMaps';
export default function routeDELETE( export default function routeDELETE(
request: http.IncomingMessage, request: http.IncomingMessage,
@ -11,58 +12,146 @@ export default function routeDELETE(
api: { callApiMethod: routesUtils.CallApiMethod }, api: { callApiMethod: routesUtils.CallApiMethod },
log: RequestLogger, log: RequestLogger,
statsClient?: StatsClient, statsClient?: StatsClient,
dataRetrievalParams?: any,
) { ) {
const call = (name: string) => { const {
return api.callApiMethod(name, request, response, log, (err, corsHeaders) => { oTel: {
routesUtils.statsReport500(err, statsClient); tracer,
return routesUtils.responseNoBody(err, corsHeaders, response, 204, log); activeSpan,
}); activeTracerContext,
} },
log.debug('routing request', { method: 'routeDELETE' }); } = dataRetrievalParams;
return tracer.startActiveSpan('Arsenal:: Performing Delete API related operations using Cloudserver, Vault and Metadata', undefined, activeTracerContext, cloudserverApiSpan => {
const { query, objectKey } = request as any activeSpan.addEvent('Request validated, routing request using routeDELETE() in arsenal');
if (query?.uploadId) { cloudserverApiSpan.setAttributes({
if (objectKey === undefined) { 'code.lineno': 8,
const message = 'A key must be specified'; 'code.filename': 'lib/s3routes/routes/routeDELETE.ts',
const err = errors.InvalidRequest.customizeDescription(message); 'code.function': 'routeDELETE()',
return routesUtils.responseNoBody(err, null, response, 200, log); })
} activeSpan.addEvent('Detecting which API to route to using arsenal routeDELETE()')
return call('multipartDelete'); const call = (name: string) => {
} else if (objectKey === undefined) { return api.callApiMethod(name, request, response, log, (err, corsHeaders) => {
if (query?.website !== undefined) { cloudserverApiSpan.end();
return call('bucketDeleteWebsite'); const action = actionMonitoringMapS3[name];
} else if (query?.cors !== undefined) { activeSpan.addEvent(`${action} API operation complete`);
return call('bucketDeleteCors'); if (err) {
} else if (query?.replication !== undefined) { activeSpan.recordException(err);
return call('bucketDeleteReplication');
} else if (query?.lifecycle !== undefined) {
return call('bucketDeleteLifecycle');
} else if (query?.policy !== undefined) {
return call('bucketDeletePolicy');
} else if (query?.encryption !== undefined) {
return call('bucketDeleteEncryption');
} else if (query?.tagging !== undefined) {
return call('bucketDeleteTagging');
}
call('bucketDelete');
} else {
if (query?.tagging !== undefined) {
return call('objectDeleteTagging');
}
api.callApiMethod('objectDelete', request, response, log,
(err, corsHeaders) => {
/*
* Since AWS expects a 204 regardless of the existence of
the object, the errors NoSuchKey and NoSuchVersion should not
* be sent back as a response.
*/
if (err && !err.is.NoSuchKey && !err.is.NoSuchVersion) {
return routesUtils.responseNoBody(err, corsHeaders,
response, undefined, log);
} }
routesUtils.statsReport500(err, statsClient); routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(null, corsHeaders, response, activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
204, log); return routesUtils.responseNoBody(err, corsHeaders, response, 204, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
}); });
} }
log.debug('routing request', { method: 'routeDELETE' });
const { query, objectKey } = request as any
if (query?.uploadId) {
// @ts-ignore
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected AbortMultipartUpload API request');
activeSpan.setAttribute('rpc.method', 'AbortMultipartUpload');
if (objectKey === undefined) {
const message = 'A key must be specified';
const err = errors.InvalidRequest.customizeDescription(message);
activeSpan.recordException(err);
cloudserverApiSpan.end();
return routesUtils.responseNoBody(err, null, response, 200, log);
}
return call('multipartDelete');
} else if (objectKey === undefined) {
if (query?.website !== undefined) {
// @ts-ignore
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected DeleteBucketWebsite API request');
activeSpan.setAttribute('rpc.method', 'DeleteBucketWebsite');
return call('bucketDeleteWebsite');
} else if (query?.cors !== undefined) {
// @ts-ignore
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected DeleteBucketCors API request');
activeSpan.setAttribute('rpc.method', 'DeleteBucketCors');
return call('bucketDeleteCors');
} else if (query?.replication !== undefined) {
// @ts-ignore
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected DeleteBucketReplication API request');
activeSpan.setAttribute('rpc.method', 'DeleteBucketReplication');
return call('bucketDeleteReplication');
} else if (query?.lifecycle !== undefined) {
// @ts-ignore
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected DeleteBucketLifecycle API request');
activeSpan.setAttribute('rpc.method', 'DeleteBucketLifecycle');
return call('bucketDeleteLifecycle');
} else if (query?.policy !== undefined) {
// @ts-ignore
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected DeleteBucketPolicy API request');
activeSpan.setAttribute('rpc.method', 'DeleteBucketPolicy');
return call('bucketDeletePolicy');
} else if (query?.encryption !== undefined) {
// @ts-ignore
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected DeleteBucketEncryption API request');
activeSpan.setAttribute('rpc.method', 'DeleteBucketEncryption');
return call('bucketDeleteEncryption');
} else if (query?.tagging !== undefined) {
// @ts-ignore
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected DeleteBucketTagging API request');
activeSpan.setAttribute('rpc.method', 'DeleteBucketTagging');
return call('bucketDeleteTagging');
}
// @ts-ignore
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected DeleteBucket API request');
activeSpan.setAttribute('rpc.method', 'DeleteBucket');
call('bucketDelete');
} else {
if (query?.tagging !== undefined) {
// @ts-ignore
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected DeleteObjectTagging API request');
activeSpan.setAttribute('rpc.method', 'DeleteObjectTagging');
return call('objectDeleteTagging');
}
// @ts-ignore
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected DeleteObject API request');
activeSpan.setAttribute('rpc.method', 'DeleteObject');
return api.callApiMethod('objectDelete', request, response, log,
(err, corsHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('DeleteObject API operation complete')
/*
* Since AWS expects a 204 regardless of the existence of
the object, the errors NoSuchKey and NoSuchVersion should not
* be sent back as a response.
*/
if (err && !err.is.NoSuchKey && !err.is.NoSuchVersion) {
activeSpan.recordException(err);
cloudserverApiSpan.end();
return routesUtils.responseNoBody(err, corsHeaders,
response, undefined, log);
}
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseNoBody(null, corsHeaders, response,
204, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
}
});
} }

View File

@ -4,6 +4,7 @@ import * as routesUtils from '../routesUtils';
import errors from '../../errors'; import errors from '../../errors';
import * as http from 'http'; import * as http from 'http';
import StatsClient from '../../metrics/StatsClient'; import StatsClient from '../../metrics/StatsClient';
import { actionMonitoringMapS3 } from '../../policyEvaluator/utils/actionMaps';
export default function routerGET( export default function routerGET(
request: http.IncomingMessage, request: http.IncomingMessage,
@ -13,84 +14,128 @@ export default function routerGET(
statsClient?: StatsClient, statsClient?: StatsClient,
dataRetrievalParams?: any, dataRetrievalParams?: any,
) { ) {
log.debug('routing request', { method: 'routerGET' }); const {
oTel: {
const { bucketName, objectKey, query } = request as any tracer,
activeSpan,
const call = (name: string) => { activeTracerContext,
api.callApiMethod(name, request, response, log, (err, xml, corsHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseXMLBody(err, xml, response, log, corsHeaders);
});
}
if (bucketName === undefined && objectKey !== undefined) {
routesUtils.responseXMLBody(errors.NoSuchBucket, null, response, log);
} else if (bucketName === undefined && objectKey === undefined) {
// GET service
call('serviceGet');
} else if (objectKey === undefined) {
// GET bucket ACL
if (query.acl !== undefined) {
call('bucketGetACL');
} else if (query.replication !== undefined) {
call('bucketGetReplication');
} else if (query.cors !== undefined) {
call('bucketGetCors');
} else if (query.versioning !== undefined) {
call('bucketGetVersioning');
} else if (query.website !== undefined) {
call('bucketGetWebsite');
} else if (query.tagging !== undefined) {
call('bucketGetTagging');
} else if (query.lifecycle !== undefined) {
call('bucketGetLifecycle');
} else if (query.uploads !== undefined) {
// List MultipartUploads
call('listMultipartUploads');
} else if (query.location !== undefined) {
call('bucketGetLocation');
} else if (query.policy !== undefined) {
call('bucketGetPolicy');
} else if (query['object-lock'] !== undefined) {
call('bucketGetObjectLock');
} else if (query.notification !== undefined) {
call('bucketGetNotification');
} else if (query.encryption !== undefined) {
call('bucketGetEncryption');
} else {
// GET bucket
call('bucketGet');
} }
} else { } = dataRetrievalParams;
if (query.acl !== undefined) { return tracer.startActiveSpan('Arsenal:: Performing Get API related operations using Cloudserver, Vault and Metadata', undefined, activeTracerContext, cloudserverApiSpan => {
// GET object ACL activeSpan.addEvent('Request validated, routing request using routeGET() in arsenal');
call('objectGetACL'); activeSpan.addEvent('Detecting which API to route to using arsenal routeGET()')
} else if (query['legal-hold'] !== undefined) { log.debug('routing request', { method: 'routerGET' });
call('objectGetLegalHold');
} else if (query.tagging !== undefined) { const { bucketName, objectKey, query } = request as any
call('objectGetTagging');
// List parts of an open multipart upload const call = (name: string) => {
} else if (query.uploadId !== undefined) { const action = actionMonitoringMapS3[name];
call('listParts'); // @ts-ignore
} else if (query.retention !== undefined) { activeSpan.updateName(`S3 API request`);
call('objectGetRetention'); activeSpan.addEvent(`Detected ${action} API request`);
} else { activeSpan.setAttribute('rpc.method', action);
// GET object return api.callApiMethod(name, request, response, log, (err, xml, corsHeaders) => {
api.callApiMethod('objectGet', request, response, log, cloudserverApiSpan.end();
(err, dataGetInfo, resMetaHeaders, range) => { activeSpan.addEvent(`${action} API operation complete`);
let contentLength = 0; if (err) {
if (resMetaHeaders && resMetaHeaders['Content-Length']) { activeSpan.recordException(err);
contentLength = resMetaHeaders['Content-Length']; }
} routesUtils.statsReport500(err, statsClient);
// TODO ARSN-216 Fix logger activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
// @ts-ignore return routesUtils.responseXMLBody(err, xml, response, log, corsHeaders);
log.end().addDefaultFields({ contentLength }); }, {
routesUtils.statsReport500(err, statsClient); cloudserverApiSpan,
return routesUtils.responseStreamData(err, query, activeSpan,
resMetaHeaders, dataGetInfo, dataRetrievalParams, response, activeTracerContext,
range, log); tracer,
}); });
} }
}
if (bucketName === undefined && objectKey !== undefined) {
activeSpan.recordException(errors.NoSuchBucket);
cloudserverApiSpan.end();
return routesUtils.responseXMLBody(errors.NoSuchBucket, null, response, log);
} else if (bucketName === undefined && objectKey === undefined) {
// GET service
call('serviceGet');
} else if (objectKey === undefined) {
// GET bucket ACL
if (query.acl !== undefined) {
call('bucketGetACL');
} else if (query.replication !== undefined) {
call('bucketGetReplication');
} else if (query.cors !== undefined) {
call('bucketGetCors');
} else if (query.versioning !== undefined) {
call('bucketGetVersioning');
} else if (query.website !== undefined) {
call('bucketGetWebsite');
} else if (query.tagging !== undefined) {
call('bucketGetTagging');
} else if (query.lifecycle !== undefined) {
call('bucketGetLifecycle');
} else if (query.uploads !== undefined) {
// List MultipartUploads
call('listMultipartUploads');
} else if (query.location !== undefined) {
call('bucketGetLocation');
} else if (query.policy !== undefined) {
call('bucketGetPolicy');
} else if (query['object-lock'] !== undefined) {
call('bucketGetObjectLock');
} else if (query.notification !== undefined) {
call('bucketGetNotification');
} else if (query.encryption !== undefined) {
call('bucketGetEncryption');
} else {
// GET bucket
call('bucketGet');
}
} else {
if (query.acl !== undefined) {
// GET object ACL
call('objectGetACL');
} else if (query['legal-hold'] !== undefined) {
call('objectGetLegalHold');
} else if (query.tagging !== undefined) {
call('objectGetTagging');
// List parts of an open multipart upload
} else if (query.uploadId !== undefined) {
call('listParts');
} else if (query.retention !== undefined) {
call('objectGetRetention');
} else {
// GET object
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected GetObject API request');
activeSpan.setAttribute('aws.request_id', log.getUids()[0]);
activeSpan.setAttribute('rpc.method', 'GetObject');
return api.callApiMethod('objectGet', request, response, log,
(err, dataGetInfo, resMetaHeaders, range) => {
cloudserverApiSpan.end();
activeSpan.addEvent('Located Data, using arsenal to make GET request')
let contentLength = 0;
if (resMetaHeaders && resMetaHeaders['Content-Length']) {
contentLength = resMetaHeaders['Content-Length'];
}
// TODO ARSN-216 Fix logger
// @ts-ignore
log.end().addDefaultFields({ contentLength });
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseStreamData(err, query,
resMetaHeaders, dataGetInfo, dataRetrievalParams, response,
range, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
}
}
});
} }

View File

@ -11,28 +11,75 @@ export default function routeHEAD(
api: { callApiMethod: routesUtils.CallApiMethod }, api: { callApiMethod: routesUtils.CallApiMethod },
log: RequestLogger, log: RequestLogger,
statsClient?: StatsClient, statsClient?: StatsClient,
dataRetrievalParams?: any,
) { ) {
log.debug('routing request', { method: 'routeHEAD' }); const{
const { bucketName, objectKey } = request as any oTel: {
if (bucketName === undefined) { tracer,
log.trace('head request without bucketName'); activeSpan,
routesUtils.responseXMLBody(errors.MethodNotAllowed, activeTracerContext,
null, response, log); }
} else if (objectKey === undefined) { } = dataRetrievalParams;
// HEAD bucket return tracer.startActiveSpan('Arsenal:: Performing Head API related operations using Cloudserver, Vault and Metadata', undefined, activeTracerContext, cloudserverApiSpan => {
api.callApiMethod('bucketHead', request, response, log, activeSpan.addEvent('Request validated, routing request using routeHEAD() in arsenal');
(err, corsHeaders) => { cloudserverApiSpan.setAttributes({
routesUtils.statsReport500(err, statsClient); 'code.lineno': 8,
return routesUtils.responseNoBody(err, corsHeaders, response, 'code.filename': 'lib/s3routes/routes/routeHEAD.ts',
200, log); 'code.function': 'routeHEAD()',
}); });
} else { activeSpan.addEvent('Detecting which API to route to using arsenal routeHEAD()');
// HEAD object log.debug('routing request', { method: 'routeHEAD' });
api.callApiMethod('objectHead', request, response, log, const { bucketName, objectKey } = request as any
(err, resHeaders) => { if (bucketName === undefined) {
routesUtils.statsReport500(err, statsClient); log.trace('head request without bucketName');
return routesUtils.responseContentHeaders(err, {}, resHeaders, activeSpan.recordException(errors.MethodNotAllowed);
response, log); cloudserverApiSpan.end();
}); routesUtils.responseXMLBody(errors.MethodNotAllowed,
} null, response, log);
} else if (objectKey === undefined) {
activeSpan.updateName('S3 API request');
activeSpan.addEvent(`Detected HeadBucket API request`);
activeSpan.setAttribute('rpc.method', 'HeadBucket');
// HEAD bucket
api.callApiMethod('bucketHead', request, response, log,
(err, corsHeaders) => {
activeSpan.addEvent('HeadBucket API operation complete')
cloudserverApiSpan.end();
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseNoBody(err, corsHeaders, response,
200, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else {
// HEAD object
activeSpan.updateName('S3 API request');
activeSpan.addEvent(`Detected HeadObject API request`);
activeSpan.setAttribute('rpc.method', 'HeadObject');
api.callApiMethod('objectHead', request, response, log,
(err, resHeaders) => {
activeSpan.addEvent('HeadObject API operation complete')
cloudserverApiSpan.end();
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseContentHeaders(err, {}, resHeaders,
response, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
}
});
} }

View File

@ -11,29 +11,64 @@ export default function routeOPTIONS(
api: { callApiMethod: routesUtils.CallApiMethod }, api: { callApiMethod: routesUtils.CallApiMethod },
log: RequestLogger, log: RequestLogger,
statsClient?: StatsClient, statsClient?: StatsClient,
dataRetrievalParams?: any,
) { ) {
log.debug('routing request', { method: 'routeOPTION' }); const {
oTel: {
const corsMethod = request.headers['access-control-request-method'] || null; tracer,
activeSpan,
if (!request.headers.origin) { activeTracerContext,
const msg = 'Insufficient information. Origin request header needed.'; }
const err = errors.BadRequest.customizeDescription(msg); } = dataRetrievalParams;
log.debug('missing origin', { method: 'routeOPTIONS', error: err }); return tracer.startActiveSpan('Arsenal:: Performing corsPreflight API related operations using Cloudserver, Vault and Metadata', undefined, activeTracerContext, cloudserverApiSpan => {
return routesUtils.responseXMLBody(err, null, response, log); activeSpan.addEvent('Request validated, routing request using routeOPTIONS() in arsenal');
} cloudserverApiSpan.setAttributes({
if (['GET', 'PUT', 'HEAD', 'POST', 'DELETE'].indexOf(corsMethod ?? '') < 0) { 'code.lineno': 8,
const msg = `Invalid Access-Control-Request-Method: ${corsMethod}`; 'code.filename': 'lib/s3routes/routes/routeOPTIONS.ts',
const err = errors.BadRequest.customizeDescription(msg); 'code.function': 'routeOPTIONS()',
log.debug('invalid Access-Control-Request-Method',
{ method: 'routeOPTIONS', error: err });
return routesUtils.responseXMLBody(err, null, response, log);
}
return api.callApiMethod('corsPreflight', request, response, log,
(err, resHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, resHeaders, response, 200,
log);
}); });
activeSpan.addEvent('Detecting which API to route to using arsenal routeOPTIONS()');
log.debug('routing request', { method: 'routeOPTION' });
const corsMethod = request.headers['access-control-request-method'] || null;
if (!request.headers.origin) {
const msg = 'Insufficient information. Origin request header needed.';
const err = errors.BadRequest.customizeDescription(msg);
activeSpan.recordException(err);
cloudserverApiSpan.end();
log.debug('missing origin', { method: 'routeOPTIONS', error: err });
return routesUtils.responseXMLBody(err, null, response, log);
}
if (['GET', 'PUT', 'HEAD', 'POST', 'DELETE'].indexOf(corsMethod ?? '') < 0) {
const msg = `Invalid Access-Control-Request-Method: ${corsMethod}`;
const err = errors.BadRequest.customizeDescription(msg);
activeSpan.recordException(err);
cloudserverApiSpan.end();
log.debug('invalid Access-Control-Request-Method',
{ method: 'routeOPTIONS', error: err });
return routesUtils.responseXMLBody(err, null, response, log);
}
// @ts-ignore
activeSpan.updateName('S3 API request');
activeSpan.addEvent(`Detected corsPreflight API request`);
activeSpan.setAttribute('rpc.method', 'corsPreflight');
return api.callApiMethod('corsPreflight', request, response, log,
(err, resHeaders) => {
activeSpan.addEvent('corsPreflight API operation complete');
cloudserverApiSpan.end();
if (err) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseNoBody(err, resHeaders, response, 200,
log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
});
} }

View File

@ -3,6 +3,7 @@ import { RequestLogger } from 'werelogs';
import * as routesUtils from '../routesUtils'; import * as routesUtils from '../routesUtils';
import errors from '../../errors'; import errors from '../../errors';
import * as http from 'http'; import * as http from 'http';
import StatsClient from '../../metrics/StatsClient';
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
export default function routePOST( export default function routePOST(
@ -10,54 +11,115 @@ export default function routePOST(
response: http.ServerResponse, response: http.ServerResponse,
api: { callApiMethod: routesUtils.CallApiMethod }, api: { callApiMethod: routesUtils.CallApiMethod },
log: RequestLogger, log: RequestLogger,
statsClient?: StatsClient,
dataRetrievalParams?: any,
) { ) {
log.debug('routing request', { method: 'routePOST' }); const {
oTel: {
tracer,
activeSpan,
activeTracerContext,
}
} = dataRetrievalParams;
return tracer.startActiveSpan('Arsenal:: Performing Post API related operations using Cloudserver, Vault and Metadata', undefined, activeTracerContext, cloudserverApiSpan => {
activeSpan.addEvent('Request validated, routing request using routePOST() in arsenal');
cloudserverApiSpan.setAttributes({
'code.lineno': 9,
'code.filename': 'lib/s3routes/routes/routePOST.ts',
'code.function': 'routePOST()',
});
activeSpan.addEvent('Detecting which API to route to using arsenal routePOST()');
log.debug('routing request', { method: 'routePOST' });
const { query, bucketName, objectKey } = request as any const { query, bucketName, objectKey } = request as any
const invalidMultiObjectDelReq = query.delete !== undefined const invalidMultiObjectDelReq = query.delete !== undefined
&& bucketName === undefined; && bucketName === undefined;
if (invalidMultiObjectDelReq) { if (invalidMultiObjectDelReq) {
return routesUtils.responseNoBody(errors.MethodNotAllowed, null, activeSpan.recordException(errors.MethodNotAllowed);
response, undefined, log); cloudserverApiSpan.end();
} return routesUtils.responseNoBody(errors.MethodNotAllowed, null,
response, undefined, log);
}
// @ts-ignore // @ts-ignore
request.post = ''; request.post = '';
const invalidInitiateMpuReq = query.uploads !== undefined const invalidInitiateMpuReq = query.uploads !== undefined
&& objectKey === undefined; && objectKey === undefined;
const invalidCompleteMpuReq = query.uploadId !== undefined const invalidCompleteMpuReq = query.uploadId !== undefined
&& objectKey === undefined; && objectKey === undefined;
if (invalidInitiateMpuReq || invalidCompleteMpuReq) { if (invalidInitiateMpuReq || invalidCompleteMpuReq) {
return routesUtils.responseNoBody(errors.InvalidURI, null, activeSpan.recordException(errors.InvalidURI);
response, undefined, log); cloudserverApiSpan.end();
} return routesUtils.responseNoBody(errors.InvalidURI, null,
response, undefined, log);
}
// POST initiate multipart upload // POST initiate multipart upload
if (query.uploads !== undefined) { if (query.uploads !== undefined) {
return api.callApiMethod('initiateMultipartUpload', request, activeSpan.updateName('S3 API request');
response, log, (err, result, corsHeaders) => activeSpan.addEvent(`Detected CreateMultipartUpload API request`);
routesUtils.responseXMLBody(err, result, response, log, activeSpan.setAttribute('rpc.method', 'CreateMultipartUpload');
corsHeaders)); return api.callApiMethod('initiateMultipartUpload', request,
} response, log, (err, result, corsHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('CreateMultipartUpload API operation complete');
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
routesUtils.responseXMLBody(err, result, response, log,
corsHeaders)
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
}
// POST complete multipart upload // POST complete multipart upload
if (query.uploadId !== undefined) { if (query.uploadId !== undefined) {
return api.callApiMethod('completeMultipartUpload', request, activeSpan.updateName('S3 API request');
response, log, (err, result, resHeaders) => activeSpan.addEvent(`Detected CompleteMultipartUpload API request`);
routesUtils.responseXMLBody(err, result, response, log, activeSpan.setAttribute('rpc.method', 'CompleteMultipartUpload');
resHeaders)); return api.callApiMethod('completeMultipartUpload', request,
} response, log, (err, result, resHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('CompleteMultipartUpload API operation complete');
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
routesUtils.responseXMLBody(err, result, response, log,
resHeaders)
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
}
// POST multiObjectDelete // POST multiObjectDelete
if (query.delete !== undefined) { if (query.delete !== undefined) {
return api.callApiMethod('multiObjectDelete', request, response, activeSpan.updateName('S3 API request');
log, (err, xml, corsHeaders) => activeSpan.addEvent(`Detected AbortMultipartUpload API request`);
routesUtils.responseXMLBody(err, xml, response, log, activeSpan.setAttribute('rpc.method', 'AbortMultipartUpload');
corsHeaders)); return api.callApiMethod('multiObjectDelete', request, response,
} log, (err, xml, corsHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('AbortMultipartUpload API operation complete');
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
routesUtils.responseXMLBody(err, xml, response, log,
corsHeaders)
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
}
activeSpan.recordException(errors.NotImplemented);
cloudserverApiSpan.end();
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseNoBody(errors.NotImplemented, null, response,
200, log);
});
return routesUtils.responseNoBody(errors.NotImplemented, null, response,
200, log);
} }

View File

@ -11,223 +11,528 @@ export default function routePUT(
api: { callApiMethod: routesUtils.CallApiMethod }, api: { callApiMethod: routesUtils.CallApiMethod },
log: RequestLogger, log: RequestLogger,
statsClient?: StatsClient, statsClient?: StatsClient,
dataRetrievalParams?: any,
) { ) {
log.debug('routing request', { method: 'routePUT' }); const {
oTel: {
tracer,
activeSpan,
activeTracerContext,
},
} = dataRetrievalParams;
return tracer.startActiveSpan('Arsenal:: Performing Put API related operations using Cloudserver, Vault and Metadata', undefined, activeTracerContext, cloudserverApiSpan => {
activeSpan.addEvent('Request validated, routing request using routePUT() in arsenal')
cloudserverApiSpan.setAttributes({
'code.lineno': 8,
'code.filename': 'lib/s3routes/routes/routePUT.ts',
'code.function': 'routePUT()',
})
activeSpan.addEvent('Detecting which API to route to using arsenal routePUT()')
log.debug('routing request', { method: 'routePUT' });
const { objectKey, query, bucketName, parsedContentLength } = request as any const { objectKey, query, bucketName, parsedContentLength } = request as any
if (objectKey === undefined) { if (objectKey === undefined) {
// PUT bucket - PUT bucket ACL // PUT bucket - PUT bucket ACL
// content-length for object is handled separately below // content-length for object is handled separately below
const contentLength = request.headers['content-length']; const contentLength = request.headers['content-length'];
const len = Number(contentLength); const len = Number(contentLength);
if ((contentLength && (Number.isNaN(len) || len < 0)) || contentLength === '') { if ((contentLength && (Number.isNaN(len) || len < 0)) || contentLength === '') {
log.debug('invalid content-length header'); log.debug('invalid content-length header');
return routesUtils.responseNoBody( return routesUtils.responseNoBody(
errors.BadRequest, null, response, undefined, log); errors.BadRequest, null, response, undefined, log);
} }
// PUT bucket ACL // PUT bucket ACL
if (query.acl !== undefined) { if (query.acl !== undefined) {
api.callApiMethod('bucketPutACL', request, response, log, activeSpan.updateName('S3 API request');
(err, corsHeaders) => { activeSpan.addEvent('Detected PutBucketAcl API request');
routesUtils.statsReport500(err, statsClient); activeSpan.setAttribute('rpc.method', 'PutBucketAcl');
return routesUtils.responseNoBody(err, corsHeaders, return api.callApiMethod('bucketPutACL', request, response, log,
response, 200, log); (err, corsHeaders) => {
}); cloudserverApiSpan.end();
} else if (query.versioning !== undefined) { activeSpan.addEvent('PutBucketAcl API operation complete');
api.callApiMethod('bucketPutVersioning', request, response, log, if (err?.code === 500) {
(err, corsHeaders) => { activeSpan.recordException(err);
routesUtils.statsReport500(err, statsClient); }
routesUtils.responseNoBody(err, corsHeaders, response, 200, routesUtils.statsReport500(err, statsClient);
log); activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
}); return routesUtils.responseNoBody(err, corsHeaders,
} else if (query.website !== undefined) { response, 200, log);
api.callApiMethod('bucketPutWebsite', request, response, log, }, {
(err, corsHeaders) => { cloudserverApiSpan,
routesUtils.statsReport500(err, statsClient); activeSpan,
return routesUtils.responseNoBody(err, corsHeaders, activeTracerContext,
response, 200, log); tracer,
}); });
} else if (query.tagging !== undefined) { } else if (query.versioning !== undefined) {
api.callApiMethod('bucketPutTagging', request, response, log, activeSpan.updateName('S3 API request');
(err, corsHeaders) => { activeSpan.addEvent('Detected PutBucketVersioning API request');
routesUtils.statsReport500(err, statsClient); activeSpan.setAttribute('rpc.method', 'PutBucketVersioning');
return routesUtils.responseNoBody(err, corsHeaders, return api.callApiMethod('bucketPutVersioning', request, response, log,
response, 200, log); (err, corsHeaders) => {
}); cloudserverApiSpan.end();
} else if (query.cors !== undefined) { activeSpan.addEvent('PutBucketVersioning API operation complete');
api.callApiMethod('bucketPutCors', request, response, log, if (err?.code === 500) {
(err, corsHeaders) => { activeSpan.recordException(err);
routesUtils.statsReport500(err, statsClient); }
return routesUtils.responseNoBody(err, corsHeaders, routesUtils.statsReport500(err, statsClient);
response, 200, log); activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
}); routesUtils.responseNoBody(err, corsHeaders, response, 200,
} else if (query.replication !== undefined) { log);
api.callApiMethod('bucketPutReplication', request, response, log, }, {
(err, corsHeaders) => { cloudserverApiSpan,
routesUtils.statsReport500(err, statsClient); activeSpan,
routesUtils.responseNoBody(err, corsHeaders, response, 200, activeTracerContext,
log); tracer,
}); });
} else if (query.lifecycle !== undefined) { } else if (query.website !== undefined) {
api.callApiMethod('bucketPutLifecycle', request, response, log, activeSpan.updateName('S3 API request');
(err, corsHeaders) => { activeSpan.addEvent('Detected PutBucketWebsite API request');
routesUtils.statsReport500(err, statsClient); activeSpan.setAttribute('rpc.method', 'PutBucketWebsite');
routesUtils.responseNoBody(err, corsHeaders, response, 200, return api.callApiMethod('bucketPutWebsite', request, response, log,
log); (err, corsHeaders) => {
}); cloudserverApiSpan.end();
} else if (query.policy !== undefined) { activeSpan.addEvent('PutBucketWebsite API operation complete');
api.callApiMethod('bucketPutPolicy', request, response, log, if (err?.code === 500) {
(err, corsHeaders) => { activeSpan.recordException(err);
routesUtils.statsReport500(err, statsClient); }
routesUtils.responseNoBody(err, corsHeaders, response, 200, routesUtils.statsReport500(err, statsClient);
log); activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
}); return routesUtils.responseNoBody(err, corsHeaders,
} else if (query['object-lock'] !== undefined) { response, 200, log);
api.callApiMethod('bucketPutObjectLock', request, response, log, }, {
(err, corsHeaders) => { cloudserverApiSpan,
routesUtils.statsReport500(err, statsClient); activeSpan,
routesUtils.responseNoBody(err, corsHeaders, response, 200, activeTracerContext,
log); tracer,
}); });
} else if (query.notification !== undefined) { } else if (query.tagging !== undefined) {
api.callApiMethod('bucketPutNotification', request, response, log, activeSpan.updateName('S3 API request');
(err, corsHeaders) => { activeSpan.addEvent('Detected PutBucketTagging API request');
routesUtils.statsReport500(err, statsClient); activeSpan.setAttribute('rpc.method', 'PutBucketTagging');
routesUtils.responseNoBody(err, corsHeaders, response, 200, return api.callApiMethod('bucketPutTagging', request, response, log,
log); (err, corsHeaders) => {
}); cloudserverApiSpan.end();
} else if (query.encryption !== undefined) { activeSpan.addEvent('PutBucketTagging API operation complete');
api.callApiMethod('bucketPutEncryption', request, response, log, if (err?.code === 500) {
(err, corsHeaders) => { activeSpan.recordException(err);
routesUtils.statsReport500(err, statsClient); }
return routesUtils.responseNoBody(err, corsHeaders, routesUtils.statsReport500(err, statsClient);
response, 200, log); activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
}); return routesUtils.responseNoBody(err, corsHeaders,
response, 200, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else if (query.cors !== undefined) {
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected PutBucketCors API request');
activeSpan.setAttribute('rpc.method', 'PutBucketCors');
return api.callApiMethod('bucketPutCors', request, response, log,
(err, corsHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('PutBucketCors API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseNoBody(err, corsHeaders,
response, 200, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else if (query.replication !== undefined) {
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected PutBucketReplication API request');
activeSpan.setAttribute('rpc.method', 'PutBucketReplication');
return api.callApiMethod('bucketPutReplication', request, response, log,
(err, corsHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('PutBucketReplication API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
routesUtils.responseNoBody(err, corsHeaders, response, 200,
log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else if (query.lifecycle !== undefined) {
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected PutBucketLifecycle API request');
activeSpan.setAttribute('rpc.method', 'PutBucketLifecycle');
return api.callApiMethod('bucketPutLifecycle', request, response, log,
(err, corsHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('PutBucketLifecycle API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
routesUtils.responseNoBody(err, corsHeaders, response, 200,
log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else if (query.policy !== undefined) {
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected PutBucketPolicy API request');
activeSpan.setAttribute('rpc.method', 'PutBucketPolicy');
return api.callApiMethod('bucketPutPolicy', request, response, log,
(err, corsHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('PutBucketPolicy API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
routesUtils.responseNoBody(err, corsHeaders, response, 200,
log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else if (query['object-lock'] !== undefined) {
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected PutObjectLockConfiguration API request');
activeSpan.setAttribute('rpc.method', 'PutObjectLockConfiguration');
return api.callApiMethod('bucketPutObjectLock', request, response, log,
(err, corsHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('PutObjectLockConfiguration API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
routesUtils.responseNoBody(err, corsHeaders, response, 200,
log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else if (query.notification !== undefined) {
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected PutBucketNotificationConfiguration API request');
activeSpan.setAttribute('rpc.method', 'PutBucketNotificationConfiguration');
return api.callApiMethod('bucketPutNotification', request, response, log,
(err, corsHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('PutBucketNotificationConfiguration API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
routesUtils.responseNoBody(err, corsHeaders, response, 200,
log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else if (query.encryption !== undefined) {
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected PutBucketEncryption API request');
activeSpan.setAttribute('rpc.method', 'PutBucketEncryption');
return api.callApiMethod('bucketPutEncryption', request, response, log,
(err, corsHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('PutBucketEncryption API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseNoBody(err, corsHeaders,
response, 200, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else {
// PUT bucket
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected PutBucket API request');
activeSpan.setAttribute('rpc.method', 'PutBucket');
return api.callApiMethod('bucketPut', request, response, log,
(err, corsHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('PutBucket API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
const location = { Location: `/${bucketName}` };
const resHeaders = corsHeaders ?
Object.assign({}, location, corsHeaders) : location;
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
}
} else { } else {
// PUT bucket // PUT object, PUT object ACL, PUT object multipart,
return api.callApiMethod('bucketPut', request, response, log, // PUT object copy or PUT object legal hold
(err, corsHeaders) => { // if content-md5 is not present in the headers, try to
routesUtils.statsReport500(err, statsClient); // parse content-md5 from meta headers
const location = { Location: `/${bucketName}` };
const resHeaders = corsHeaders ?
Object.assign({}, location, corsHeaders) : location;
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
});
}
} else {
// PUT object, PUT object ACL, PUT object multipart,
// PUT object copy or PUT object legal hold
// if content-md5 is not present in the headers, try to
// parse content-md5 from meta headers
if (request.headers['content-md5'] === '') { if (request.headers['content-md5'] === '') {
log.debug('empty content-md5 header', { activeSpan.recordException(errors.InvalidDigest);
method: 'routePUT', cloudserverApiSpan.end();
}); log.debug('empty content-md5 header', {
return routesUtils method: 'routePUT',
.responseNoBody(errors.InvalidDigest, null, response, 200, log); });
} return routesUtils
if (request.headers['content-md5']) { .responseNoBody(errors.InvalidDigest, null, response, 200, log);
// @ts-ignore }
request.contentMD5 = request.headers['content-md5']; if (request.headers['content-md5']) {
} else { // @ts-ignore
// @ts-ignore request.contentMD5 = request.headers['content-md5'];
request.contentMD5 = routesUtils.parseContentMD5(request.headers); } else {
} // @ts-ignore
// @ts-ignore request.contentMD5 = routesUtils.parseContentMD5(request.headers);
if (request.contentMD5 && request.contentMD5.length !== 32) { }
// @ts-ignore
request.contentMD5 = Buffer.from(request.contentMD5, 'base64').toString('hex');
// @ts-ignore // @ts-ignore
if (request.contentMD5 && request.contentMD5.length !== 32) { if (request.contentMD5 && request.contentMD5.length !== 32) {
// @ts-ignore // @ts-ignore
log.debug('invalid md5 digest', { contentMD5: request.contentMD5 }); request.contentMD5 = Buffer.from(request.contentMD5, 'base64').toString('hex');
return routesUtils // @ts-ignore
.responseNoBody(errors.InvalidDigest, null, response, 200, if (request.contentMD5 && request.contentMD5.length !== 32) {
log); activeSpan.recordException(errors.InvalidDigest);
cloudserverApiSpan.end();
// @ts-ignore
log.debug('invalid md5 digest', { contentMD5: request.contentMD5 });
return routesUtils
.responseNoBody(errors.InvalidDigest, null, response, 200,
log);
}
} }
} if (query.partNumber) {
if (query.partNumber) { if (request.headers['x-amz-copy-source']) {
if (request.headers['x-amz-copy-source']) { activeSpan.updateName('S3 API request');
api.callApiMethod('objectPutCopyPart', request, response, log, activeSpan.addEvent('Detected UploadPartCopy API request');
(err, xml, additionalHeaders) => { activeSpan.setAttribute('rpc.method', 'UploadPartCopy');
return api.callApiMethod('objectPutCopyPart', request, response, log,
(err, xml, additionalHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('UploadPartCopy API operation complete');
if (err) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseXMLBody(err, xml, response, log,
additionalHeaders);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else {
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected UploadPart API request');
activeSpan.setAttribute('rpc.method', 'UploadPart');
return api.callApiMethod('objectPutPart', request, response, log,
(err, calculatedHash, corsHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('UploadPart API operation complete');
if (err) {
return routesUtils.responseNoBody(err, corsHeaders,
response, 200, log);
}
// ETag's hex should always be enclosed in quotes
const resMetaHeaders = corsHeaders || {};
resMetaHeaders.ETag = `"${calculatedHash}"`;
if (err) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseNoBody(err, resMetaHeaders,
response, 200, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
}
} else if (query.acl !== undefined) {
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected PutObjectAcl API request');
activeSpan.setAttribute('rpc.method', 'PutObjectAcl');
return api.callApiMethod('objectPutACL', request, response, log,
(err, resHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('PutObjectAcl API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient); routesUtils.statsReport500(err, statsClient);
return routesUtils.responseXMLBody(err, xml, response, log, activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else if (query['legal-hold'] !== undefined) {
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected PutObjectLegalHold API request');
activeSpan.setAttribute('rpc.method', 'PutObjectLegalHold');
return api.callApiMethod('objectPutLegalHold', request, response, log,
(err, resHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('PutObjectLegalHold API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else if (query.tagging !== undefined) {
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected PutObjectTagging API request');
activeSpan.setAttribute('rpc.method', 'PutObjectTagging');
return api.callApiMethod('objectPutTagging', request, response, log,
(err, resHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('PutObjectTagging API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else if (query.retention !== undefined) {
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected PutObjectRetention API request');
activeSpan.setAttribute('rpc.method', 'PutObjectRetention');
return api.callApiMethod('objectPutRetention', request, response, log,
(err, resHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('PutObjectRetention API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
} else if (request.headers['x-amz-copy-source']) {
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected CopyObject API request');
activeSpan.setAttribute('rpc.method', 'CopyObject');
return api.callApiMethod('objectCopy', request, response, log,
(err, xml, additionalHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('CopyObject API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
routesUtils.responseXMLBody(err, xml, response, log,
additionalHeaders); additionalHeaders);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
}); });
} else { } else {
api.callApiMethod('objectPutPart', request, response, log, if (request.headers['content-length'] === undefined &&
(err, calculatedHash, corsHeaders) => { request.headers['x-amz-decoded-content-length'] === undefined) {
if (err) { activeSpan.recordException(errors.MissingContentLength);
return routesUtils.responseNoBody(err, corsHeaders, cloudserverApiSpan.end();
response, 200, log); return routesUtils.responseNoBody(errors.MissingContentLength,
null, response, 411, log);
}
if (Number.isNaN(parsedContentLength) || parsedContentLength < 0) {
activeSpan.recordException(errors.BadRequest);
cloudserverApiSpan.end();
return routesUtils.responseNoBody(errors.BadRequest,
null, response, 400, log);
}
// TODO ARSN-216 What's happening?
// @ts-ignore
log.end().addDefaultFields({ contentLength: request.parsedContentLength });
activeSpan.updateName('S3 API request');
activeSpan.addEvent('Detected PutObject API request');
activeSpan.setAttribute('rpc.method', 'PutObject');
api.callApiMethod('objectPut', request, response, log,
(err, resHeaders) => {
cloudserverApiSpan.end();
activeSpan.addEvent('PutObject API operation complete');
if (err?.code === 500) {
activeSpan.recordException(err);
} }
// ETag's hex should always be enclosed in quotes
const resMetaHeaders = corsHeaders || {};
resMetaHeaders.ETag = `"${calculatedHash}"`;
routesUtils.statsReport500(err, statsClient); routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, resMetaHeaders, activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log); response, 200, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
}); });
} }
} else if (query.acl !== undefined) {
api.callApiMethod('objectPutACL', request, response, log,
(err, resHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
});
} else if (query['legal-hold'] !== undefined) {
api.callApiMethod('objectPutLegalHold', request, response, log,
(err, resHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
});
} else if (query.tagging !== undefined) {
api.callApiMethod('objectPutTagging', request, response, log,
(err, resHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
});
} else if (query.retention !== undefined) {
api.callApiMethod('objectPutRetention', request, response, log,
(err, resHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
});
} else if (request.headers['x-amz-copy-source']) {
return api.callApiMethod('objectCopy', request, response, log,
(err, xml, additionalHeaders) => {
routesUtils.statsReport500(err, statsClient);
routesUtils.responseXMLBody(err, xml, response, log,
additionalHeaders);
});
} else {
if (request.headers['content-length'] === undefined &&
request.headers['x-amz-decoded-content-length'] === undefined) {
return routesUtils.responseNoBody(errors.MissingContentLength,
null, response, 411, log);
}
if (Number.isNaN(parsedContentLength) || parsedContentLength < 0) {
return routesUtils.responseNoBody(errors.BadRequest,
null, response, 400, log);
}
// TODO ARSN-216 What's happening?
// @ts-ignore
log.end().addDefaultFields({ contentLength: request.parsedContentLength });
api.callApiMethod('objectPut', request, response, log,
(err, resHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
});
} }
} return undefined;
return undefined; });
} }

View File

@ -13,76 +13,129 @@ export default function routerWebsite(
statsClient?: StatsClient, statsClient?: StatsClient,
dataRetrievalFn?: any, dataRetrievalFn?: any,
) { ) {
const { bucketName, query } = request as any const {
log.debug('routing request', { method: 'routerWebsite' }); oTel: {
// website endpoint only supports GET and HEAD and must have a bucket tracer,
// http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteEndpoints.html activeSpan,
if ((request.method !== 'GET' && request.method !== 'HEAD') activeTracerContext,
|| !bucketName) { }
return routesUtils.errorHtmlResponse(errors.MethodNotAllowed, } = dataRetrievalFn;
false, bucketName, response, null, log); return tracer.startActiveSpan('Arsenal:: Performing Website API related operations using Cloudserver, Vault and Metadata', undefined, activeTracerContext, cloudserverApiSpan => {
} activeSpan.addEvent('Request validated, routing request using routerWebsite() in arsenal');
if (request.method === 'GET') { cloudserverApiSpan.setAttributes({
return api.callApiMethod('websiteGet', request, response, log, 'code.lineno': 8,
(err, userErrorPageFailure, dataGetInfo, resMetaHeaders, 'code.filename': 'lib/s3routes/routes/routeWebsite.ts',
redirectInfo, key) => { 'code.function': 'routerWebsite()',
routesUtils.statsReport500(err, statsClient); });
// request being redirected activeSpan.addEvent('Detecting which API to route to using arsenal routerWebsite()');
if (redirectInfo) { const { bucketName, query } = request as any
if (err && redirectInfo.withError) { log.debug('routing request', { method: 'routerWebsite' });
return routesUtils.redirectRequestOnError(err, // website endpoint only supports GET and HEAD and must have a bucket
'GET', redirectInfo, dataGetInfo, dataRetrievalFn, // http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteEndpoints.html
response, resMetaHeaders, log) if ((request.method !== 'GET' && request.method !== 'HEAD')
|| !bucketName) {
activeSpan.recordException(errors.MethodNotAllowed);
cloudserverApiSpan.end();
return routesUtils.errorHtmlResponse(errors.MethodNotAllowed,
false, bucketName, response, null, log);
}
if (request.method === 'GET') {
activeSpan.updateName('S3 API request');
activeSpan.addEvent(`Detected GetWebsite API request`);
activeSpan.setAttribute('rpc.method', 'GetWebsite');
return api.callApiMethod('websiteGet', request, response, log,
(err, userErrorPageFailure, dataGetInfo, resMetaHeaders,
redirectInfo, key) => {
cloudserverApiSpan.end();
activeSpan.addEvent('Located Data')
if (err?.code === 500) {
activeSpan.recordException(err);
} }
// note that key might have been modified in websiteGet routesUtils.statsReport500(err, statsClient);
// api to add index document // request being redirected
return routesUtils.redirectRequest(redirectInfo, if (redirectInfo) {
// TODO ARSN-217 encrypted does not exists in request.connection if (err && redirectInfo.withError) {
// @ts-ignore activeSpan.recordException(err);
key, request.connection.encrypted, return routesUtils.redirectRequestOnError(err,
response, request.headers.host!, resMetaHeaders, log); 'GET', redirectInfo, dataGetInfo, dataRetrievalFn,
} response, resMetaHeaders, log)
// user has their own error page }
if (err && dataGetInfo) { activeSpan.addEvent('Redirecting request');
return routesUtils.streamUserErrorPage(err, dataGetInfo, // note that key might have been modified in websiteGet
dataRetrievalFn, response, resMetaHeaders, log); // api to add index document
} return routesUtils.redirectRequest(redirectInfo,
// send default error html response // TODO ARSN-217 encrypted does not exists in request.connection
if (err) { // @ts-ignore
return routesUtils.errorHtmlResponse(err, key, request.connection.encrypted,
userErrorPageFailure, bucketName, response, request.headers.host!, resMetaHeaders, log);
response, resMetaHeaders, log);
}
// no error, stream data
return routesUtils.responseStreamData(null, query,
resMetaHeaders, dataGetInfo, dataRetrievalFn, response,
undefined, log);
});
}
if (request.method === 'HEAD') {
return api.callApiMethod('websiteHead', request, response, log,
(err, resMetaHeaders, redirectInfo, key) => {
routesUtils.statsReport500(err, statsClient);
if (redirectInfo) {
if (err && redirectInfo.withError) {
return routesUtils.redirectRequestOnError(err,
'HEAD', redirectInfo, null, dataRetrievalFn,
response, resMetaHeaders, log)
} }
return routesUtils.redirectRequest(redirectInfo, // user has their own error page
// TODO ARSN-217 encrypted does not exists in request.connection if (err && dataGetInfo) {
// @ts-ignore activeSpan.recordException(err);
key, request.connection.encrypted, return routesUtils.streamUserErrorPage(err, dataGetInfo,
response, request.headers.host!, resMetaHeaders, log); dataRetrievalFn, response, resMetaHeaders, log);
} }
// could redirect on err so check for redirectInfo first // send default error html response
if (err) { if (err) {
return routesUtils.errorHeaderResponse(err, response, activeSpan.recordException(err);
resMetaHeaders, log); return routesUtils.errorHtmlResponse(err,
} userErrorPageFailure, bucketName,
return routesUtils.responseContentHeaders(err, {}, resMetaHeaders, response, resMetaHeaders, log);
response, log); }
}); // no error, stream data
} return routesUtils.responseStreamData(null, query,
return undefined; resMetaHeaders, dataGetInfo, dataRetrievalFn, response,
undefined, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
}
if (request.method === 'HEAD') {
activeSpan.updateName('S3 API request');
activeSpan.addEvent(`Detected HeadWebsite API request`);
activeSpan.setAttribute('rpc.method', 'HeadWebsite');
return api.callApiMethod('websiteHead', request, response, log,
(err, resMetaHeaders, redirectInfo, key) => {
cloudserverApiSpan.end();
activeSpan.addEvent('HeadWebsite API operation complete')
if (err?.code === 500) {
activeSpan.recordException(err);
}
routesUtils.statsReport500(err, statsClient);
if (redirectInfo) {
if (err && redirectInfo.withError) {
activeSpan.recordException(err);
return routesUtils.redirectRequestOnError(err,
'HEAD', redirectInfo, null, dataRetrievalFn,
response, resMetaHeaders, log)
}
activeSpan.addEvent('Redirecting request');
return routesUtils.redirectRequest(redirectInfo,
// TODO ARSN-217 encrypted does not exists in request.connection
// @ts-ignore
key, request.connection.encrypted,
response, request.headers.host!, resMetaHeaders, log);
}
// could redirect on err so check for redirectInfo first
if (err) {
activeSpan.recordException(err);
return routesUtils.errorHeaderResponse(err, response,
resMetaHeaders, log);
}
activeSpan.addEvent('Finalizing Response with Content Headers and sending response to client');
return routesUtils.responseContentHeaders(err, {}, resMetaHeaders,
response, log);
}, {
cloudserverApiSpan,
activeSpan,
activeTracerContext,
tracer,
});
}
return undefined;
});
} }

View File

@ -1,6 +1,7 @@
import * as url from 'url'; import * as url from 'url';
import * as http from 'http'; import * as http from 'http';
import { eachSeries } from 'async'; import { eachSeries } from 'async';
const opentelemetry = require('@opentelemetry/api');
import { RequestLogger } from 'werelogs'; import { RequestLogger } from 'werelogs';
@ -16,6 +17,10 @@ export type CallApiMethod = (
response: http.ServerResponse, response: http.ServerResponse,
log: RequestLogger, log: RequestLogger,
callback: (err: ArsenalError | null, ...data: any[]) => void, callback: (err: ArsenalError | null, ...data: any[]) => void,
cloudserverApiSpan?: any,
activeSpan?: any,
activeTracerContext?: any,
tracer?: any,
) => void; ) => void;
/** /**
@ -350,6 +355,10 @@ function retrieveData(
retrieveDataParams: any, retrieveDataParams: any,
response: http.ServerResponse, response: http.ServerResponse,
log: RequestLogger, log: RequestLogger,
sproxydSpan?: any,
activeSpan?: any,
activeTracerContext?: any,
tracer?: any,
) { ) {
if (locations.length === 0) { if (locations.length === 0) {
return response.end(); return response.end();
@ -367,6 +376,8 @@ function retrieveData(
}; };
// the S3-client might close the connection while we are processing it // the S3-client might close the connection while we are processing it
response.once('close', () => { response.once('close', () => {
activeSpan.addEvent('response closed by client request');
sproxydSpan?.end();
responseDestroyed = true; responseDestroyed = true;
if (currentStream) { if (currentStream) {
currentStream.destroy(); currentStream.destroy();
@ -384,58 +395,71 @@ function retrieveData(
} = retrieveDataParams; } = retrieveDataParams;
const data = new DataWrapper( const data = new DataWrapper(
client, implName, config, kms, metadata, locStorageCheckFn, vault); client, implName, config, kms, metadata, locStorageCheckFn, vault);
return eachSeries(locations, return tracer.startActiveSpan('Streaming Data Using Sproxyd', dataSpan => {
(current, next) => data.get(current, response, log, dataSpan.setAttributes({
(err: any, readable: http.IncomingMessage) => { 'code.function': 'Arsenal:: retrieveData()',
// NB: readable is of IncomingMessage type 'code.filepath': 'lib/s3routes/routesUtils.js',
if (err) { 'code.lineno': 349,
log.error('failed to get object', { });
error: err, return eachSeries(locations,
method: 'retrieveData', (current, next) => data.get(current, response, log,
}); (err: any, readable: http.IncomingMessage) => {
_destroyResponse(); // NB: readable is of IncomingMessage type
return next(err); if (err) {
} log.error('failed to get object', {
// response.isclosed is set by the S3 server. Might happen if error: err,
// the S3-client closes the connection before the first request method: 'retrieveData',
// to the backend is started. });
// @ts-expect-error _destroyResponse();
if (responseDestroyed || response.isclosed) { return next(err);
log.debug( }
'response destroyed before readable could stream'); // response.isclosed is set by the S3 server. Might happen if
readable.destroy(); // the S3-client closes the connection before the first request
const responseErr = new Error(); // to the backend is started.
// @ts-ignore
responseErr.code = 'ResponseError';
responseErr.message = 'response closed by client request before all data sent';
return next(responseErr);
}
// readable stream successfully consumed
readable.on('end', () => {
currentStream = null;
log.debug('readable stream end reached');
return next();
});
// errors on server side with readable stream
readable.on('error', err => {
log.error('error piping data from source');
_destroyResponse();
return next(err);
});
currentStream = readable;
return readable.pipe(response, { end: false });
}), err => {
currentStream = null;
if (err) {
log.debug('abort response due to error', {
// @ts-expect-error // @ts-expect-error
error: err.code, errMsg: err.message }); if (responseDestroyed || response.isclosed) {
} log.debug(
// call end for all cases (error/success) per node.js docs 'response destroyed before readable could stream');
// recommendation readable.destroy();
response.end(); const responseErr = new Error();
}, // @ts-ignore
); responseErr.code = 'ResponseError';
responseErr.message = 'response closed by client request before all data sent';
return next(responseErr);
}
// readable stream successfully consumed
readable.on('end', () => {
sproxydSpan?.addEvent('Readable stream successfully consumed');
dataSpan.end();
sproxydSpan?.end();
currentStream = null;
log.debug('readable stream end reached');
return next();
});
// errors on server side with readable stream
readable.on('error', err => {
activeSpan.recordException(err);
dataSpan.end();
sproxydSpan?.end();
log.error('error piping data from source');
_destroyResponse();
return next(err);
});
currentStream = readable;
return readable.pipe(response, { end: false });
}), err => {
currentStream = null;
if (err) {
log.debug('abort response due to error', {
// @ts-expect-error
error: err.code, errMsg: err.message });
}
// call end for all cases (error/success) per node.js docs
// recommendation
response.end();
},
);
});
} }
function _responseBody( function _responseBody(
@ -606,46 +630,62 @@ export function responseStreamData(
range: [number, number] | undefined, range: [number, number] | undefined,
log: RequestLogger, log: RequestLogger,
) { ) {
if (errCode && !response.headersSent) { const{
return XMLResponseBackend.errorResponse(errCode, response, log, oTel: {
resHeaders); tracer,
} activeSpan,
if (dataLocations !== null && !response.headersSent) { activeTracerContext,
// sanity check of content length against individual data },
// locations to fetch } = retrieveDataParams;
const contentLength = resHeaders && resHeaders['Content-Length']; activeSpan.addEvent('Request processed, getting Data from sproxyd');
if (contentLength !== undefined && return tracer.startActiveSpan('Getting Object Data from RING', undefined, activeTracerContext, sproxydSpan => {
!_contentLengthMatchesLocations(contentLength, sproxydSpan.setAttributes({
dataLocations)) { 'code.function': 'Arsenal:: responseStreamData()',
log.error('logic error: total length of fetched data ' + 'code.filepath': 'lib/s3routes/routesUtils.js',
'locations does not match returned content-length', 'code.lineno': 609,
{ contentLength, dataLocations }); })
return XMLResponseBackend.errorResponse(errors.InternalError, if (errCode && !response.headersSent) {
response, log, return XMLResponseBackend.errorResponse(errCode, response, log,
resHeaders); resHeaders);
} }
} if (dataLocations !== null && !response.headersSent) {
if (!response.headersSent) { // sanity check of content length against individual data
okContentHeadersResponse(overrideParams, resHeaders, response, // locations to fetch
range, log); const contentLength = resHeaders && resHeaders['Content-Length'];
} if (contentLength !== undefined &&
if (dataLocations === null || _computeContentLengthFromLocation(dataLocations) === 0) { !_contentLengthMatchesLocations(contentLength,
return response.end(() => { dataLocations)) {
log.error('logic error: total length of fetched data ' +
'locations does not match returned content-length',
{ contentLength, dataLocations });
return XMLResponseBackend.errorResponse(errors.InternalError,
response, log,
resHeaders);
}
}
if (!response.headersSent) {
okContentHeadersResponse(overrideParams, resHeaders, response,
range, log);
}
if (dataLocations === null || _computeContentLengthFromLocation(dataLocations) === 0) {
return response.end(() => {
// TODO ARSN-216 Fix logger
// @ts-expect-error
log.end().info('responded with only metadata', {
httpCode: response.statusCode,
});
});
}
response.on('finish', () => {
// TODO ARSN-216 Fix logger // TODO ARSN-216 Fix logger
activeSpan.addEvent('Data retrieved from sproxyd and sending response to client');
// @ts-expect-error // @ts-expect-error
log.end().info('responded with only metadata', { log.end().info('responded with streamed content', {
httpCode: response.statusCode, httpCode: response.statusCode,
}); });
}); });
} return retrieveData(dataLocations, retrieveDataParams, response, log, sproxydSpan, activeSpan, activeTracerContext, tracer);
response.on('finish', () => {
// TODO ARSN-216 Fix logger
// @ts-expect-error
log.end().info('responded with streamed content', {
httpCode: response.statusCode,
});
}); });
return retrieveData(dataLocations, retrieveDataParams, response, log);
} }
/** /**
@ -666,18 +706,26 @@ export function streamUserErrorPage(
corsHeaders: { [key: string]: string }, corsHeaders: { [key: string]: string },
log: RequestLogger, log: RequestLogger,
) { ) {
const{
oTel: {
tracer,
activeSpan,
activeTracerContext,
},
} = retrieveDataParams;
setCommonResponseHeaders(corsHeaders, response, log); setCommonResponseHeaders(corsHeaders, response, log);
response.setHeader('x-amz-error-code', err.message); response.setHeader('x-amz-error-code', err.message);
response.setHeader('x-amz-error-message', err.description); response.setHeader('x-amz-error-message', err.description);
response.writeHead(err.code, { 'Content-type': 'text/html' }); response.writeHead(err.code, { 'Content-type': 'text/html' });
response.on('finish', () => { response.on('finish', () => {
// TODO ARSN-216 Fix logger // TODO ARSN-216 Fix logger
activeSpan.recordException(err);
// @ts-expect-error // @ts-expect-error
log.end().info('responded with streamed content', { log.end().info('responded with streamed content', {
httpCode: response.statusCode, httpCode: response.statusCode,
}); });
}); });
return retrieveData(dataLocations, retrieveDataParams, response, log); return retrieveData(dataLocations, retrieveDataParams, response, log, undefined, activeSpan, activeTracerContext, tracer);
} }
/** /**