Compare commits
27 Commits
developmen
...
S3C-8896-O
Author | SHA1 | Date |
---|---|---|
Anurag Mittal | d90bc4f741 | |
Anurag Mittal | b9dd7139ad | |
Anurag Mittal | 6543d9f88d | |
Anurag Mittal | 44efcd625c | |
Anurag Mittal | c6bb489ade | |
Anurag Mittal | 76c4c2b2bb | |
Anurag Mittal | d15d6f8a06 | |
Anurag Mittal | 8d40bab08f | |
Anurag Mittal | 24f6d8374e | |
Anurag Mittal | bb3b448757 | |
Anurag Mittal | 892dee6c13 | |
Anurag Mittal | 6876861b5d | |
Anurag Mittal | ff66b13a1a | |
Anurag Mittal | a1ac267b48 | |
Anurag Mittal | a12716ffe3 | |
Anurag Mittal | 05173de018 | |
Anurag Mittal | b75d73fe40 | |
Anurag Mittal | 052113c0ff | |
Anurag Mittal | 5af62b174d | |
Anurag Mittal | 4e158a25b6 | |
Anurag Mittal | 8fd1c42d8d | |
Anurag Mittal | f77da8a8a2 | |
Anurag Mittal | a291fbc10b | |
Anurag Mittal | 4a76a9c5f6 | |
Anurag Mittal | 068570bc26 | |
Anurag Mittal | b7122681c2 | |
Anurag Mittal | 3f7eb4c31d |
119
lib/auth/auth.ts
119
lib/auth/auth.ts
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue