Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvement/arsn 424 post object auth #2250

Draft
wants to merge 7 commits into
base: improvement/ARSN-414-post-object
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions lib/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const checkFunctions = {
v4: {
headers: v4.header.check,
query: v4.query.check,
form: v4.form.check,
},
};

Expand Down Expand Up @@ -63,7 +64,7 @@ function extractParams(
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;
let method: 'query' | 'headers' | 'form' | null = null;

// Identify auth version and method to dispatch to the right check function
if (authHeader) {
Expand All @@ -85,6 +86,9 @@ function extractParams(
} else if (data['X-Amz-Algorithm']) {
method = 'query';
version = 'v4';
} if (data.policy) {
method = 'form';
version = 'v4';
}

// Here, either both values are set, or none is set
Expand Down Expand Up @@ -121,7 +125,8 @@ function doAuth(
awsService: string,
requestContexts: any[] | null
) {
const res = extractParams(request, log, awsService, request.query);
const data: { [key: string]: string; } = request.formData || request.query || {};
const res = extractParams(request, log, awsService, data);
if (res.err) {
return cb(res.err);
} else if (res.params instanceof AuthInfo) {
Expand Down
1 change: 1 addition & 0 deletions lib/auth/v4/authV4.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * as header from './headerAuthCheck';
export * as query from './queryAuthCheck';
export * as form from './formAuthCheck';
108 changes: 108 additions & 0 deletions lib/auth/v4/formAuthCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Logger } from 'werelogs';
import * as constants from '../../constants';
import errors from '../../errors';
import { convertAmzTimeToMs } from './timeUtils';
import { validateCredentials, extractFormParams } 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 }) {
let signatureFromRequest;
let timestamp;
let expiration;
let credential;

if (data['x-amz-algorithm'] !== 'AWS4-HMAC-SHA256') {
log.debug('algorithm param incorrect', { algo: data['X-Amz-Algorithm'] });
return { err: errors.InvalidArgument };
}

signatureFromRequest = data['x-amz-signature'];
if (!signatureFromRequest) {
log.debug('missing signature');
return { err: errors.InvalidArgument };
}

timestamp = data['x-amz-date'];
if (!timestamp || timestamp.length !== 16) {
log.debug('missing or invalid timestamp', { timestamp: data['x-amz-date'] });
return { err: errors.InvalidArgument };
}

const policy = data['policy'];
if (policy && policy.length > 0) {
const decryptedPolicy = Buffer.from(policy, 'base64').toString('utf8');
const policyObj = JSON.parse(decryptedPolicy);
expiration = policyObj.expiration;
} else {
log.debug('missing or invalid policy', { policy: data['policy'] });
return { err: errors.InvalidArgument };
}

credential = data['x-amz-credential'];
if (credential && credential.length > 28 && credential.indexOf('/') > -1) {
// @ts-ignore
credential = credential.split('/');
const validationResult = validateCredentials(credential, timestamp,
log);
if (validationResult instanceof Error) {
log.debug('credentials in improper format', { credential,
timestamp, validationResult });
return { err: validationResult };
}
} else {
log.debug('invalid credential param', { credential: data['X-Amz-Credential'] });
return { err: errors.InvalidArgument };
}

const token = data['x-amz-security-token'];
if (token && !constants.iamSecurityToken.pattern.test(token)) {
log.debug('invalid security token', { token });
return { err: errors.InvalidToken };
}

// check if the expiration date is past the current time
if (Date.parse(expiration) < Date.now()) {
return { err: errors.AccessDenied.customizeDescription('Invalid according to Policy: Policy expired.') };
}

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];

// string to sign is the policy for form requests
const stringToSign = data['policy'];

log.trace('constructed stringToSign', { stringToSign });
return {
err: null,
params: {
version: 4,
data: {
accessKey,
signatureFromRequest,
region,
scopeDate,
stringToSign,
service,
authType: 'REST-FORM-DATA',
signatureVersion: 'AWS4-HMAC-SHA256',
signatureAge: Date.now() - convertAmzTimeToMs(timestamp),
timestamp,
securityToken: token,
},
},
};
}
69 changes: 69 additions & 0 deletions lib/auth/v4/validateInputs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Logger } from 'werelogs';
import errors from '../../../lib/errors';
import { auth } from '../../..';
import { String } from 'aws-sdk/clients/cloudwatchevents';

/**
* Validate Credentials
Expand Down Expand Up @@ -130,6 +132,73 @@ export function extractQueryParams(
return authParams;
}

/**
* 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;
expiration?: String;
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;
}

const policy = formObj['policy'];
if (policy && policy.length > 0) {
const decryptedPolicy = Buffer.from(policy, 'base64').toString('utf8');
const policyObj = JSON.parse(decryptedPolicy);
const expiration = policyObj.expiration;
authParams.expiration = expiration;
} else {
log.warn('missing or invalid policy', { policy: formObj['policy'] });
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: formObj['X-Amz-Credential'] });
return authParams;
}

return authParams;
}


/**
* Extract and validate components from auth header
Expand Down
4 changes: 2 additions & 2 deletions lib/errors/arsenalErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,10 @@ export const MaxMessageLengthExceeded: ErrorFormat = {
description: 'Your request was too big.',
};

export const MaxPostPreDataLengthExceededError: ErrorFormat = {
export const MaxPostPreDataLengthExceeded: ErrorFormat = {
code: 400,
description:
'Your POST request fields preceding the upload file were too large.',
'Your POST request fields preceeding the upload file was too large.',
};

export const MetadataTooLarge: ErrorFormat = {
Expand Down
4 changes: 4 additions & 0 deletions lib/s3routes/routes/routePOST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ export default function routePOST(
corsHeaders));
}

if (objectKey === undefined && Object.keys(query).length === 0) {
return api.callApiMethod('objectPost', request, response, log, (err, resHeaders) => routesUtils.responseNoBody(err, resHeaders, response, 204, log));
}

return routesUtils.responseNoBody(errors.NotImplemented, null, response,
200, log);
}
Loading
Loading