Compare commits
10 Commits
892dee6c13
...
d90bc4f741
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 |
|
@ -68,6 +68,12 @@ function extractParams(
|
||||||
} = oTel;
|
} = oTel;
|
||||||
activeSpan?.addEvent('Arsenal:: entered Arsenal.auth.server.extractParams');
|
activeSpan?.addEvent('Arsenal:: entered Arsenal.auth.server.extractParams');
|
||||||
return tracer.startActiveSpan('Check validity of request parameters to authenticate using Arsenal', undefined, activeTracerContext, extractParamsSpan => {
|
return tracer.startActiveSpan('Check validity of request parameters to authenticate using Arsenal', undefined, activeTracerContext, extractParamsSpan => {
|
||||||
|
extractParamsSpan.setAttributes({
|
||||||
|
'code.lineno': 75,
|
||||||
|
'code.filename': 'lib/auth/auth.ts',
|
||||||
|
'code.function': 'extractParams',
|
||||||
|
'code.url': 'https://github.com/scality/arsenal/blob/892dee6c1333fcc25c88333ee991f02830cb3c51/lib/auth/auth.ts',
|
||||||
|
});
|
||||||
log.trace('entered', { method: 'Arsenal.auth.server.extractParams' });
|
log.trace('entered', { method: 'Arsenal.auth.server.extractParams' });
|
||||||
const authHeader = request.headers.authorization;
|
const authHeader = request.headers.authorization;
|
||||||
let version: 'v2' |'v4' | null = null;
|
let version: 'v2' |'v4' | null = null;
|
||||||
|
@ -108,8 +114,7 @@ function extractParams(
|
||||||
activeSpan?.addEvent(`Arsenal:: Identified auth method version: ${version} and method: ${method}`);
|
activeSpan?.addEvent(`Arsenal:: Identified auth method version: ${version} and method: ${method}`);
|
||||||
activeSpan?.addEvent('Arsenal:: Checking if valid request headers and query are used to make request to vault');
|
activeSpan?.addEvent('Arsenal:: Checking if valid request headers and query are used to make request to vault');
|
||||||
log.trace('identified auth method', { version, authMethod: method });
|
log.trace('identified auth method', { version, authMethod: method });
|
||||||
extractParamsSpan.end();
|
return checkFunctions[version][method](request, log, data, awsService, { activeSpan, extractParamsSpan, activeTracerContext, tracer });
|
||||||
return checkFunctions[version][method](request, log, data, awsService, { activeSpan, activeTracerContext, tracer });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// no auth info identified
|
// no auth info identified
|
||||||
|
|
|
@ -6,109 +6,97 @@ import checkRequestExpiry from './checkRequestExpiry';
|
||||||
import algoCheck from './algoCheck';
|
import algoCheck from './algoCheck';
|
||||||
|
|
||||||
export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) {
|
export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) {
|
||||||
const { activeSpan, activeTracerContext, tracer } = oTel;
|
const { activeSpan, extractParamsSpan, activeTracerContext, tracer } = oTel;
|
||||||
activeSpan?.addEvent('Entered V2 header auth check');
|
activeSpan?.addEvent('Entered V2 header auth check');
|
||||||
return tracer.startActiveSpan('V2 Header Auth Check', undefined, activeTracerContext, authCheckSpan => {
|
log.trace('running header auth check');
|
||||||
log.trace('running header auth check');
|
activeSpan?.addEvent('Running header auth check');
|
||||||
activeSpan?.addEvent('Running header auth check');
|
const headers = request.headers;
|
||||||
|
activeSpan?.addEvent('Extracting security token');
|
||||||
const headers = request.headers;
|
const token = headers['x-amz-security-token'];
|
||||||
|
if (token && !constants.iamSecurityToken.pattern.test(token)) {
|
||||||
activeSpan?.addEvent('Extracting security token');
|
log.debug('invalid security token', { token });
|
||||||
const token = headers['x-amz-security-token'];
|
activeSpan.recordException(errors.InvalidToken);
|
||||||
if (token && !constants.iamSecurityToken.pattern.test(token)) {
|
extractParamsSpan.end();
|
||||||
log.debug('invalid security token', { token });
|
return { err: errors.InvalidToken };
|
||||||
activeSpan.recordException(errors.InvalidToken);
|
}
|
||||||
authCheckSpan.end();
|
activeSpan?.addEvent('Extracted security token');
|
||||||
return { err: errors.InvalidToken };
|
activeSpan?.addEvent('Checking timestamp');
|
||||||
}
|
// Check to make sure timestamp is within 15 minutes of current time
|
||||||
activeSpan?.addEvent('Extracted security token');
|
let timestamp = headers['x-amz-date'] ? headers['x-amz-date'] : headers.date;
|
||||||
|
timestamp = Date.parse(timestamp);
|
||||||
activeSpan?.addEvent('Checking timestamp');
|
if (!timestamp) {
|
||||||
// Check to make sure timestamp is within 15 minutes of current time
|
log.debug('missing or invalid date header', { method: 'auth/v2/headerAuthCheck.check' });
|
||||||
let timestamp = headers['x-amz-date'] ? headers['x-amz-date'] : headers.date;
|
activeSpan.recordException(errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header'));
|
||||||
timestamp = Date.parse(timestamp);
|
extractParamsSpan.end();
|
||||||
if (!timestamp) {
|
return { err: errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header') };
|
||||||
log.debug('missing or invalid date header', { method: 'auth/v2/headerAuthCheck.check' });
|
}
|
||||||
activeSpan.recordException(errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header'));
|
activeSpan?.addEvent('Checked timestamp');
|
||||||
authCheckSpan.end();
|
activeSpan?.addEvent('Checking request expiry');
|
||||||
return { err: errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header') };
|
const err = checkRequestExpiry(timestamp, log);
|
||||||
}
|
if (err) {
|
||||||
activeSpan?.addEvent('Checked timestamp');
|
activeSpan.recordException(err);
|
||||||
|
extractParamsSpan.end();
|
||||||
activeSpan?.addEvent('Checking request expiry');
|
return { err };
|
||||||
const err = checkRequestExpiry(timestamp, log);
|
}
|
||||||
if (err) {
|
activeSpan?.addEvent('Checked request expiry');
|
||||||
activeSpan.recordException(err);
|
activeSpan?.addEvent('Extracting authorization header');
|
||||||
authCheckSpan.end();
|
// Authorization Header should be in the format of 'AWS AccessKey:Signature'
|
||||||
return { err };
|
const authInfo = headers.authorization;
|
||||||
}
|
activeSpan?.addEvent('Extracted authorization header');
|
||||||
activeSpan?.addEvent('Checked request expiry');
|
if (!authInfo) {
|
||||||
|
log.debug('missing authorization security header');
|
||||||
activeSpan?.addEvent('Extracting authorization header');
|
activeSpan.recordException(errors.MissingSecurityHeader);
|
||||||
// Authorization Header should be in the format of 'AWS AccessKey:Signature'
|
extractParamsSpan.end();
|
||||||
const authInfo = headers.authorization;
|
return { err: errors.MissingSecurityHeader };
|
||||||
activeSpan?.addEvent('Extracted authorization header');
|
}
|
||||||
|
const semicolonIndex = authInfo.indexOf(':');
|
||||||
if (!authInfo) {
|
if (semicolonIndex < 0) {
|
||||||
log.debug('missing authorization security header');
|
log.debug('invalid authorization header', { authInfo });
|
||||||
activeSpan.recordException(errors.MissingSecurityHeader);
|
activeSpan.recordException(errors.InvalidArgument);
|
||||||
authCheckSpan.end();
|
extractParamsSpan.end();
|
||||||
return { err: errors.MissingSecurityHeader };
|
return { err: errors.InvalidArgument };
|
||||||
}
|
}
|
||||||
const semicolonIndex = authInfo.indexOf(':');
|
const accessKey = semicolonIndex > 4 ? authInfo.substring(4, semicolonIndex).trim() : undefined;
|
||||||
if (semicolonIndex < 0) {
|
if (typeof accessKey !== 'string' || accessKey.length === 0) {
|
||||||
log.debug('invalid authorization header', { authInfo });
|
log.trace('invalid authorization header', { authInfo });
|
||||||
activeSpan.recordException(errors.InvalidArgument);
|
activeSpan.recordException(errors.MissingSecurityHeader);
|
||||||
authCheckSpan.end();
|
extractParamsSpan.end();
|
||||||
return { err: errors.InvalidArgument };
|
return { err: errors.MissingSecurityHeader };
|
||||||
}
|
}
|
||||||
const accessKey = semicolonIndex > 4 ? authInfo.substring(4, semicolonIndex).trim() : undefined;
|
// @ts-ignore
|
||||||
if (typeof accessKey !== 'string' || accessKey.length === 0) {
|
log.addDefaultFields({ accessKey });
|
||||||
log.trace('invalid authorization header', { authInfo });
|
const signatureFromRequest = authInfo.substring(semicolonIndex + 1).trim();
|
||||||
activeSpan.recordException(errors.MissingSecurityHeader);
|
log.trace('signature from request', { signatureFromRequest });
|
||||||
authCheckSpan.end();
|
activeSpan?.addEvent('Extracting signature from request');
|
||||||
return { err: errors.MissingSecurityHeader };
|
activeSpan?.addEvent('Constructing string to sign');
|
||||||
}
|
const stringToSign = constructStringToSign(request, data, log);
|
||||||
// @ts-ignore
|
log.trace('constructed string to sign', { stringToSign });
|
||||||
log.addDefaultFields({ accessKey });
|
activeSpan?.addEvent('Constructed string to sign v2 headers');
|
||||||
|
const algo = algoCheck(signatureFromRequest.length);
|
||||||
const signatureFromRequest = authInfo.substring(semicolonIndex + 1).trim();
|
log.trace('algo for calculating signature', { algo });
|
||||||
log.trace('signature from request', { signatureFromRequest });
|
activeSpan?.addEvent('Checked algorithm for calculating signature');
|
||||||
activeSpan?.addEvent('Extracting signature from request');
|
if (algo === undefined) {
|
||||||
|
activeSpan.recordException(errors.InvalidArgument);
|
||||||
const stringToSign = constructStringToSign(request, data, log);
|
extractParamsSpan.end();
|
||||||
log.trace('constructed string to sign', { stringToSign });
|
return { err: errors.InvalidArgument };
|
||||||
activeSpan?.addEvent('Constructed string to sign');
|
}
|
||||||
|
activeSpan?.addEvent('Exiting V2 header auth check');
|
||||||
const algo = algoCheck(signatureFromRequest.length);
|
extractParamsSpan.end();
|
||||||
log.trace('algo for calculating signature', { algo });
|
return {
|
||||||
activeSpan?.addEvent('Checked algorithm for calculating signature');
|
err: null,
|
||||||
|
params: {
|
||||||
if (algo === undefined) {
|
version: 2,
|
||||||
activeSpan.recordException(errors.InvalidArgument);
|
data: {
|
||||||
authCheckSpan.end();
|
accessKey,
|
||||||
return { err: errors.InvalidArgument };
|
signatureFromRequest,
|
||||||
}
|
stringToSign,
|
||||||
|
algo,
|
||||||
activeSpan?.addEvent('Exiting V2 header auth check');
|
authType: 'REST-HEADER',
|
||||||
authCheckSpan.end();
|
signatureVersion: 'AWS',
|
||||||
return {
|
signatureAge: Date.now() - timestamp,
|
||||||
err: null,
|
securityToken: token,
|
||||||
params: {
|
|
||||||
version: 2,
|
|
||||||
data: {
|
|
||||||
accessKey,
|
|
||||||
signatureFromRequest,
|
|
||||||
stringToSign,
|
|
||||||
algo,
|
|
||||||
authType: 'REST-HEADER',
|
|
||||||
signatureVersion: 'AWS',
|
|
||||||
signatureAge: Date.now() - timestamp,
|
|
||||||
securityToken: token,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,113 +5,98 @@ import algoCheck from './algoCheck';
|
||||||
import constructStringToSign from './constructStringToSign';
|
import constructStringToSign from './constructStringToSign';
|
||||||
|
|
||||||
export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) {
|
export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) {
|
||||||
const { activeSpan, activeTracerContext, tracer } = oTel;
|
const { activeSpan, extractParamsSpan, activeTracerContext, tracer } = oTel;
|
||||||
activeSpan?.addEvent('Entered query auth check');
|
activeSpan?.addEvent('Entered query auth check');
|
||||||
return tracer.startActiveSpan('Query Auth Check', undefined, activeTracerContext, authCheckSpan => {
|
log.trace('running query auth check');
|
||||||
log.trace('running query auth check');
|
activeSpan?.addEvent('Running query auth check');
|
||||||
activeSpan?.addEvent('Running query auth check');
|
if (request.method === 'POST') {
|
||||||
|
log.debug('query string auth not supported for post requests');
|
||||||
if (request.method === 'POST') {
|
activeSpan.recordException(errors.NotImplemented);
|
||||||
log.debug('query string auth not supported for post requests');
|
extractParamsSpan.end();
|
||||||
activeSpan.recordException(errors.NotImplemented);
|
return { err: errors.NotImplemented };
|
||||||
authCheckSpan.end();
|
}
|
||||||
return { err: errors.NotImplemented };
|
const token = data.SecurityToken;
|
||||||
}
|
activeSpan?.addEvent('Extracting security token');
|
||||||
|
if (token && !constants.iamSecurityToken.pattern.test(token)) {
|
||||||
const token = data.SecurityToken;
|
log.debug('invalid security token', { token });
|
||||||
activeSpan?.addEvent('Extracting security token');
|
activeSpan.recordException(errors.InvalidToken);
|
||||||
if (token && !constants.iamSecurityToken.pattern.test(token)) {
|
extractParamsSpan.end();
|
||||||
log.debug('invalid security token', { token });
|
return { err: errors.InvalidToken };
|
||||||
activeSpan.recordException(errors.InvalidToken);
|
}
|
||||||
authCheckSpan.end();
|
activeSpan?.addEvent('Extracted security token');
|
||||||
return { err: errors.InvalidToken };
|
/*
|
||||||
}
|
Check whether request has expired or if
|
||||||
activeSpan?.addEvent('Extracted security token');
|
expires parameter is more than 604800000 milliseconds
|
||||||
|
(7 days) in the future.
|
||||||
/*
|
Expires time is provided in seconds so need to
|
||||||
Check whether request has expired or if
|
multiply by 1000 to obtain
|
||||||
expires parameter is more than 604800000 milliseconds
|
milliseconds to compare to Date.now()
|
||||||
(7 days) in the future.
|
*/
|
||||||
Expires time is provided in seconds so need to
|
activeSpan?.addEvent('Checking expiration time');
|
||||||
multiply by 1000 to obtain
|
const expirationTime = parseInt(data.Expires, 10) * 1000;
|
||||||
milliseconds to compare to Date.now()
|
if (Number.isNaN(expirationTime)) {
|
||||||
*/
|
log.debug('invalid expires parameter', { expires: data.Expires });
|
||||||
activeSpan?.addEvent('Checking expiration time');
|
activeSpan.recordException(errors.MissingSecurityHeader);
|
||||||
const expirationTime = parseInt(data.Expires, 10) * 1000;
|
extractParamsSpan.end();
|
||||||
if (Number.isNaN(expirationTime)) {
|
return { err: errors.MissingSecurityHeader };
|
||||||
log.debug('invalid expires parameter', { expires: data.Expires });
|
}
|
||||||
activeSpan.recordException(errors.MissingSecurityHeader);
|
activeSpan?.addEvent('Checked expiration time');
|
||||||
authCheckSpan.end();
|
const currentTime = Date.now();
|
||||||
return { err: errors.MissingSecurityHeader };
|
const preSignedURLExpiry = process.env.PRE_SIGN_URL_EXPIRY
|
||||||
}
|
&& !Number.isNaN(process.env.PRE_SIGN_URL_EXPIRY)
|
||||||
activeSpan?.addEvent('Checked expiration time');
|
? Number.parseInt(process.env.PRE_SIGN_URL_EXPIRY, 10)
|
||||||
|
: constants.defaultPreSignedURLExpiry * 1000;
|
||||||
const currentTime = Date.now();
|
if (expirationTime > currentTime + preSignedURLExpiry) {
|
||||||
|
log.debug('expires parameter too far in future', { expires: request.query.Expires });
|
||||||
const preSignedURLExpiry = process.env.PRE_SIGN_URL_EXPIRY
|
activeSpan.recordException(errors.AccessDenied);
|
||||||
&& !Number.isNaN(process.env.PRE_SIGN_URL_EXPIRY)
|
extractParamsSpan.end();
|
||||||
? Number.parseInt(process.env.PRE_SIGN_URL_EXPIRY, 10)
|
return { err: errors.AccessDenied };
|
||||||
: constants.defaultPreSignedURLExpiry * 1000;
|
}
|
||||||
|
if (currentTime > expirationTime) {
|
||||||
if (expirationTime > currentTime + preSignedURLExpiry) {
|
log.debug('current time exceeds expires time', { expires: request.query.Expires });
|
||||||
log.debug('expires parameter too far in future', { expires: request.query.Expires });
|
activeSpan.recordException(errors.RequestTimeTooSkewed);
|
||||||
activeSpan.recordException(errors.AccessDenied);
|
extractParamsSpan.end();
|
||||||
authCheckSpan.end();
|
return { err: errors.RequestTimeTooSkewed };
|
||||||
return { err: errors.AccessDenied };
|
}
|
||||||
}
|
const accessKey = data.AWSAccessKeyId;
|
||||||
if (currentTime > expirationTime) {
|
// @ts-ignore
|
||||||
log.debug('current time exceeds expires time', { expires: request.query.Expires });
|
log.addDefaultFields({ accessKey });
|
||||||
activeSpan.recordException(errors.RequestTimeTooSkewed);
|
const signatureFromRequest = decodeURIComponent(data.Signature);
|
||||||
authCheckSpan.end();
|
log.trace('signature from request', { signatureFromRequest });
|
||||||
return { err: errors.RequestTimeTooSkewed };
|
activeSpan?.addEvent('Extracting signature from request');
|
||||||
}
|
if (!accessKey || !signatureFromRequest) {
|
||||||
|
log.debug('invalid access key/signature parameters');
|
||||||
const accessKey = data.AWSAccessKeyId;
|
activeSpan.recordException(errors.MissingSecurityHeader);
|
||||||
// @ts-ignore
|
extractParamsSpan.end();
|
||||||
log.addDefaultFields({ accessKey });
|
return { err: errors.MissingSecurityHeader };
|
||||||
|
}
|
||||||
const signatureFromRequest = decodeURIComponent(data.Signature);
|
const stringToSign = constructStringToSign(request, data, log);
|
||||||
log.trace('signature from request', { signatureFromRequest });
|
log.trace('constructed string to sign', { stringToSign });
|
||||||
activeSpan?.addEvent('Extracting signature from request');
|
activeSpan?.addEvent('Constructed string to sign v2 query');
|
||||||
|
const algo = algoCheck(signatureFromRequest.length);
|
||||||
if (!accessKey || !signatureFromRequest) {
|
log.trace('algo for calculating signature', { algo });
|
||||||
log.debug('invalid access key/signature parameters');
|
activeSpan?.addEvent('Checked algorithm for calculating signature');
|
||||||
activeSpan.recordException(errors.MissingSecurityHeader);
|
if (algo === undefined) {
|
||||||
authCheckSpan.end();
|
activeSpan.recordException(errors.InvalidArgument);
|
||||||
return { err: errors.MissingSecurityHeader };
|
extractParamsSpan.end();
|
||||||
}
|
return { err: errors.InvalidArgument };
|
||||||
|
}
|
||||||
const stringToSign = constructStringToSign(request, data, log);
|
activeSpan?.addEvent('Exiting query auth check');
|
||||||
log.trace('constructed string to sign', { stringToSign });
|
extractParamsSpan.end();
|
||||||
activeSpan?.addEvent('Constructed string to sign');
|
return {
|
||||||
|
err: null,
|
||||||
const algo = algoCheck(signatureFromRequest.length);
|
params: {
|
||||||
log.trace('algo for calculating signature', { algo });
|
version: 2,
|
||||||
activeSpan?.addEvent('Checked algorithm for calculating signature');
|
data: {
|
||||||
|
accessKey,
|
||||||
if (algo === undefined) {
|
signatureFromRequest,
|
||||||
activeSpan.recordException(errors.InvalidArgument);
|
stringToSign,
|
||||||
authCheckSpan.end();
|
algo,
|
||||||
return { err: errors.InvalidArgument };
|
authType: 'REST-QUERY-STRING',
|
||||||
}
|
signatureVersion: 'AWS',
|
||||||
|
securityToken: token,
|
||||||
activeSpan?.addEvent('Exiting query auth check');
|
|
||||||
authCheckSpan.end();
|
|
||||||
return {
|
|
||||||
err: null,
|
|
||||||
params: {
|
|
||||||
version: 2,
|
|
||||||
data: {
|
|
||||||
accessKey,
|
|
||||||
signatureFromRequest,
|
|
||||||
stringToSign,
|
|
||||||
algo,
|
|
||||||
authType: 'REST-QUERY-STRING',
|
|
||||||
signatureVersion: 'AWS',
|
|
||||||
securityToken: token,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,181 +22,165 @@ import {
|
||||||
* @param awsService - Aws service ('iam' or 's3')
|
* @param awsService - Aws service ('iam' or 's3')
|
||||||
*/
|
*/
|
||||||
export function check(request: any, log: Logger, data: { [key: string]: string }, awsService: string, oTel: any) {
|
export function check(request: any, log: Logger, data: { [key: string]: string }, awsService: string, oTel: any) {
|
||||||
const { activeSpan, activeTracerContext, tracer } = oTel;
|
const { activeSpan, extractParamsSpan, activeTracerContext, tracer } = oTel;
|
||||||
activeSpan?.addEvent('Entered V4 header auth check');
|
activeSpan?.addEvent('Entered V4 header auth check');
|
||||||
return tracer.startActiveSpan('V4 Header Auth Check', undefined, activeTracerContext, authCheckSpan => {
|
log.trace('running header auth check');
|
||||||
log.trace('running header auth check');
|
activeSpan?.addEvent('Extracting security token');
|
||||||
|
const token = request.headers['x-amz-security-token'];
|
||||||
activeSpan?.addEvent('Extracting security token');
|
if (token && !constants.iamSecurityToken.pattern.test(token)) {
|
||||||
const token = request.headers['x-amz-security-token'];
|
log.debug('invalid security token', { token });
|
||||||
if (token && !constants.iamSecurityToken.pattern.test(token)) {
|
activeSpan.recordException(errors.InvalidToken);
|
||||||
log.debug('invalid security token', { token });
|
extractParamsSpan.end();
|
||||||
activeSpan.recordException(errors.InvalidToken);
|
return { err: errors.InvalidToken };
|
||||||
authCheckSpan.end();
|
}
|
||||||
return { err: errors.InvalidToken };
|
activeSpan?.addEvent('Extracted security token');
|
||||||
}
|
activeSpan?.addEvent('Extracting authorization header');
|
||||||
activeSpan?.addEvent('Extracted security token');
|
const authHeader = request.headers.authorization;
|
||||||
|
if (!authHeader) {
|
||||||
activeSpan?.addEvent('Extracting authorization header');
|
log.debug('missing authorization header');
|
||||||
const authHeader = request.headers.authorization;
|
activeSpan.recordException(errors.MissingSecurityHeader);
|
||||||
if (!authHeader) {
|
extractParamsSpan.end();
|
||||||
log.debug('missing authorization header');
|
return { err: errors.MissingSecurityHeader };
|
||||||
activeSpan.recordException(errors.MissingSecurityHeader);
|
}
|
||||||
authCheckSpan.end();
|
activeSpan?.addEvent('Extracted authorization header');
|
||||||
return { err: errors.MissingSecurityHeader };
|
activeSpan?.addEvent('Extracting auth header items');
|
||||||
}
|
const authHeaderItems = extractAuthItems(authHeader, log);
|
||||||
activeSpan?.addEvent('Extracted authorization header');
|
if (Object.keys(authHeaderItems).length < 3) {
|
||||||
|
log.debug('invalid authorization header', { authHeader });
|
||||||
activeSpan?.addEvent('Extracting auth header items');
|
activeSpan.recordException(errors.InvalidArgument);
|
||||||
const authHeaderItems = extractAuthItems(authHeader, log);
|
extractParamsSpan.end();
|
||||||
if (Object.keys(authHeaderItems).length < 3) {
|
return { err: errors.InvalidArgument };
|
||||||
log.debug('invalid authorization header', { authHeader });
|
}
|
||||||
|
activeSpan?.addEvent('Extracted auth header items');
|
||||||
|
const payloadChecksum = request.headers['x-amz-content-sha256'];
|
||||||
|
if (!payloadChecksum && awsService !== 'iam') {
|
||||||
|
log.debug('missing payload checksum');
|
||||||
|
activeSpan.recordException(errors.MissingSecurityHeader);
|
||||||
|
extractParamsSpan.end();
|
||||||
|
return { err: errors.MissingSecurityHeader };
|
||||||
|
}
|
||||||
|
if (payloadChecksum === 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD') {
|
||||||
|
log.trace('requesting streaming v4 auth');
|
||||||
|
if (request.method !== 'PUT') {
|
||||||
|
log.debug('streaming v4 auth for put only', { method: 'auth/v4/headerAuthCheck.check' });
|
||||||
activeSpan.recordException(errors.InvalidArgument);
|
activeSpan.recordException(errors.InvalidArgument);
|
||||||
authCheckSpan.end();
|
extractParamsSpan.end();
|
||||||
return { err: errors.InvalidArgument };
|
return { err: errors.InvalidArgument };
|
||||||
}
|
}
|
||||||
activeSpan?.addEvent('Extracted auth header items');
|
if (!request.headers['x-amz-decoded-content-length']) {
|
||||||
|
|
||||||
const payloadChecksum = request.headers['x-amz-content-sha256'];
|
|
||||||
if (!payloadChecksum && awsService !== 'iam') {
|
|
||||||
log.debug('missing payload checksum');
|
|
||||||
activeSpan.recordException(errors.MissingSecurityHeader);
|
activeSpan.recordException(errors.MissingSecurityHeader);
|
||||||
authCheckSpan.end();
|
extractParamsSpan.end();
|
||||||
return { err: errors.MissingSecurityHeader };
|
return { err: errors.MissingSecurityHeader };
|
||||||
}
|
}
|
||||||
if (payloadChecksum === 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD') {
|
}
|
||||||
log.trace('requesting streaming v4 auth');
|
log.trace('authorization header from request', { authHeader });
|
||||||
if (request.method !== 'PUT') {
|
const signatureFromRequest = authHeaderItems.signatureFromRequest!;
|
||||||
log.debug('streaming v4 auth for put only', { method: 'auth/v4/headerAuthCheck.check' });
|
const credentialsArr = authHeaderItems.credentialsArr!;
|
||||||
activeSpan.recordException(errors.InvalidArgument);
|
const signedHeaders = authHeaderItems.signedHeaders!;
|
||||||
authCheckSpan.end();
|
activeSpan.addEvent('Checking if signed headers are complete');
|
||||||
return { err: errors.InvalidArgument };
|
if (!areSignedHeadersComplete(signedHeaders, request.headers)) {
|
||||||
}
|
log.debug('signedHeaders are incomplete', { signedHeaders });
|
||||||
if (!request.headers['x-amz-decoded-content-length']) {
|
activeSpan.recordException(errors.AccessDenied);
|
||||||
activeSpan.recordException(errors.MissingSecurityHeader);
|
extractParamsSpan.end();
|
||||||
authCheckSpan.end();
|
return { err: errors.AccessDenied };
|
||||||
return { err: errors.MissingSecurityHeader };
|
}
|
||||||
}
|
activeSpan.addEvent('Signed headers are complete');
|
||||||
|
let timestamp: string | undefined;
|
||||||
|
// check request timestamp
|
||||||
|
activeSpan.addEvent('Checking request timestamp');
|
||||||
|
const xAmzDate = request.headers['x-amz-date'];
|
||||||
|
if (xAmzDate) {
|
||||||
|
const xAmzDateArr = xAmzDate.split('T');
|
||||||
|
// check that x-amz- date has the correct format and after epochTime
|
||||||
|
if (xAmzDateArr.length === 2 && xAmzDateArr[0].length === 8 && xAmzDateArr[1].length === 7 && Number.parseInt(xAmzDateArr[0], 10) > 19700101) {
|
||||||
|
// format of x-amz- date is ISO 8601: YYYYMMDDTHHMMSSZ
|
||||||
|
timestamp = request.headers['x-amz-date'];
|
||||||
}
|
}
|
||||||
|
} else if (request.headers.date) {
|
||||||
log.trace('authorization header from request', { authHeader });
|
timestamp = convertUTCtoISO8601(request.headers.date);
|
||||||
|
}
|
||||||
const signatureFromRequest = authHeaderItems.signatureFromRequest!;
|
if (!timestamp) {
|
||||||
const credentialsArr = authHeaderItems.credentialsArr!;
|
log.debug('missing or invalid date header', { method: 'auth/v4/headerAuthCheck.check' });
|
||||||
const signedHeaders = authHeaderItems.signedHeaders!;
|
activeSpan.recordException(errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header'));
|
||||||
|
extractParamsSpan.end();
|
||||||
activeSpan.addEvent('Checking if signed headers are complete');
|
return { err: errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header') };
|
||||||
if (!areSignedHeadersComplete(signedHeaders, request.headers)) {
|
}
|
||||||
log.debug('signedHeaders are incomplete', { signedHeaders });
|
activeSpan.addEvent('Request timestamp is valid');
|
||||||
activeSpan.recordException(errors.AccessDenied);
|
activeSpan.addEvent('Validating credentials');
|
||||||
authCheckSpan.end();
|
const validationResult = validateCredentials(credentialsArr, timestamp, log);
|
||||||
return { err: errors.AccessDenied };
|
if (validationResult instanceof Error) {
|
||||||
}
|
log.debug('credentials in improper format', { credentialsArr, timestamp, validationResult });
|
||||||
activeSpan.addEvent('Signed headers are complete');
|
activeSpan.recordException(validationResult);
|
||||||
|
extractParamsSpan.end();
|
||||||
let timestamp: string | undefined;
|
return { err: validationResult };
|
||||||
// check request timestamp
|
}
|
||||||
activeSpan.addEvent('Checking request timestamp');
|
activeSpan.addEvent('Credentials are valid');
|
||||||
const xAmzDate = request.headers['x-amz-date'];
|
// credentialsArr is [accessKey, date, region, aws-service, aws4_request]
|
||||||
if (xAmzDate) {
|
const scopeDate = credentialsArr[1];
|
||||||
const xAmzDateArr = xAmzDate.split('T');
|
const region = credentialsArr[2];
|
||||||
// check that x-amz- date has the correct format and after epochTime
|
const service = credentialsArr[3];
|
||||||
if (xAmzDateArr.length === 2 && xAmzDateArr[0].length === 8 && xAmzDateArr[1].length === 7 && Number.parseInt(xAmzDateArr[0], 10) > 19700101) {
|
const accessKey = credentialsArr.shift();
|
||||||
// format of x-amz- date is ISO 8601: YYYYMMDDTHHMMSSZ
|
const credentialScope = credentialsArr.join('/');
|
||||||
timestamp = request.headers['x-amz-date'];
|
// In AWS Signature Version 4, the signing key is valid for up to seven days
|
||||||
}
|
// (see Introduction to Signing Requests.
|
||||||
} else if (request.headers.date) {
|
// Therefore, a signature is also valid for up to seven days or
|
||||||
timestamp = convertUTCtoISO8601(request.headers.date);
|
// less if specified by a bucket policy.
|
||||||
}
|
// Since policies are not yet implemented, we will have a 15
|
||||||
if (!timestamp) {
|
// minute default like in v2 Auth.
|
||||||
log.debug('missing or invalid date header', { method: 'auth/v4/headerAuthCheck.check' });
|
// See http://docs.aws.amazon.com/AmazonS3/latest/API/
|
||||||
activeSpan.recordException(errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header'));
|
// bucket-policy-s3-sigv4-conditions.html
|
||||||
authCheckSpan.end();
|
// TODO: When implementing bucket policies,
|
||||||
return { err: errors.AccessDenied.customizeDescription('Authentication requires a valid Date or x-amz-date header') };
|
// note that expiration can be shortened so
|
||||||
}
|
// expiry is as set out in the policy.
|
||||||
activeSpan.addEvent('Request timestamp is valid');
|
// 15 minutes in seconds
|
||||||
|
activeSpan.addEvent('checking if signature is expired')
|
||||||
activeSpan.addEvent('Validating credentials');
|
const expiry = (15 * 60);
|
||||||
const validationResult = validateCredentials(credentialsArr, timestamp, log);
|
const isTimeSkewed = checkTimeSkew(timestamp, expiry, log);
|
||||||
if (validationResult instanceof Error) {
|
if (isTimeSkewed) {
|
||||||
log.debug('credentials in improper format', { credentialsArr, timestamp, validationResult });
|
activeSpan.recordException(errors.RequestTimeTooSkewed);
|
||||||
activeSpan.recordException(validationResult);
|
extractParamsSpan.end();
|
||||||
authCheckSpan.end();
|
return { err: errors.RequestTimeTooSkewed };
|
||||||
return { err: validationResult };
|
}
|
||||||
}
|
activeSpan.addEvent('signature is not expired');
|
||||||
activeSpan.addEvent('Credentials are valid');
|
activeSpan.addEvent('Constructing string to sign');
|
||||||
// credentialsArr is [accessKey, date, region, aws-service, aws4_request]
|
const stringToSign = constructStringToSign({
|
||||||
const scopeDate = credentialsArr[1];
|
log,
|
||||||
const region = credentialsArr[2];
|
request,
|
||||||
const service = credentialsArr[3];
|
query: data,
|
||||||
const accessKey = credentialsArr.shift();
|
signedHeaders,
|
||||||
const credentialScope = credentialsArr.join('/');
|
credentialScope,
|
||||||
|
timestamp,
|
||||||
// In AWS Signature Version 4, the signing key is valid for up to seven days
|
payloadChecksum,
|
||||||
// (see Introduction to Signing Requests.
|
awsService: service,
|
||||||
// Therefore, a signature is also valid for up to seven days or
|
}, oTel);
|
||||||
// less if specified by a bucket policy.
|
log.trace('constructed stringToSign', { stringToSign });
|
||||||
// Since policies are not yet implemented, we will have a 15
|
if (stringToSign instanceof Error) {
|
||||||
// minute default like in v2 Auth.
|
activeSpan.recordException(stringToSign);
|
||||||
// See http://docs.aws.amazon.com/AmazonS3/latest/API/
|
extractParamsSpan.end();
|
||||||
// bucket-policy-s3-sigv4-conditions.html
|
return { err: stringToSign };
|
||||||
// TODO: When implementing bucket policies,
|
}
|
||||||
// note that expiration can be shortened so
|
activeSpan.addEvent('Constructed string to sign v4 headers');
|
||||||
// expiry is as set out in the policy.
|
activeSpan.addEvent('Exiting V4 header auth check');
|
||||||
|
extractParamsSpan.end();
|
||||||
// 15 minutes in seconds
|
return {
|
||||||
activeSpan.addEvent('checking if signature is expired')
|
err: null,
|
||||||
const expiry = (15 * 60);
|
params: {
|
||||||
const isTimeSkewed = checkTimeSkew(timestamp, expiry, log);
|
version: 4,
|
||||||
if (isTimeSkewed) {
|
data: {
|
||||||
activeSpan.recordException(errors.RequestTimeTooSkewed);
|
accessKey,
|
||||||
authCheckSpan.end();
|
signatureFromRequest,
|
||||||
return { err: errors.RequestTimeTooSkewed };
|
region,
|
||||||
}
|
service,
|
||||||
activeSpan.addEvent('signature is not expired');
|
scopeDate,
|
||||||
|
stringToSign,
|
||||||
activeSpan.addEvent('Constructing string to sign');
|
authType: 'REST-HEADER',
|
||||||
const stringToSign = constructStringToSign({
|
signatureVersion: 'AWS4-HMAC-SHA256',
|
||||||
log,
|
signatureAge: Date.now() - convertAmzTimeToMs(timestamp),
|
||||||
request,
|
credentialScope,
|
||||||
query: data,
|
timestamp,
|
||||||
signedHeaders,
|
securityToken: token,
|
||||||
credentialScope,
|
|
||||||
timestamp,
|
|
||||||
payloadChecksum,
|
|
||||||
awsService: service,
|
|
||||||
});
|
|
||||||
log.trace('constructed stringToSign', { stringToSign });
|
|
||||||
if (stringToSign instanceof Error) {
|
|
||||||
activeSpan.recordException(stringToSign);
|
|
||||||
authCheckSpan.end();
|
|
||||||
return { err: stringToSign };
|
|
||||||
}
|
|
||||||
|
|
||||||
activeSpan.addEvent('Constructed string to sign');
|
|
||||||
|
|
||||||
activeSpan.addEvent('Exiting V4 header auth check');
|
|
||||||
authCheckSpan.end();
|
|
||||||
return {
|
|
||||||
err: null,
|
|
||||||
params: {
|
|
||||||
version: 4,
|
|
||||||
data: {
|
|
||||||
accessKey,
|
|
||||||
signatureFromRequest,
|
|
||||||
region,
|
|
||||||
service,
|
|
||||||
scopeDate,
|
|
||||||
stringToSign,
|
|
||||||
authType: 'REST-HEADER',
|
|
||||||
signatureVersion: 'AWS4-HMAC-SHA256',
|
|
||||||
signatureAge: Date.now() - convertAmzTimeToMs(timestamp),
|
|
||||||
credentialScope,
|
|
||||||
timestamp,
|
|
||||||
securityToken: token,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,115 +15,116 @@ import { areSignedHeadersComplete } from './validateInputs';
|
||||||
export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) {
|
export function check(request: any, log: Logger, data: { [key: string]: string }, oTel: any) {
|
||||||
const {
|
const {
|
||||||
activeSpan,
|
activeSpan,
|
||||||
|
extractParamsSpan,
|
||||||
activeTracerContext,
|
activeTracerContext,
|
||||||
tracer,
|
tracer,
|
||||||
} = oTel;
|
} = oTel;
|
||||||
activeSpan?.addEvent('Arsenal:: entered Arsenal.auth.v4.queryAuthCheck');
|
activeSpan?.addEvent('Arsenal:: entered Arsenal.auth.v4.queryAuthCheck');
|
||||||
return tracer.startActiveSpan('Arsenal::Arsenal.auth.v4.queryAuthCheck', undefined, activeTracerContext, authCheckSpan => {
|
activeSpan?.addEvent('Arsenal:: extracting query parameters')
|
||||||
activeSpan?.addEvent('Arsenal:: extracting query parameters')
|
const authParams = extractQueryParams(data, log);
|
||||||
const authParams = extractQueryParams(data, log);
|
activeSpan?.addEvent('Arsenal:: extracting query params');
|
||||||
activeSpan?.addEvent('Arsenal:: extracting query params');
|
if (Object.keys(authParams).length !== 5) {
|
||||||
if (Object.keys(authParams).length !== 5) {
|
activeSpan.recordException(errors.InvalidArgument);
|
||||||
activeSpan.recordException(errors.InvalidArgument);
|
extractParamsSpan.end();
|
||||||
authCheckSpan.end();
|
return { err: errors.InvalidArgument };
|
||||||
return { err: errors.InvalidArgument };
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Query params are not specified in AWS documentation as case-insensitive,
|
// Query params are not specified in AWS documentation as case-insensitive,
|
||||||
// so we use case-sensitive
|
// so we use case-sensitive
|
||||||
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);
|
activeSpan.recordException(errors.InvalidToken);
|
||||||
authCheckSpan.end();
|
extractParamsSpan.end();
|
||||||
return { err: errors.InvalidToken };
|
return { err: errors.InvalidToken };
|
||||||
}
|
}
|
||||||
|
|
||||||
const signedHeaders = authParams.signedHeaders!;
|
const signedHeaders = authParams.signedHeaders!;
|
||||||
const signatureFromRequest = authParams.signatureFromRequest!;
|
const signatureFromRequest = authParams.signatureFromRequest!;
|
||||||
const timestamp = authParams.timestamp!;
|
const timestamp = authParams.timestamp!;
|
||||||
const expiry = authParams.expiry!;
|
const expiry = authParams.expiry!;
|
||||||
const credential = authParams.credential!;
|
const credential = authParams.credential!;
|
||||||
|
|
||||||
if (!areSignedHeadersComplete(signedHeaders, request.headers)) {
|
if (!areSignedHeadersComplete(signedHeaders, request.headers)) {
|
||||||
log.debug('signedHeaders are incomplete', { signedHeaders });
|
log.debug('signedHeaders are incomplete', { signedHeaders });
|
||||||
activeSpan.recordException(errors.AccessDenied);
|
activeSpan.recordException(errors.AccessDenied);
|
||||||
authCheckSpan.end();
|
extractParamsSpan.end();
|
||||||
return { err: errors.AccessDenied };
|
return { err: errors.AccessDenied };
|
||||||
}
|
}
|
||||||
|
|
||||||
const validationResult = validateCredentials(credential, timestamp,
|
const validationResult = validateCredentials(credential, timestamp,
|
||||||
log);
|
log);
|
||||||
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);
|
activeSpan.recordException(validationResult);
|
||||||
authCheckSpan.end();
|
extractParamsSpan.end();
|
||||||
return { err: validationResult };
|
return { err: validationResult };
|
||||||
}
|
}
|
||||||
const accessKey = credential[0];
|
const accessKey = credential[0];
|
||||||
const scopeDate = credential[1];
|
const scopeDate = credential[1];
|
||||||
const region = credential[2];
|
const region = credential[2];
|
||||||
const service = credential[3];
|
const service = credential[3];
|
||||||
const requestType = credential[4];
|
const requestType = credential[4];
|
||||||
|
|
||||||
const isTimeSkewed = checkTimeSkew(timestamp, expiry, log);
|
const isTimeSkewed = checkTimeSkew(timestamp, expiry, log);
|
||||||
if (isTimeSkewed) {
|
if (isTimeSkewed) {
|
||||||
activeSpan.recordException(errors.RequestTimeTooSkewed);
|
activeSpan.recordException(errors.RequestTimeTooSkewed);
|
||||||
authCheckSpan.end();
|
extractParamsSpan.end();
|
||||||
return { err: errors.RequestTimeTooSkewed };
|
return { err: errors.RequestTimeTooSkewed };
|
||||||
}
|
}
|
||||||
|
|
||||||
// In query v4 auth, the canonical request needs
|
// In query v4 auth, the canonical request needs
|
||||||
// to include the query params OTHER THAN
|
// to include the query params OTHER THAN
|
||||||
// the signature so create a
|
// the signature so create a
|
||||||
// copy of the query object and remove
|
// copy of the query object and remove
|
||||||
// the X-Amz-Signature property.
|
// the X-Amz-Signature property.
|
||||||
const queryWithoutSignature = Object.assign({}, data);
|
const queryWithoutSignature = Object.assign({}, data);
|
||||||
delete queryWithoutSignature['X-Amz-Signature'];
|
delete queryWithoutSignature['X-Amz-Signature'];
|
||||||
|
|
||||||
// For query auth, instead of a
|
// For query auth, instead of a
|
||||||
// checksum of the contents, the
|
// checksum of the contents, the
|
||||||
// string 'UNSIGNED-PAYLOAD' should be
|
// string 'UNSIGNED-PAYLOAD' should be
|
||||||
// added to the canonicalRequest in
|
// added to the canonicalRequest in
|
||||||
// building string to sign
|
// building string to sign
|
||||||
const payloadChecksum = 'UNSIGNED-PAYLOAD';
|
const payloadChecksum = 'UNSIGNED-PAYLOAD';
|
||||||
|
|
||||||
const stringToSign = constructStringToSign({
|
activeSpan?.addEvent('Constructing string to sign');
|
||||||
log,
|
const stringToSign = constructStringToSign({
|
||||||
request,
|
log,
|
||||||
query: queryWithoutSignature,
|
request,
|
||||||
signedHeaders,
|
query: queryWithoutSignature,
|
||||||
payloadChecksum,
|
signedHeaders,
|
||||||
timestamp,
|
payloadChecksum,
|
||||||
credentialScope:
|
timestamp,
|
||||||
`${scopeDate}/${region}/${service}/${requestType}`,
|
credentialScope:
|
||||||
awsService: service,
|
`${scopeDate}/${region}/${service}/${requestType}`,
|
||||||
});
|
awsService: service,
|
||||||
if (stringToSign instanceof Error) {
|
}, oTel);
|
||||||
activeSpan.recordException(stringToSign);
|
activeSpan?.addEvent('Constructed string to sign v4 query');
|
||||||
authCheckSpan.end();
|
if (stringToSign instanceof Error) {
|
||||||
return { err: stringToSign };
|
activeSpan.recordException(stringToSign);
|
||||||
}
|
extractParamsSpan.end();
|
||||||
log.trace('constructed stringToSign', { stringToSign });
|
return { err: stringToSign };
|
||||||
activeSpan.addEvent('Arsenal:: exiting Arsenal.auth.v4.queryAuthCheck');
|
}
|
||||||
authCheckSpan.end();
|
log.trace('constructed stringToSign', { stringToSign });
|
||||||
return {
|
activeSpan.addEvent('Arsenal:: exiting Arsenal.auth.v4.queryAuthCheck');
|
||||||
err: null,
|
extractParamsSpan.end();
|
||||||
params: {
|
return {
|
||||||
version: 4,
|
err: null,
|
||||||
data: {
|
params: {
|
||||||
accessKey,
|
version: 4,
|
||||||
signatureFromRequest,
|
data: {
|
||||||
region,
|
accessKey,
|
||||||
scopeDate,
|
signatureFromRequest,
|
||||||
stringToSign,
|
region,
|
||||||
authType: 'REST-QUERY-STRING',
|
scopeDate,
|
||||||
signatureVersion: 'AWS4-HMAC-SHA256',
|
stringToSign,
|
||||||
signatureAge: Date.now() - convertAmzTimeToMs(timestamp),
|
authType: 'REST-QUERY-STRING',
|
||||||
securityToken: token,
|
signatureVersion: 'AWS4-HMAC-SHA256',
|
||||||
},
|
signatureAge: Date.now() - convertAmzTimeToMs(timestamp),
|
||||||
|
securityToken: token,
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,6 @@ export default function routerGET(
|
||||||
} = dataRetrievalParams;
|
} = dataRetrievalParams;
|
||||||
return tracer.startActiveSpan('Arsenal:: Performing Get API related operations using Cloudserver, Vault and Metadata', undefined, activeTracerContext, cloudserverApiSpan => {
|
return tracer.startActiveSpan('Arsenal:: Performing Get API related operations using Cloudserver, Vault and Metadata', undefined, activeTracerContext, cloudserverApiSpan => {
|
||||||
activeSpan.addEvent('Request validated, routing request using routeGET() in arsenal');
|
activeSpan.addEvent('Request validated, routing request using routeGET() in arsenal');
|
||||||
cloudserverApiSpan.setAttributes({
|
|
||||||
'code.lineno': 9,
|
|
||||||
'code.filename': 'lib/s3routes/routes/routeGET.ts',
|
|
||||||
'code.function': 'routerGET()',
|
|
||||||
})
|
|
||||||
activeSpan.addEvent('Detecting which API to route to using arsenal routeGET()')
|
activeSpan.addEvent('Detecting which API to route to using arsenal routeGET()')
|
||||||
log.debug('routing request', { method: 'routerGET' });
|
log.debug('routing request', { method: 'routerGET' });
|
||||||
|
|
||||||
|
|
|
@ -638,7 +638,7 @@ export function responseStreamData(
|
||||||
},
|
},
|
||||||
} = retrieveDataParams;
|
} = retrieveDataParams;
|
||||||
activeSpan.addEvent('Request processed, getting Data from sproxyd');
|
activeSpan.addEvent('Request processed, getting Data from sproxyd');
|
||||||
return tracer.startActiveSpan('Getting Data From RING', undefined, activeTracerContext, sproxydSpan => {
|
return tracer.startActiveSpan('Getting Object Data from RING', undefined, activeTracerContext, sproxydSpan => {
|
||||||
sproxydSpan.setAttributes({
|
sproxydSpan.setAttributes({
|
||||||
'code.function': 'Arsenal:: responseStreamData()',
|
'code.function': 'Arsenal:: responseStreamData()',
|
||||||
'code.filepath': 'lib/s3routes/routesUtils.js',
|
'code.filepath': 'lib/s3routes/routesUtils.js',
|
||||||
|
|
Loading…
Reference in New Issue