Compare commits
No commits in common. "3363bb4bc0f52d11c568b25b4fe566dbec880262" and "a643a3e6ccbc49327339a285de1d4cb17afcd171" have entirely different histories.
3363bb4bc0
...
a643a3e6cc
lib
s3routes/routes
|
@ -21,13 +21,10 @@ const checkFunctions = {
|
||||||
v2: {
|
v2: {
|
||||||
headers: v2.header.check,
|
headers: v2.header.check,
|
||||||
query: v2.query.check,
|
query: v2.query.check,
|
||||||
// TODO ARSN-414 check v2 auth for POST requests with form data
|
|
||||||
// form: v2.form.check,
|
|
||||||
},
|
},
|
||||||
v4: {
|
v4: {
|
||||||
headers: v4.header.check,
|
headers: v4.header.check,
|
||||||
query: v4.query.check,
|
query: v4.query.check,
|
||||||
form: v4.form.check,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -66,7 +63,7 @@ function extractParams(
|
||||||
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;
|
||||||
let method: 'query' | 'headers' | 'form' | null = null;
|
let method: 'query' | 'headers' | null = null;
|
||||||
|
|
||||||
// Identify auth version and method to dispatch to the right check function
|
// Identify auth version and method to dispatch to the right check function
|
||||||
if (authHeader) {
|
if (authHeader) {
|
||||||
|
@ -88,16 +85,6 @@ function extractParams(
|
||||||
} else if (data['X-Amz-Algorithm']) {
|
} else if (data['X-Amz-Algorithm']) {
|
||||||
method = 'query';
|
method = 'query';
|
||||||
version = 'v4';
|
version = 'v4';
|
||||||
} if (data.Policy) {
|
|
||||||
if (data['X-Amz-Algorithm']) {
|
|
||||||
method = 'form';
|
|
||||||
version = 'v4';
|
|
||||||
}
|
|
||||||
// TODO ARSN-414 check v2 auth for POST requests with form data
|
|
||||||
// if (formData.Signature) {
|
|
||||||
// method = 'form';
|
|
||||||
// version = 'v2';
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here, either both values are set, or none is set
|
// Here, either both values are set, or none is set
|
||||||
|
@ -134,8 +121,7 @@ function doAuth(
|
||||||
awsService: string,
|
awsService: string,
|
||||||
requestContexts: any[] | null
|
requestContexts: any[] | null
|
||||||
) {
|
) {
|
||||||
const data: { [key: string]: string; } = request.formData || request.query || {};
|
const res = extractParams(request, log, awsService, request.query);
|
||||||
const res = extractParams(request, log, awsService, data);
|
|
||||||
if (res.err) {
|
if (res.err) {
|
||||||
return cb(res.err);
|
return cb(res.err);
|
||||||
} else if (res.params instanceof AuthInfo) {
|
} else if (res.params instanceof AuthInfo) {
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
export * as header from './headerAuthCheck';
|
export * as header from './headerAuthCheck';
|
||||||
export * as query from './queryAuthCheck';
|
export * as query from './queryAuthCheck';
|
||||||
export * as form from './formAuthCheck';
|
|
||||||
|
|
|
@ -1,187 +0,0 @@
|
||||||
import { Logger } from 'werelogs';
|
|
||||||
import * as constants from '../../constants';
|
|
||||||
import errors from '../../errors';
|
|
||||||
import constructStringToSign from './constructStringToSign';
|
|
||||||
import { checkTimeSkew, convertAmzTimeToMs } from './timeUtils';
|
|
||||||
import { validateCredentials, extractFormParams } from './validateInputs';
|
|
||||||
import { areSignedHeadersComplete } from './validateInputs';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* V4 query auth check
|
|
||||||
* @param request - HTTP request object
|
|
||||||
* @param log - logging object
|
|
||||||
* @param data - Contain authentification params (GET or POST data)
|
|
||||||
*/
|
|
||||||
export function check(request: any, log: Logger, data: { [key: string]: string }) {
|
|
||||||
const authParams = extractFormParams(data, log);
|
|
||||||
|
|
||||||
if (Object.keys(authParams).length !== 4) {
|
|
||||||
return { err: errors.InvalidArgument };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query params are not specified in AWS documentation as case-insensitive,
|
|
||||||
// so we use case-sensitive
|
|
||||||
const token = data['X-Amz-Security-Token'];
|
|
||||||
if (token && !constants.iamSecurityToken.pattern.test(token)) {
|
|
||||||
log.debug('invalid security token', { token });
|
|
||||||
return { err: errors.InvalidToken };
|
|
||||||
}
|
|
||||||
|
|
||||||
const signedHeaders = authParams.signedHeaders!;
|
|
||||||
const signatureFromRequest = authParams.signatureFromRequest!;
|
|
||||||
const timestamp = authParams.timestamp!;
|
|
||||||
//const expiry = authParams.expiry!;
|
|
||||||
const credential = authParams.credential!;
|
|
||||||
|
|
||||||
if (!areSignedHeadersComplete(signedHeaders, request.headers)) {
|
|
||||||
log.debug('signedHeaders are incomplete', { signedHeaders });
|
|
||||||
return { err: errors.AccessDenied };
|
|
||||||
}
|
|
||||||
|
|
||||||
const validationResult = validateCredentials(credential, timestamp,
|
|
||||||
log);
|
|
||||||
if (validationResult instanceof Error) {
|
|
||||||
log.debug('credentials in improper format', { credential,
|
|
||||||
timestamp, validationResult });
|
|
||||||
return { err: validationResult };
|
|
||||||
}
|
|
||||||
const accessKey = credential[0];
|
|
||||||
const scopeDate = credential[1];
|
|
||||||
const region = credential[2];
|
|
||||||
const service = credential[3];
|
|
||||||
const requestType = credential[4];
|
|
||||||
|
|
||||||
// In query v4 auth, the canonical request needs
|
|
||||||
// to include the query params OTHER THAN
|
|
||||||
// the signature so create a
|
|
||||||
// copy of the query object and remove
|
|
||||||
// the X-Amz-Signature property.
|
|
||||||
const queryWithoutSignature = Object.assign({}, data);
|
|
||||||
delete queryWithoutSignature['X-Amz-Signature'];
|
|
||||||
|
|
||||||
// For query auth, instead of a
|
|
||||||
// checksum of the contents, the
|
|
||||||
// string 'UNSIGNED-PAYLOAD' should be
|
|
||||||
// added to the canonicalRequest in
|
|
||||||
// building string to sign
|
|
||||||
const payloadChecksum = 'UNSIGNED-PAYLOAD';
|
|
||||||
|
|
||||||
// string to sign is the policy
|
|
||||||
const stringToSign = data['Policy'];
|
|
||||||
log.trace('constructed stringToSign', { stringToSign });
|
|
||||||
return {
|
|
||||||
err: null,
|
|
||||||
params: {
|
|
||||||
version: 4,
|
|
||||||
data: {
|
|
||||||
accessKey,
|
|
||||||
signatureFromRequest,
|
|
||||||
region,
|
|
||||||
scopeDate,
|
|
||||||
stringToSign,
|
|
||||||
service,
|
|
||||||
authType: 'REST-QUERY-STRING',
|
|
||||||
signatureVersion: 'AWS4-HMAC-SHA256',
|
|
||||||
signatureAge: Date.now() - convertAmzTimeToMs(timestamp),
|
|
||||||
securityToken: token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * V4 form auth check for POST Object request
|
|
||||||
// * @param request - HTTP request object containing form data
|
|
||||||
// * @param log - logging object
|
|
||||||
// */
|
|
||||||
// export function check(request: any, log: Logger, formData: { [key: string]: string }) {
|
|
||||||
// // Assume form data is already parsed and attached to request.body
|
|
||||||
|
|
||||||
// // Extract authentication parameters from formData
|
|
||||||
// const algorithm = formData['X-Amz-Algorithm'];
|
|
||||||
// const credentials = formData['X-Amz-Credential'];
|
|
||||||
// const date = formData['X-Amz-Date'];
|
|
||||||
// const securityToken = formData['X-Amz-Security-Token'];
|
|
||||||
// const signature = formData['X-Amz-Signature'];
|
|
||||||
|
|
||||||
// let splitCredentials : [string, string, string, string, string];
|
|
||||||
// if (credentials && credentials.length > 28 && credentials.indexOf('/') > -1) {
|
|
||||||
// // @ts-ignore
|
|
||||||
// splitCredentials = credentials.split('/');
|
|
||||||
// } else {
|
|
||||||
// log.debug('invalid credential param', { credentials,
|
|
||||||
// date });
|
|
||||||
// return { err: errors.InvalidArgument };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!algorithm || !splitCredentials || !date || !signature) {
|
|
||||||
// return { err: errors.InvalidArgument };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Validate the token if present
|
|
||||||
// if (securityToken && !constants.iamSecurityToken.pattern.test(securityToken)) {
|
|
||||||
// log.debug('invalid security token', { token: securityToken });
|
|
||||||
// return { err: errors.InvalidToken };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Checking credential format
|
|
||||||
// const validationResult = validateCredentials(splitCredentials, date,
|
|
||||||
// log);
|
|
||||||
// if (validationResult instanceof Error) {
|
|
||||||
// log.debug('credentials in improper format', { splitCredentials,
|
|
||||||
// date, validationResult });
|
|
||||||
// return { err: validationResult };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const accessKey = splitCredentials[0];
|
|
||||||
// const scopeDate = splitCredentials[1];
|
|
||||||
// const region = splitCredentials[2];
|
|
||||||
// const service = splitCredentials[3];
|
|
||||||
// const requestType = splitCredentials[4];
|
|
||||||
|
|
||||||
// // Verifying the timestamp and potential expiration
|
|
||||||
// const isTimeSkewed = checkTimeSkew(date, request.expiry, log);
|
|
||||||
// if (isTimeSkewed) {
|
|
||||||
// return { err: errors.RequestTimeTooSkewed };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Extract signed headers
|
|
||||||
// const signedHeaders = Object.keys(request.headers).map(key => key.toLowerCase()).sort().join(';');
|
|
||||||
|
|
||||||
|
|
||||||
// const stringToSign = constructStringToSign({
|
|
||||||
// request,
|
|
||||||
// signedHeaders,
|
|
||||||
// payloadChecksum: null,
|
|
||||||
// credentialScope:
|
|
||||||
// `${scopeDate}/${region}/${service}/${requestType}`,
|
|
||||||
// timestamp: date,
|
|
||||||
// query: formData,
|
|
||||||
// log,
|
|
||||||
// awsService: service,
|
|
||||||
// });
|
|
||||||
// if (stringToSign instanceof Error) {
|
|
||||||
// return { err: stringToSign };
|
|
||||||
// }
|
|
||||||
// log.trace('constructed stringToSign', { stringToSign });
|
|
||||||
|
|
||||||
// // If all checks are successful
|
|
||||||
// return {
|
|
||||||
// err: null,
|
|
||||||
// params: {
|
|
||||||
// version: 4,
|
|
||||||
// data: {
|
|
||||||
// accessKey: accessKey,
|
|
||||||
// signatureFromRequest: signature,
|
|
||||||
// date: date,
|
|
||||||
// region: region,
|
|
||||||
// scopeDate: scopeDate,
|
|
||||||
// stringToSign: stringToSign,
|
|
||||||
// authType: 'POST-OBJECT',
|
|
||||||
// signatureVersion: 'AWS4-HMAC-SHA256',
|
|
||||||
// signatureAge: Date.now() - convertAmzTimeToMs(date),
|
|
||||||
// securityToken: securityToken,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
|
@ -131,73 +131,6 @@ export function extractQueryParams(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract and validate components from formData object
|
|
||||||
* @param formObj - formData object from request
|
|
||||||
* @param log - logging object
|
|
||||||
* @return object containing extracted query params for authV4
|
|
||||||
*/
|
|
||||||
export function extractFormParams(
|
|
||||||
formObj: { [key: string]: string | undefined },
|
|
||||||
log: Logger
|
|
||||||
) {
|
|
||||||
const authParams: {
|
|
||||||
signedHeaders?: string;
|
|
||||||
signatureFromRequest?: string;
|
|
||||||
timestamp?: string;
|
|
||||||
expiry?: number;
|
|
||||||
credential?: [string, string, string, string, string];
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
// Do not need the algorithm sent back
|
|
||||||
if (formObj['X-Amz-Algorithm'] !== 'AWS4-HMAC-SHA256') {
|
|
||||||
log.warn('algorithm param incorrect',
|
|
||||||
{ algo: formObj['X-Amz-Algorithm'] });
|
|
||||||
return authParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
// adding placeholder for signedHeaders to satisfy Vault
|
|
||||||
// as this is not required for form auth
|
|
||||||
authParams.signedHeaders = 'content-type;host;x-amz-date;x-amz-security-token';
|
|
||||||
|
|
||||||
const signature = formObj['X-Amz-Signature'];
|
|
||||||
if (signature && signature.length === 64) {
|
|
||||||
authParams.signatureFromRequest = signature;
|
|
||||||
} else {
|
|
||||||
log.warn('missing signature');
|
|
||||||
return authParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timestamp = formObj['X-Amz-Date'];
|
|
||||||
if (timestamp && timestamp.length === 16) {
|
|
||||||
authParams.timestamp = timestamp;
|
|
||||||
} else {
|
|
||||||
log.warn('missing or invalid timestamp',
|
|
||||||
{ timestamp: formObj['X-Amz-Date'] });
|
|
||||||
return authParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO? ARSN-414 Does not seem to be required for form auth
|
|
||||||
// const expiry = Number.parseInt(formObj['X-Amz-Expires'] ?? 'nope', 10);
|
|
||||||
// const sevenDays = 604800;
|
|
||||||
// if (expiry && (expiry > 0 && expiry <= sevenDays)) {
|
|
||||||
// authParams.expiry = expiry;
|
|
||||||
// } else {
|
|
||||||
// log.warn('invalid expiry', { expiry });
|
|
||||||
// return authParams;
|
|
||||||
// }
|
|
||||||
|
|
||||||
const credential = formObj['X-Amz-Credential'];
|
|
||||||
if (credential && credential.length > 28 && credential.indexOf('/') > -1) {
|
|
||||||
// @ts-ignore
|
|
||||||
authParams.credential = credential.split('/');
|
|
||||||
} else {
|
|
||||||
log.warn('invalid credential param', { credential });
|
|
||||||
return authParams;
|
|
||||||
}
|
|
||||||
return authParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract and validate components from auth header
|
* Extract and validate components from auth header
|
||||||
* @param authHeader - authorization header from request
|
* @param authHeader - authorization header from request
|
||||||
|
|
|
@ -58,10 +58,6 @@ export default function routePOST(
|
||||||
corsHeaders));
|
corsHeaders));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (objectKey === undefined && Object.keys(query).length === 0) {
|
|
||||||
return api.callApiMethod('objectPost', request, response, log, (err, resHeaders) => routesUtils.responseNoBody(err, resHeaders, response, 200, log));
|
|
||||||
}
|
|
||||||
|
|
||||||
return routesUtils.responseNoBody(errors.NotImplemented, null, response,
|
return routesUtils.responseNoBody(errors.NotImplemented, null, response,
|
||||||
200, log);
|
200, log);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue