Compare commits
No commits in common. "02c9b14df51b1ab626fccdf43019a26b9f37ae11" and "1016c270856ffae3071cba0237cee079888d3085" have entirely different histories.
02c9b14df5
...
1016c27085
|
@ -25,7 +25,6 @@ const checkFunctions = {
|
||||||
v4: {
|
v4: {
|
||||||
headers: v4.header.check,
|
headers: v4.header.check,
|
||||||
query: v4.query.check,
|
query: v4.query.check,
|
||||||
form: v4.form.check,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,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) {
|
||||||
|
@ -86,9 +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) {
|
|
||||||
method = 'form';
|
|
||||||
version = 'v4';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here, either both values are set, or none is set
|
// Here, either both values are set, or none is set
|
||||||
|
@ -125,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,108 +0,0 @@
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { Logger } from 'werelogs';
|
import { Logger } from 'werelogs';
|
||||||
import errors from '../../../lib/errors';
|
import errors from '../../../lib/errors';
|
||||||
import { auth } from '../../..';
|
|
||||||
import { String } from 'aws-sdk/clients/cloudwatchevents';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate Credentials
|
* Validate Credentials
|
||||||
|
@ -132,73 +130,6 @@ export function extractQueryParams(
|
||||||
return authParams;
|
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
|
* Extract and validate components from auth header
|
||||||
|
|
|
@ -1,172 +0,0 @@
|
||||||
'use strict'; // eslint-disable-line strict
|
|
||||||
|
|
||||||
const assert = require('assert');
|
|
||||||
const fakeTimers = require('@sinonjs/fake-timers');
|
|
||||||
|
|
||||||
const errors = require('../../../../lib/errors').default;
|
|
||||||
|
|
||||||
const createAlteredRequest = require('../../helpers').createAlteredRequest;
|
|
||||||
const formAuthCheck = require('../../../../lib/auth/v4/formAuthCheck').check;
|
|
||||||
const DummyRequestLogger = require('../../helpers').DummyRequestLogger;
|
|
||||||
|
|
||||||
const log = new DummyRequestLogger();
|
|
||||||
|
|
||||||
const method = 'POST';
|
|
||||||
const path = decodeURIComponent('/mybucket');
|
|
||||||
const host = 'localhost:8000';
|
|
||||||
|
|
||||||
const formatDate = now => now.toISOString().replace(/[:-]|\.\d{3}/g, '');
|
|
||||||
|
|
||||||
const requestDate = new Date(Date.now());
|
|
||||||
|
|
||||||
function prepPolicy(data, expiration = new Date(requestDate.getTime() + 15 * 60 * 1000)) {
|
|
||||||
try {
|
|
||||||
// 15 minutes
|
|
||||||
const policy = { expiration: expiration.toISOString() };
|
|
||||||
policy.conditions = Object.keys(data).map(key => ({ key: data[key] }));
|
|
||||||
// return base64 version of policy
|
|
||||||
return policy;
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Policy is not a valid JSON', e);
|
|
||||||
throw new Error('Policy is not a valid JSON');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = {
|
|
||||||
'x-amz-algorithm': 'AWS4-HMAC-SHA256',
|
|
||||||
'x-amz-credential': `accessKey1/${formatDate(requestDate).split('T')[0]}/us-east-1/s3/aws4_request`,
|
|
||||||
'x-amz-date': formatDate(requestDate),
|
|
||||||
'x-amz-signature': '036c5d854aca98a003c1c155a' +
|
|
||||||
'7723157d8148ad5888b3aee1133784eb5aec08b',
|
|
||||||
};
|
|
||||||
formData.policy = `${btoa(JSON.stringify(prepPolicy(formData)))}`;
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
host,
|
|
||||||
};
|
|
||||||
const request = {
|
|
||||||
method,
|
|
||||||
path,
|
|
||||||
headers,
|
|
||||||
formData,
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('v4 formAuthCheck', () => {
|
|
||||||
it('should return error if algorithm param incorrect', done => {
|
|
||||||
const alteredRequest = createAlteredRequest({
|
|
||||||
'x-amz-algorithm':
|
|
||||||
'AWS4-HMAC-SHA1',
|
|
||||||
}, 'formData', request, formData);
|
|
||||||
const res = formAuthCheck(alteredRequest, log, alteredRequest.formData);
|
|
||||||
assert.deepStrictEqual(res.err, errors.InvalidArgument);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return error if x-amz-credential param is undefined', done => {
|
|
||||||
const alteredRequest = createAlteredRequest({
|
|
||||||
'x-amz-credential':
|
|
||||||
undefined,
|
|
||||||
}, 'formData', request, formData);
|
|
||||||
const res = formAuthCheck(alteredRequest, log, alteredRequest.formData);
|
|
||||||
assert.deepStrictEqual(res.err, errors.InvalidArgument);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return error if credential param format incorrect', done => {
|
|
||||||
const alteredRequest = createAlteredRequest({
|
|
||||||
'x-amz-credential':
|
|
||||||
'incorrectformat',
|
|
||||||
}, 'formData', request, formData);
|
|
||||||
const res = formAuthCheck(alteredRequest, log, alteredRequest.formData);
|
|
||||||
assert.deepStrictEqual(res.err, errors.InvalidArgument);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return error if service set forth in ' +
|
|
||||||
'credential param is not s3', done => {
|
|
||||||
const alteredRequest = createAlteredRequest({
|
|
||||||
'x-amz-credential':
|
|
||||||
`accessKey1/${formatDate(requestDate).split('T')[0]}/us-east-1/EC2/aws4_request`
|
|
||||||
},
|
|
||||||
'formData', request, formData);
|
|
||||||
const res = formAuthCheck(alteredRequest, log, alteredRequest.formData);
|
|
||||||
assert.deepStrictEqual(res.err, errors.InvalidArgument);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return error if requestType set forth in ' +
|
|
||||||
'credential param is not aws4_request', done => {
|
|
||||||
const alteredRequest = createAlteredRequest({
|
|
||||||
'x-amz-credential':
|
|
||||||
`accessKey1/${formatDate(requestDate).split('T')[0]}/us-east-1/s3/aws2_request`
|
|
||||||
},
|
|
||||||
'formData', request, formData);
|
|
||||||
const res = formAuthCheck(alteredRequest, log, alteredRequest.formData);
|
|
||||||
assert.deepStrictEqual(res.err, errors.InvalidArgument);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return error if undefined x-amz-signature param', done => {
|
|
||||||
const alteredRequest = createAlteredRequest({
|
|
||||||
'x-amz-signature':
|
|
||||||
undefined
|
|
||||||
}, 'formData', request, formData);
|
|
||||||
const res = formAuthCheck(alteredRequest, log, alteredRequest.formData);
|
|
||||||
assert.deepStrictEqual(res.err, errors.InvalidArgument);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return error if undefined x-amz-date param', done => {
|
|
||||||
const alteredRequest = createAlteredRequest({
|
|
||||||
'x-amz-date':
|
|
||||||
undefined
|
|
||||||
}, 'formData', request, formData);
|
|
||||||
const res = formAuthCheck(alteredRequest, log, alteredRequest.formData);
|
|
||||||
assert.deepStrictEqual(res.err, errors.InvalidArgument);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return error if expiration param is too old', done => {
|
|
||||||
const expiredDate = new Date(Date.now() - 30 * 60 * 1000);
|
|
||||||
|
|
||||||
// Update the expiration date in formData
|
|
||||||
const alteredFormData = {
|
|
||||||
...formData,
|
|
||||||
policy: `${btoa(JSON.stringify(prepPolicy(formData, expiredDate)))}`
|
|
||||||
};
|
|
||||||
|
|
||||||
// Assuming alteredRequest is the request object that includes formData
|
|
||||||
const alteredRequest = {
|
|
||||||
...request,
|
|
||||||
formData: alteredFormData
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = formAuthCheck(alteredRequest, log, alteredRequest.formData);
|
|
||||||
assert.deepStrictEqual(res.err, errors.AccessDenied);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return error if scope date from x-amz-credential param' +
|
|
||||||
'does not match date from x-amz-date param', done => {
|
|
||||||
const clock = fakeTimers.install({ now: 1454974984001 });
|
|
||||||
const alteredRequest = createAlteredRequest({
|
|
||||||
'x-amz-credential': 'accessKey1/20160209/' +
|
|
||||||
'us-east-1/s3/aws4_request',
|
|
||||||
}, 'formData', request, formData);
|
|
||||||
const res = formAuthCheck(alteredRequest, log, alteredRequest.formData);
|
|
||||||
clock.uninstall();
|
|
||||||
assert.deepStrictEqual(res.err, errors.RequestTimeTooSkewed);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should successfully return v4 and no error', done => {
|
|
||||||
// Freezes time so date created within function will be Feb 8, 2016
|
|
||||||
// (within 15 minutes of timestamp in request)
|
|
||||||
const clock = fakeTimers.install({ now: 1454974984001 });
|
|
||||||
const res = formAuthCheck(request, log, request.formData);
|
|
||||||
clock.uninstall();
|
|
||||||
assert.deepStrictEqual(res.err, null);
|
|
||||||
assert.strictEqual(res.params.version, 4);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in New Issue