Compare commits

...

13 Commits

Author SHA1 Message Date
Jonathan Gramain 23f7e5af6a ARSN-296 bump version to 7.10.29-4 2023-01-23 13:12:14 +01:00
Jonathan Gramain cb31a07d8d ARSN-296 Revert "ARSN-252 - listing bug in DelimisterMaster"
This reverts commit 5f0df14ca8.
2023-01-23 13:12:14 +01:00
Jonathan Gramain 62be7f926d ARSN-296 Revert "ARSN-269 - listing bug in versioned bucket edge cases."
This reverts commit 88f16366a1.
2023-01-23 12:57:46 +01:00
Alexander Chan 2d205f8131 ARSN-283: bump version 7.10.29-3 2022-11-29 19:13:53 -08:00
Jonathan Gramain 0aa95823b7 bugfix: ARSN-262 fixes/tests in RequestContext
- remove "postXml" field, as it was a left-over from prototyping

- handle fields related to tag conditions: requestObjTags,
  existingObjTag, needTagEval, those were missing from constructor
  params

- fix a typo in serialization: requersterInfo -> requesterInfo

- new unit tests for RequestContext
  constructor/serialize/deserialize/getters

(cherry picked from commit 4f2b1ca960)
2022-11-29 19:06:54 -08:00
Jonathan Gramain 5f1e91b44c bugfix: ARSN-255 revamp evaluatePolicy logic for tag conditions
Rethink the logic of tag condition evaluation, so that the
"evaluateAllPolicies" function appropriately returns the verdict:
Allow or Deny or NeedTagConditionEval, the latter being when tag
values (request and/or object tags) are needed to settle the verdict
to Allow or Deny, in which case, Cloudserver knows it has to resend
the request to Vault along with tag info.

(cherry picked from commit 5cd1df8601)
2022-11-29 18:50:00 -08:00
Jonathan Gramain c5c736aa48 ARSN-255 [cleanup] better exports in evaluator.ts
Turn 'const' function objects into actual functions.

(cherry picked from commit ee38856f29)
2022-11-29 18:49:31 -08:00
Jonathan Gramain bca37e4b06 improvement: ARSN-260 improve efficiency of findConditionKey
Instead of pre-creating a Map with all supported condition keys before
returning the wanted one, use a switch/case construct to directly
return the attribute from the request context.

(cherry picked from commit dc229bb8aa)
2022-11-29 18:49:22 -08:00
Nicolas Humbert 5fd84e78e2 bump package version 2022-11-23 15:21:50 -05:00
williamlardier dd603482f0 ARSN-270: change bad permission names
(cherry picked from commit 7763685cb0)
2022-11-23 15:21:25 -05:00
Artem Bakalov c16d4e6954 Bump version to 7.10.29-1 2022-10-11 16:10:00 -07:00
Artem Bakalov 88f16366a1 ARSN-269 - listing bug in versioned bucket edge cases.
Simplifies testing that was used in ARSN-262. Adds a function allowDelimiterRangeSkip
to determine when a nextContinueMarker range can be skipped when .skipping is called.
This function uses a new state variable prefixKeySeen and the nextContinueMarker to determine
if a range of the form prefix/ can be skipped. An additional check is added when processing
delete markers of the form prefix/foo/(bar) so that the prefix/foo/ range can still be skipped
as an optimization.

(cherry picked from commit 87b060f2ae)
2022-10-11 16:09:03 -07:00
Artem Bakalov 5f0df14ca8 ARSN-252 - listing bug in DelimisterMaster
DelimiterMaster.filter is used to determine when a key range can be skipped in Metadata:RepdServer to optimize listing performance.
When a bucket is created with vFormat=v0, and subsequently a listing is done with a prefix, DelimiterMaster.filter was incorrectly
determining that a range could be skipped if a key was listed such that key == prefix. This case is now correctly handled in filterV0.

(cherry picked from commit f62c3d22ed)
2022-10-11 13:48:54 -07:00
9 changed files with 679 additions and 223 deletions

View File

@ -166,7 +166,6 @@ export default class RequestContext {
_policyArn: string;
_action?: string;
_needQuota: boolean;
_postXml?: string;
_requestObjTags: string | null;
_existingObjTag: string | null;
_needTagEval: boolean;
@ -190,7 +189,9 @@ export default class RequestContext {
securityToken: string,
policyArn: string,
action?: string,
postXml?: string,
requestObjTags?: string,
existingObjTag?: string,
needTagEval?: false,
) {
this._headers = headers;
this._query = query;
@ -220,10 +221,9 @@ export default class RequestContext {
this._policyArn = policyArn;
this._action = action;
this._needQuota = _actionNeedQuotaCheck[apiMethod] === true;
this._postXml = postXml;
this._requestObjTags = null;
this._existingObjTag = null;
this._needTagEval = false;
this._requestObjTags = requestObjTags || null;
this._existingObjTag = existingObjTag || null;
this._needTagEval = needTagEval || false;
return this;
}
@ -236,7 +236,7 @@ export default class RequestContext {
apiMethod: this._apiMethod,
headers: this._headers,
query: this._query,
requersterInfo: this._requesterInfo,
requesterInfo: this._requesterInfo,
requesterIp: this._requesterIp,
sslEnabled: this._sslEnabled,
awsService: this._awsService,
@ -252,7 +252,6 @@ export default class RequestContext {
securityToken: this._securityToken,
policyArn: this._policyArn,
action: this._action,
postXml: this._postXml,
requestObjTags: this._requestObjTags,
existingObjTag: this._existingObjTag,
needTagEval: this._needTagEval,
@ -276,12 +275,27 @@ export default class RequestContext {
if (resource) {
obj.specificResource = resource;
}
return new RequestContext(obj.headers, obj.query, obj.generalResource,
obj.specificResource, obj.requesterIp, obj.sslEnabled,
obj.apiMethod, obj.awsService, obj.locationConstraint,
obj.requesterInfo, obj.signatureVersion,
obj.authType, obj.signatureAge, obj.securityToken, obj.policyArn,
obj.action, obj.postXml);
return new RequestContext(
obj.headers,
obj.query,
obj.generalResource,
obj.specificResource,
obj.requesterIp,
obj.sslEnabled,
obj.apiMethod,
obj.awsService,
obj.locationConstraint,
obj.requesterInfo,
obj.signatureVersion,
obj.authType,
obj.signatureAge,
obj.securityToken,
obj.policyArn,
obj.action,
obj.requestObjTags,
obj.existingObjTag,
obj.needTagEval,
);
}
/**
@ -625,26 +639,6 @@ export default class RequestContext {
return this._needQuota;
}
/**
* Set request post
*
* @param postXml - request post
* @return itself
*/
setPostXml(postXml: string) {
this._postXml = postXml;
return this;
}
/**
* Get request post
*
* @return request post
*/
getPostXml() {
return this._postXml;
}
/**
* Set request object tags
*

View File

@ -13,7 +13,11 @@ const operatorsWithVariables = ['StringEquals', 'StringNotEquals',
const operatorsWithNegation = ['StringNotEquals',
'StringNotEqualsIgnoreCase', 'StringNotLike', 'ArnNotEquals',
'ArnNotLike', 'NumericNotEquals'];
const tagConditions = new Set(['s3:ExistingObjectTag', 's3:RequestObjectTagKey', 's3:RequestObjectTagKeys']);
const tagConditions = new Set([
's3:ExistingObjectTag',
's3:RequestObjectTagKey',
's3:RequestObjectTagKeys',
]);
/**
@ -24,11 +28,11 @@ const tagConditions = new Set(['s3:ExistingObjectTag', 's3:RequestObjectTagKey',
* @param log - logger
* @return true if applicable, false if not
*/
export const isResourceApplicable = (
export function isResourceApplicable(
requestContext: RequestContext,
statementResource: string | string[],
log: Logger,
): boolean => {
): boolean {
const resource = requestContext.getResource();
if (!Array.isArray(statementResource)) {
// eslint-disable-next-line no-param-reassign
@ -59,7 +63,7 @@ export const isResourceApplicable = (
{ requestResource: resource });
// If no match found, no resource is applicable
return false;
};
}
/**
* Check whether action in policy statement applies to request
@ -69,11 +73,11 @@ export const isResourceApplicable = (
* @param log - logger
* @return true if applicable, false if not
*/
export const isActionApplicable = (
export function isActionApplicable(
requestAction: string,
statementAction: string | string[],
log: Logger,
): boolean => {
): boolean {
if (!Array.isArray(statementAction)) {
// eslint-disable-next-line no-param-reassign
statementAction = [statementAction];
@ -95,32 +99,33 @@ export const isActionApplicable = (
{ requestAction });
// If no match found, return false
return false;
};
}
/**
* Check whether request meets policy conditions
* @param requestContext - info about request
* @param statementCondition - Condition statement from policy
* @param log - logger
* @return contains whether conditions are allowed and whether they
* contain any tag condition keys
* @param {RequestContext} requestContext - info about request
* @param {object} statementCondition - Condition statement from policy
* @param {Logger} log - logger
* @return {boolean|null} a condition evaluation result, one of:
* - true: condition is met
* - false: condition is not met
* - null: condition evaluation requires additional info to be
* provided (namely, for tag conditions, request tags and/or object
* tags have to be provided to evaluate the condition)
*/
export const meetConditions = (
export function meetConditions(
requestContext: RequestContext,
statementCondition: any,
log: Logger,
) => {
): boolean | null {
let hasTagConditions = false;
// The Condition portion of a policy is an object with different
// operators as keys
const conditionEval = {};
const operators = Object.keys(statementCondition);
const length = operators.length;
for (let i = 0; i < length; i++) {
const operator = operators[i];
for (const operator of Object.keys(statementCondition)) {
const hasPrefix = operator.includes(':');
const hasIfExistsCondition = operator.endsWith('IfExists');
// If has "IfExists" added to operator name, or operator has "ForAnyValue" or
// "For All Values" prefix, find operator name without "IfExists" or prefix
// "ForAllValues" prefix, find operator name without "IfExists" or prefix
let bareOperator = hasIfExistsCondition ? operator.slice(0, -8) :
operator;
let prefix: string | undefined;
@ -135,10 +140,6 @@ export const meetConditions = (
// Note: this should be the actual operator name, not the bareOperator
const conditionsWithSameOperator = statementCondition[operator];
const conditionKeys = Object.keys(conditionsWithSameOperator);
if (conditionKeys.some(key => tagConditions.has(key)) && !requestContext.getNeedTagEval()) {
// @ts-expect-error
conditionEval.tagConditions = true;
}
const conditionKeysLength = conditionKeys.length;
for (let j = 0; j < conditionKeysLength; j++) {
const key = conditionKeys[j];
@ -155,6 +156,10 @@ export const meetConditions = (
// tag key is included in condition key and needs to be
// moved to value for evaluation, otherwise key/value are unchanged
const [transformedKey, transformedValue] = transformTagKeyValue(key, value);
if (tagConditions.has(transformedKey) && !requestContext.getNeedTagEval()) {
hasTagConditions = true;
continue;
}
// Pull key using requestContext
// TODO: If applicable to S3, handle policy set operations
// where a keyBasedOnRequestContext returns multiple values and
@ -180,11 +185,10 @@ export const meetConditions = (
log.trace('condition not satisfied due to ' +
'missing info', { operator,
conditionKey: transformedKey, policyValue: transformedValue });
return { allow: false };
return false;
}
// If condition operator prefix is included, the key should be an array
if (prefix && !Array.isArray(keyBasedOnRequestContext)) {
// @ts-expect-error
keyBasedOnRequestContext = [keyBasedOnRequestContext];
}
// Transalate operator into function using bareOperator
@ -196,14 +200,16 @@ export const meetConditions = (
if (!operatorFunction(keyBasedOnRequestContext, transformedValue, prefix)) {
log.trace('did not satisfy condition', { operator: bareOperator,
keyBasedOnRequestContext, policyValue: transformedValue });
return { allow: false };
return false;
}
}
}
// @ts-expect-error
conditionEval.allow = true;
return conditionEval;
};
// one or more conditions required tag info to be evaluated
if (hasTagConditions) {
return null;
}
return true;
}
/**
* Evaluate whether a request is permitted under a policy.
@ -216,13 +222,15 @@ export const meetConditions = (
* @return Allow if permitted, Deny if not permitted or Neutral
* if not applicable
*/
export const evaluatePolicy = (
export function evaluatePolicy(
requestContext: RequestContext,
policy: any,
log: Logger,
): string => {
): string {
// TODO: For bucket policies need to add Principal evaluation
let verdict = 'Neutral';
let allow = false;
let allowWithTagCondition = false;
let denyWithTagCondition = false;
if (!Array.isArray(policy.Statement)) {
// eslint-disable-next-line no-param-reassign
@ -259,10 +267,18 @@ export const evaluatePolicy = (
}
const conditionEval = currentStatement.Condition ?
meetConditions(requestContext, currentStatement.Condition, log) :
null;
true;
// If do not meet conditions move on to next statement
// @ts-expect-error
if (conditionEval && !conditionEval.allow) {
if (conditionEval === false) {
continue;
}
// If condition needs tag info to be evaluated, mark and move on to next statement
if (conditionEval === null) {
if (currentStatement.Effect === 'Deny') {
denyWithTagCondition = true;
} else {
allowWithTagCondition = true;
}
continue;
}
if (currentStatement.Effect === 'Deny') {
@ -271,17 +287,27 @@ export const evaluatePolicy = (
return 'Deny';
}
log.trace('Allow statement applies');
// If statement is applicable, conditions are met and Effect is
// to Allow, set verdict to Allow
// statement is applicable, conditions are met and Effect is
// to Allow
allow = true;
}
let verdict;
if (denyWithTagCondition) {
// priority is on checking tags to potentially deny
verdict = 'DenyWithTagCondition';
} else if (allow) {
// at least one statement is an allow
verdict = 'Allow';
// @ts-expect-error
if (conditionEval && conditionEval.tagConditions) {
verdict = 'NeedTagConditionEval';
}
} else if (allowWithTagCondition) {
// all allow statements need tag checks
verdict = 'AllowWithTagCondition';
} else {
// no statement matched to allow or deny
verdict = 'Neutral';
}
log.trace('result of evaluating single policy', { verdict });
return verdict;
};
}
/**
* Evaluate whether a request is permitted under a policy.
@ -294,24 +320,43 @@ export const evaluatePolicy = (
* @return Allow if permitted, Deny if not permitted.
* Default is to Deny. Deny overrides an Allow
*/
export const evaluateAllPolicies = (
export function evaluateAllPolicies(
requestContext: RequestContext,
allPolicies: any[],
log: Logger,
): string => {
): string {
log.trace('evaluating all policies');
let verdict = 'Deny';
let allow = false;
let allowWithTagCondition = false;
let denyWithTagCondition = false;
for (let i = 0; i < allPolicies.length; i++) {
const singlePolicyVerdict =
evaluatePolicy(requestContext, allPolicies[i], log);
const singlePolicyVerdict = evaluatePolicy(requestContext, allPolicies[i], log);
// If there is any Deny, just return Deny
if (singlePolicyVerdict === 'Deny') {
return 'Deny';
}
if (singlePolicyVerdict === 'Allow') {
allow = true;
} else if (singlePolicyVerdict === 'AllowWithTagCondition') {
allowWithTagCondition = true;
} else if (singlePolicyVerdict === 'DenyWithTagCondition') {
denyWithTagCondition = true;
} // else 'Neutral'
}
let verdict;
if (allow) {
if (denyWithTagCondition) {
verdict = 'NeedTagConditionEval';
} else {
verdict = 'Allow';
}
} else {
if (allowWithTagCondition) {
verdict = 'NeedTagConditionEval';
} else {
verdict = 'Deny';
}
}
log.trace('result of evaluating all pollicies', { verdict });
log.trace('result of evaluating all policies', { verdict });
return verdict;
};
}

View File

@ -23,15 +23,22 @@ export default class Principal {
* @param statement - Statement policy field
* @return True if meet conditions
*/
static _evaluateCondition(
static _evaluateStatement(
params: Params,
statement: Statement,
// TODO Fix return type
): any {
if (statement.Condition) {
return meetConditions(params.rc, statement.Condition, params.log);
): 'Neutral' | 'Allow' | 'Deny' {
const reverse = !!statement.NotPrincipal;
if (reverse) {
// In case of anonymous NotPrincipal, this will neutral everyone
return 'Neutral';
}
return true;
if (statement.Condition) {
const conditionEval = meetConditions(params.rc, statement.Condition, params.log);
if (conditionEval === false || conditionEval === null) {
return 'Neutral';
}
}
return statement.Effect;
}
/**
@ -48,19 +55,12 @@ export default class Principal {
statement: Statement,
valids: Valid,
): 'Neutral' | 'Allow' | 'Deny' {
const reverse = !!statement.NotPrincipal;
const principal = (statement.Principal || statement.NotPrincipal)!;
if (typeof principal === 'string' && principal === '*') {
if (reverse) {
// In case of anonymous NotPrincipal, this will neutral everyone
return 'Neutral';
const reverse = !!statement.NotPrincipal;
if (typeof principal === 'string') {
if (principal === '*') {
return Principal._evaluateStatement(params, statement);
}
const conditionEval = Principal._evaluateCondition(params, statement);
if (!conditionEval || conditionEval.allow === false) {
return 'Neutral';
}
return statement.Effect;
} else if (typeof principal === 'string') {
return 'Deny';
}
let ref = [];
@ -82,28 +82,8 @@ export default class Principal {
}
toCheck = Array.isArray(toCheck) ? toCheck : [toCheck];
ref = Array.isArray(ref) ? ref : [ref];
if (toCheck.indexOf('*') !== -1) {
if (reverse) {
return 'Neutral';
}
const conditionEval = Principal._evaluateCondition(params, statement);
if (!conditionEval || conditionEval.allow === false) {
return 'Neutral';
}
return statement.Effect;
}
const len = ref.length;
for (let i = 0; i < len; ++i) {
if (toCheck.indexOf(ref[i]) !== -1) {
if (reverse) {
return 'Neutral';
}
const conditionEval = Principal._evaluateCondition(params, statement);
if (!conditionEval || conditionEval.allow === false) {
return 'Neutral';
}
return statement.Effect;
}
if (toCheck.includes('*') || ref.some(r => toCheck.includes(r))) {
return Principal._evaluateStatement(params, statement);
}
if (reverse) {
return statement.Effect;

View File

@ -4,14 +4,14 @@ const sharedActionMap = {
bucketDeleteEncryption: 's3:PutEncryptionConfiguration',
bucketDeletePolicy: 's3:DeleteBucketPolicy',
bucketDeleteWebsite: 's3:DeleteBucketWebsite',
bucketDeleteTagging: 's3:DeleteBucketTagging',
bucketDeleteTagging: 's3:PutBucketTagging',
bucketGet: 's3:ListBucket',
bucketGetACL: 's3:GetBucketAcl',
bucketGetCors: 's3:GetBucketCORS',
bucketGetEncryption: 's3:GetEncryptionConfiguration',
bucketGetLifecycle: 's3:GetLifecycleConfiguration',
bucketGetLocation: 's3:GetBucketLocation',
bucketGetNotification: 's3:GetBucketNotificationConfiguration',
bucketGetNotification: 's3:GetBucketNotification',
bucketGetObjectLock: 's3:GetBucketObjectLockConfiguration',
bucketGetPolicy: 's3:GetBucketPolicy',
bucketGetReplication: 's3:GetReplicationConfiguration',
@ -23,7 +23,7 @@ const sharedActionMap = {
bucketPutCors: 's3:PutBucketCORS',
bucketPutEncryption: 's3:PutEncryptionConfiguration',
bucketPutLifecycle: 's3:PutLifecycleConfiguration',
bucketPutNotification: 's3:PutBucketNotificationConfiguration',
bucketPutNotification: 's3:PutBucketNotification',
bucketPutObjectLock: 's3:PutBucketObjectLockConfiguration',
bucketPutPolicy: 's3:PutBucketPolicy',
bucketPutReplication: 's3:PutReplicationConfiguration',
@ -55,8 +55,8 @@ const actionMapRQ = {
// see http://docs.aws.amazon.com/AmazonS3/latest/API/
// RESTBucketDELETEcors.html
bucketDeleteCors: 's3:PutBucketCORS',
bucketDeleteReplication: 's3:DeleteReplicationConfiguration',
bucketDeleteLifecycle: 's3:DeleteLifecycleConfiguration',
bucketDeleteReplication: 's3:PutReplicationConfiguration',
bucketDeleteLifecycle: 's3:PutLifecycleConfiguration',
completeMultipartUpload: 's3:PutObject',
initiateMultipartUpload: 's3:PutObject',
objectDeleteVersion: 's3:DeleteObjectVersion',
@ -72,6 +72,7 @@ const actionMapRQ = {
objectReplicate: 's3:ReplicateObject',
objectPutRetentionVersion: 's3:PutObjectVersionRetention',
objectPutLegalHoldVersion: 's3:PutObjectVersionLegalHold',
listObjectVersions: 's3:ListBucketVersions',
...sharedActionMap,
};
@ -104,7 +105,7 @@ const actionMonitoringMapS3 = {
bucketGetCors: 'GetBucketCors',
bucketGetLifecycle: 'GetBucketLifecycleConfiguration',
bucketGetLocation: 'GetBucketLocation',
bucketGetNotification: 'GetBucketNotificationConfiguration',
bucketGetNotification: 'GetBucketNotification',
bucketGetObjectLock: 'GetObjectLockConfiguration',
bucketGetPolicy: 'GetBucketPolicy',
bucketGetReplication: 'GetBucketReplication',
@ -117,7 +118,7 @@ const actionMonitoringMapS3 = {
bucketPutACL: 'PutBucketAcl',
bucketPutCors: 'PutBucketCors',
bucketPutLifecycle: 'PutBucketLifecycleConfiguration',
bucketPutNotification: 'PutBucketNotificationConfiguration',
bucketPutNotification: 'PutBucketNotification',
bucketPutObjectLock: 'PutObjectLockConfiguration',
bucketPutPolicy: 'PutBucketPolicy',
bucketPutReplication: 'PutBucketReplication',

View File

@ -11,31 +11,30 @@ import ipaddr from 'ipaddr.js';
* @param requestContext - info sent with request
* @return condition key value
*/
export const findConditionKey = (
export function findConditionKey(
key: string,
requestContext: RequestContext,
): string => {
): any {
// TODO: Consider combining with findVariable function if no benefit
// to keeping separate
const headers = requestContext.getHeaders();
const query = requestContext.getQuery();
const requesterInfo = requestContext.getRequesterInfo();
const map = new Map();
// Possible AWS Condition keys (http://docs.aws.amazon.com/IAM/latest/
// UserGuide/reference_policies_elements.html#AvailableKeys)
switch (key) {
// aws:CurrentTime Used for date/time conditions
// (see Date Condition Operators).
map.set('aws:CurrentTime', new Date().toISOString());
case 'aws:CurrentTime': return new Date().toISOString();
// aws:EpochTime Used for date/time conditions
// (see Date Condition Operators).
map.set('aws:EpochTime', Date.now().toString());
case 'aws:EpochTime': return Date.now().toString();
// aws:TokenIssueTime Date/time that temporary security
// credentials were issued (see Date Condition Operators).
// Only present in requests that are signed using temporary security
// credentials.
map.set('aws:TokenIssueTime', requestContext.getTokenIssueTime());
case 'aws:TokenIssueTime': return requestContext.getTokenIssueTime();
// aws:MultiFactorAuthPresent Used to check whether MFA was used
// (see Boolean Condition Operators).
// Note: This key is only present if MFA was used. So, the following
@ -45,131 +44,132 @@ export const findConditionKey = (
// Instead use:
// "Condition" :
// { "Null" : { "aws:MultiFactorAuthPresent" : true } }
map.set('aws:MultiFactorAuthPresent',
requestContext.getMultiFactorAuthPresent());
case 'aws:MultiFactorAuthPresent': return requestContext.getMultiFactorAuthPresent();
// aws:MultiFactorAuthAge Used to check how many seconds since
// MFA credentials were issued. If MFA was not used,
// this key is not present
map.set('aws:MultiFactorAuthAge', requestContext.getMultiFactorAuthAge());
case 'aws:MultiFactorAuthAge': return requestContext.getMultiFactorAuthAge();
// aws:principaltype states whether the principal is an account,
// user, federated, or assumed role
// Note: Docs for conditions have "PrincipalType" but simulator
// and docs for variables have lowercase
map.set('aws:principaltype', requesterInfo.principaltype);
case 'aws:principaltype': return requesterInfo.principaltype;
// aws:Referer Used to check who referred the client browser to
// the address the request is being sent to. Only supported by some
// services, such as S3. Value comes from the referer header in the
// HTTPS request made to AWS.
map.set('aws:referer', headers.referer);
case 'aws:referer': return headers.referer;
// aws:SecureTransport Used to check whether the request was sent
// using SSL (see Boolean Condition Operators).
map.set('aws:SecureTransport',
requestContext.getSslEnabled() ? 'true' : 'false');
case 'aws:SecureTransport': return requestContext.getSslEnabled() ? 'true' : 'false';
// aws:SourceArn Used check the source of the request,
// using the ARN of the source. N/A here.
map.set('aws:SourceArn', undefined);
case 'aws:SourceArn': return undefined;
// aws:SourceIp Used to check the requester's IP address
// (see IP Address Condition Operators)
map.set('aws:SourceIp', requestContext.getRequesterIp());
case 'aws:SourceIp': return requestContext.getRequesterIp();
// aws:SourceVpc Used to restrict access to a specific
// AWS Virtual Private Cloud. N/A here.
map.set('aws:SourceVpc', undefined);
case 'aws:SourceVpc': return undefined;
// aws:SourceVpce Used to limit access to a specific VPC endpoint
// N/A here
map.set('aws:SourceVpce', undefined);
case 'aws:SourceVpce': return undefined;
// aws:UserAgent Used to check the requester's client app.
// (see String Condition Operators)
map.set('aws:UserAgent', headers['user-agent']);
case 'aws:UserAgent': return headers['user-agent'];
// aws:userid Used to check the requester's unique user ID.
// (see String Condition Operators)
map.set('aws:userid', requesterInfo.userid);
case 'aws:userid': return requesterInfo.userid;
// aws:username Used to check the requester's friendly user name.
// (see String Condition Operators)
map.set('aws:username', requesterInfo.username);
case 'aws:username': return requesterInfo.username;
// Possible condition keys for S3:
// s3:x-amz-acl is acl request for bucket or object put request
map.set('s3:x-amz-acl', headers['x-amz-acl']);
case 's3:x-amz-acl': return headers['x-amz-acl'];
// s3:x-amz-grant-PERMISSION (where permission can be:
// read, write, read-acp, write-acp or full-control)
// Value is the value of that header (ex. id of grantee)
map.set('s3:x-amz-grant-read', headers['x-amz-grant-read']);
map.set('s3:x-amz-grant-write', headers['x-amz-grant-write']);
map.set('s3:x-amz-grant-read-acp', headers['x-amz-grant-read-acp']);
map.set('s3:x-amz-grant-write-acp', headers['x-amz-grant-write-acp']);
map.set('s3:x-amz-grant-full-control', headers['x-amz-grant-full-control']);
case 's3:x-amz-grant-read': return headers['x-amz-grant-read'];
case 's3:x-amz-grant-write': return headers['x-amz-grant-write'];
case 's3:x-amz-grant-read-acp': return headers['x-amz-grant-read-acp'];
case 's3:x-amz-grant-write-acp': return headers['x-amz-grant-write-acp'];
case 's3:x-amz-grant-full-control': return headers['x-amz-grant-full-control'];
// s3:x-amz-copy-source is x-amz-copy-source header if applicable on
// a put object
map.set('s3:x-amz-copy-source', headers['x-amz-copy-source']);
case 's3:x-amz-copy-source': return headers['x-amz-copy-source'];
// s3:x-amz-metadata-directive is x-amz-metadata-directive header if
// applicable on a put object copy. Determines whether metadata will
// be copied from original object or replaced. Values or "COPY" or
// "REPLACE". Default is "COPY"
map.set('s3:x-amz-metadata-directive', headers['metadata-directive']);
case 's3:x-amz-metadata-directive': return headers['metadata-directive'];
// s3:x-amz-server-side-encryption -- Used to require that object put
// use server side encryption. Value is the encryption algo such as
// "AES256"
map.set('s3:x-amz-server-side-encryption',
headers['x-amz-server-side-encryption']);
case 's3:x-amz-server-side-encryption': return headers['x-amz-server-side-encryption'];
// s3:x-amz-storage-class -- x-amz-storage-class header value
// (STANDARD, etc.)
map.set('s3:x-amz-storage-class', headers['x-amz-storage-class']);
case 's3:x-amz-storage-class': return headers['x-amz-storage-class'];
// s3:VersionId -- version id of object
map.set('s3:VersionId', query.versionId);
case 's3:VersionId': return query.versionId;
// s3:LocationConstraint -- Used to restrict creation of bucket
// in certain region. Only applicable for CreateBucket
map.set('s3:LocationConstraint', requestContext.getLocationConstraint());
case 's3:LocationConstraint': return requestContext.getLocationConstraint();
// s3:delimiter is delimiter for listing request
map.set('s3:delimiter', query.delimiter);
case 's3:delimiter': return query.delimiter;
// s3:max-keys is max-keys for listing request
map.set('s3:max-keys', query['max-keys']);
case 's3:max-keys': return query['max-keys'];
// s3:prefix is prefix for listing request
map.set('s3:prefix', query.prefix);
case 's3:prefix': return query.prefix;
// s3 auth v4 additional condition keys
// (See http://docs.aws.amazon.com/AmazonS3/latest/API/
// bucket-policy-s3-sigv4-conditions.html)
// s3:signatureversion -- Either "AWS" for v2 or
// "AWS4-HMAC-SHA256" for v4
map.set('s3:signatureversion', requestContext.getSignatureVersion());
case 's3:signatureversion': return requestContext.getSignatureVersion();
// s3:authType -- Method of authentication: either "REST-HEADER",
// "REST-QUERY-STRING" or "POST"
map.set('s3:authType', requestContext.getAuthType());
case 's3:authType': return requestContext.getAuthType();
// s3:signatureAge is the length of time, in milliseconds,
// that a signature is valid in an authenticated request. So,
// can use this to limit the age to less than 7 days
map.set('s3:signatureAge', requestContext.getSignatureAge());
case 's3:signatureAge': return requestContext.getSignatureAge();
// s3:x-amz-content-sha256 - Valid value is "UNSIGNED-PAYLOAD"
// so can use this in a deny policy to deny any requests that do not
// have a signed payload
map.set('s3:x-amz-content-sha256', headers['x-amz-content-sha256']);
case 's3:x-amz-content-sha256': return headers['x-amz-content-sha256'];
// s3:ObjLocationConstraint is the location constraint set for an
// object on a PUT request using the "x-amz-meta-scal-location-constraint"
// header
map.set('s3:ObjLocationConstraint',
headers['x-amz-meta-scal-location-constraint']);
map.set('sts:ExternalId', requestContext.getRequesterExternalId());
map.set('iam:PolicyArn', requestContext.getPolicyArn());
case 's3:ObjLocationConstraint': return headers['x-amz-meta-scal-location-constraint'];
case 'sts:ExternalId': return requestContext.getRequesterExternalId();
case 'iam:PolicyArn': return requestContext.getPolicyArn();
// s3:ExistingObjectTag - Used to check that existing object tag has
// specific tag key and value. Extraction of correct tag key is done in CloudServer.
// On first pass of policy evaluation, CloudServer information will not be included,
// so evaluation should be skipped
map.set('s3:ExistingObjectTag', requestContext.getNeedTagEval() ? requestContext.getExistingObjTag() : undefined);
case 's3:ExistingObjectTag':
return requestContext.getNeedTagEval()
? requestContext.getExistingObjTag() : undefined;
// s3:RequestObjectTag - Used to limit putting object tags to specific
// tag key and value. N/A here.
// Requires information from CloudServer
// On first pass of policy evaluation, CloudServer information will not be included,
// so evaluation should be skipped
map.set('s3:RequestObjectTagKey', requestContext.getNeedTagEval() ? requestContext.getRequestObjTags() : undefined);
case 's3:RequestObjectTagKey':
return requestContext.getNeedTagEval()
? requestContext.getRequestObjTags() : undefined;
// s3:RequestObjectTagKeys - Used to limit putting object tags specific tag keys.
// Requires information from CloudServer.
// On first pass of policy evaluation, CloudServer information will not be included,
// so evaluation should be skipped
map.set('s3:RequestObjectTagKeys',
requestContext.getNeedTagEval() && requestContext.getRequestObjTags()
? getTagKeys(requestContext.getRequestObjTags()!)
: undefined,
);
return map.get(key);
};
case 's3:RequestObjectTagKeys':
return requestContext.getNeedTagEval() && requestContext.getRequestObjTags()
? getTagKeys(requestContext.getRequestObjTags()!)
: undefined;
default:
return undefined;
}
}
// Wildcards are allowed in certain string comparison and arn comparisons
@ -229,7 +229,7 @@ function convertToEpochTime(time: string | string[]) {
* reference_policies_elements.html)
* @return true if condition passes and false if not
*/
export const convertConditionOperator = (operator: string): boolean => {
export function convertConditionOperator(operator: string): boolean {
// Policy Validator checks that the condition operator
// is only one of these strings so should not have undefined
// or security issue with object assignment
@ -444,4 +444,4 @@ export const convertConditionOperator = (operator: string): boolean => {
},
};
return operatorMap[operator];
};
}

View File

@ -3,7 +3,7 @@
"engines": {
"node": ">=16"
},
"version": "7.10.29",
"version": "7.10.29-4",
"description": "Common utilities for the S3 project components",
"main": "build/index.js",
"repository": {

View File

@ -166,7 +166,7 @@ describe('network.Server: ', () => {
if (err) {
return ws.onStop(() => {
clearTimeout(requestTimeout);
if (err.code === 'EPROTO') {
if (err.code === 'EPROTO' || err.code === 'ECONNRESET') {
return done();
}
return done(err);

View File

@ -1162,30 +1162,6 @@ describe('policyEvaluator', () => {
check(requestContext, rcModifiers, policy, 'Neutral');
});
it('should allow with StringEquals operator and ExistingObjectTag ' +
'key if meet condition', () => {
policy.Statement.Condition = {
StringEquals: { 's3:ExistingObjectTag/tagKey': 'tagValue' },
};
const rcModifiers = {
_existingObjTag: 'tagKey=tagValue',
_needTagEval: true,
};
check(requestContext, rcModifiers, policy, 'Allow');
});
it('should allow StringEquals operator and RequestObjectTag ' +
'key if meet condition', () => {
policy.Statement.Condition = {
StringEquals: { 's3:RequestObjectTagKey/tagKey': 'tagValue' },
};
const rcModifiers = {
_requestObjTags: 'tagKey=tagValue',
_needTagEval: true,
};
check(requestContext, rcModifiers, policy, 'Allow');
});
it('should allow with ForAnyValue prefix if meet condition', () => {
policy.Statement.Condition = {
'ForAnyValue:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
@ -1208,7 +1184,7 @@ describe('policyEvaluator', () => {
check(requestContext, rcModifiers, policy, 'Allow');
});
it('should not allow with ForAnyValue prefix if do not meet condition', () => {
it('should be neutral with ForAnyValue prefix if do not meet condition', () => {
policy.Statement.Condition = {
'ForAnyValue:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
};
@ -1219,12 +1195,12 @@ describe('policyEvaluator', () => {
check(requestContext, rcModifiers, policy, 'Neutral');
});
it('should not allow with ForAllValues prefix if do not meet condition', () => {
it('should be neutral with ForAllValues prefix if do not meet condition', () => {
policy.Statement.Condition = {
'ForAllValues:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
};
const rcModifiers = {
_requestObjTags: 'tagThree=keyThree&tagFour=keyFour',
_requestObjTags: 'tagOne=keyOne&tagThree=keyThree',
_needTagEval: true,
};
check(requestContext, rcModifiers, policy, 'Neutral');
@ -1241,6 +1217,203 @@ describe('policyEvaluator', () => {
check(requestContext, rcModifiers, policy, 'Neutral');
});
});
describe('with multiple statements', () => {
beforeEach(() => {
requestContext = new RequestContext({}, {}, 'bucket',
undefined, undefined, undefined, 'objectPut', 's3');
requestContext.setRequesterInfo({});
});
const TestMatrix = [
{
statementsToEvaluate: [],
expectedPolicyEvaluation: 'Neutral',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: true },
],
expectedPolicyEvaluation: 'Allow',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: false },
],
expectedPolicyEvaluation: 'Neutral',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: true },
],
expectedPolicyEvaluation: 'Deny',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: false },
],
expectedPolicyEvaluation: 'Neutral',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: false },
{ effect: 'Allow', meetConditions: true },
],
expectedPolicyEvaluation: 'Allow',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: false },
{ effect: 'Allow', meetConditions: false },
],
expectedPolicyEvaluation: 'Neutral',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: false },
{ effect: 'Deny', meetConditions: false },
],
expectedPolicyEvaluation: 'Neutral',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: true },
{ effect: 'Deny', meetConditions: true },
],
expectedPolicyEvaluation: 'Deny',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: true },
{ effect: 'Allow', meetConditions: true },
],
expectedPolicyEvaluation: 'Deny',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: true },
{ effect: 'Deny', meetConditions: false },
],
expectedPolicyEvaluation: 'Allow',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: null },
],
expectedPolicyEvaluation: 'AllowWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: null },
{ effect: 'Allow', meetConditions: null },
],
expectedPolicyEvaluation: 'AllowWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: null },
],
expectedPolicyEvaluation: 'DenyWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: null },
{ effect: 'Deny', meetConditions: null },
],
expectedPolicyEvaluation: 'DenyWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: true },
{ effect: 'Allow', meetConditions: null },
],
expectedPolicyEvaluation: 'Allow',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: false },
{ effect: 'Allow', meetConditions: null },
],
expectedPolicyEvaluation: 'AllowWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: true },
{ effect: 'Deny', meetConditions: null },
],
expectedPolicyEvaluation: 'Deny',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: true },
{ effect: 'Allow', meetConditions: null },
],
expectedPolicyEvaluation: 'Deny',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: false },
{ effect: 'Deny', meetConditions: null },
],
expectedPolicyEvaluation: 'DenyWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: true },
{ effect: 'Deny', meetConditions: null },
],
expectedPolicyEvaluation: 'DenyWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: null },
{ effect: 'Deny', meetConditions: null },
],
expectedPolicyEvaluation: 'DenyWithTagCondition',
},
];
TestMatrix.forEach(testCase => {
const policyDesc = testCase.statementsToEvaluate
.map(statement => `${statement.effect}(met:${statement.meetConditions})`)
.join(', ');
it(`policy with statements evaluating individually to [${policyDesc}] ` +
`should return ${testCase.expectedPolicyEvaluation}`, () => {
const policy = {
Version: '2012-10-17',
Statement: testCase.statementsToEvaluate.map(statement => {
let condition;
if (statement.meetConditions === true) {
condition = {
StringEquals: { 'aws:UserAgent': 'CyberSquaw' },
};
} else if (statement.meetConditions === false) {
condition = {
StringEquals: { 'aws:UserAgent': 'OtherAgent' },
};
} else if (statement.meetConditions === null) {
condition = {
StringEquals: { 's3:ExistingObjectTag/tagKey': 'tagValue' },
};
}
return {
Effect: statement.effect,
Action: 's3:PutObject',
Resource: 'arn:aws:s3:::bucket',
Condition: condition,
};
}),
};
requestContext.setHeaders({
'user-agent': 'CyberSquaw',
});
requestContext.setNeedTagEval(false);
const result = evaluatePolicy(requestContext, policy, log);
assert.strictEqual(result, testCase.expectedPolicyEvaluation);
});
});
});
});
describe('evaluate multiple policies', () => {
@ -1266,17 +1439,152 @@ describe('policyEvaluator', () => {
assert.strictEqual(result, 'Deny');
});
it('should deny access if request resource is not in any policy',
() => {
it('should deny access if request resource is not in any policy', () => {
requestContext = new RequestContext({}, {},
'notbucket', undefined,
undefined, undefined, 'objectGet', 's3');
requestContext.setRequesterInfo({});
const result = evaluateAllPolicies(requestContext, [
samples['Multi-Statement Policy'],
samples['Variable Bucket Policy'],
], log);
assert.strictEqual(result, 'Deny');
});
const TestMatrixPolicies = {
Allow: {
Version: '2012-10-17',
Statement: {
Effect: 'Allow',
Action: 's3:*',
Resource: '*',
},
},
Neutral: {
Version: '2012-10-17',
Statement: {
Effect: 'Allow',
Action: 's3:*',
Resource: 'arn:aws:s3:::other-bucket',
},
},
Deny: {
Version: '2012-10-17',
Statement: {
Effect: 'Deny',
Action: 's3:*',
Resource: '*',
},
},
AllowWithTagCondition: {
Version: '2012-10-17',
Statement: {
Effect: 'Allow',
Action: 's3:*',
Resource: '*',
Condition: {
StringEquals: {
's3:ExistingObjectTag/tagKey': 'tagValue',
},
},
},
},
DenyWithTagCondition: {
Version: '2012-10-17',
Statement: {
Effect: 'Deny',
Action: 's3:*',
Resource: '*',
Condition: {
StringEquals: {
's3:ExistingObjectTag/tagKey': 'tagValue',
},
},
},
},
};
const TestMatrix = [
{
policiesToEvaluate: [],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['Allow'],
expectedPolicyEvaluation: 'Allow',
},
{
policiesToEvaluate: ['Neutral'],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['Deny'],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['Allow', 'Allow'],
expectedPolicyEvaluation: 'Allow',
},
{
policiesToEvaluate: ['Allow', 'Neutral'],
expectedPolicyEvaluation: 'Allow',
},
{
policiesToEvaluate: ['Neutral', 'Allow'],
expectedPolicyEvaluation: 'Allow',
},
{
policiesToEvaluate: ['Neutral', 'Neutral'],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['Allow', 'Deny'],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['AllowWithTagCondition'],
expectedPolicyEvaluation: 'NeedTagConditionEval',
},
{
policiesToEvaluate: ['Allow', 'AllowWithTagCondition'],
expectedPolicyEvaluation: 'Allow',
},
{
policiesToEvaluate: ['DenyWithTagCondition'],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['Allow', 'DenyWithTagCondition'],
expectedPolicyEvaluation: 'NeedTagConditionEval',
},
{
policiesToEvaluate: ['AllowWithTagCondition', 'DenyWithTagCondition'],
expectedPolicyEvaluation: 'NeedTagConditionEval',
},
{
policiesToEvaluate: ['AllowWithTagCondition', 'DenyWithTagCondition', 'Deny'],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['DenyWithTagCondition', 'AllowWithTagCondition', 'Allow'],
expectedPolicyEvaluation: 'NeedTagConditionEval',
},
];
TestMatrix.forEach(testCase => {
it(`policies evaluating individually to [${testCase.policiesToEvaluate.join(', ')}] `
+ `should return ${testCase.expectedPolicyEvaluation}`, () => {
requestContext = new RequestContext({}, {},
'notbucket', undefined,
'my_favorite_bucket', undefined,
undefined, undefined, 'objectGet', 's3');
requestContext.setRequesterInfo({});
const result = evaluateAllPolicies(requestContext,
[samples['Multi-Statement Policy'],
samples['Variable Bucket Policy']], log);
assert.strictEqual(result, 'Deny');
const result = evaluateAllPolicies(
requestContext,
testCase.policiesToEvaluate.map(policyName => TestMatrixPolicies[policyName]),
log);
assert.strictEqual(result, testCase.expectedPolicyEvaluation);
});
});
});
});

View File

@ -0,0 +1,128 @@
const assert = require('assert');
const RequestContext = require('../../../lib/policyEvaluator/RequestContext').default;
describe('RequestContext', () => {
const constructorParams = [
{ 'some-header': 'some-value' }, // headers
{ q1: 'v1', q2: 'v2' }, // query
'general-resource', // generalResource
'specific-resource', // specificResource
'127.0.0.1', // requesterIp
true, // sslEnabled
'GET', // apiMethod
's3', // awsService
'us-east-1', // locationConstraint
{ // requesterInfo
arn: 'arn:aws:iam::user/johndoe',
accountId: 'JOHNACCOUNT',
username: 'John Doe',
principalType: 'user',
},
'v4', // signatureVersion
'REST-HEADER', // authType
123456, // signatureAge
'security-token', // securityToken
'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess', // policyArn
'objectGet', // action
'reqTagOne=valueOne&reqTagTwo=valueTwo', // requestObjTags
'existingTagOne=valueOne&existingTagTwo=valueTwo', // existingObjTag
true, // needTagEval
];
const rc = new RequestContext(...constructorParams);
const GetterTests = [
{ name: 'getAction', expectedValue: 'objectGet' },
{ name: 'getResource', expectedValue: 'arn:aws:s3:::general-resource/specific-resource' },
{ name: 'getHeaders', expectedValue: { 'some-header': 'some-value' } },
{ name: 'getQuery', expectedValue: { q1: 'v1', q2: 'v2' } },
{
name: 'getRequesterInfo',
expectedValue: {
accountId: 'JOHNACCOUNT',
arn: 'arn:aws:iam::user/johndoe',
username: 'John Doe',
principalType: 'user',
},
},
{ name: 'getRequesterIp', expectedValueToString: '127.0.0.1' },
{ name: 'getRequesterAccountId', expectedValue: undefined },
{ name: 'getRequesterEndArn', expectedValue: 'arn:aws:iam::user/johndoe' },
{ name: 'getRequesterExternalId', expectedValue: undefined },
{ name: 'getRequesterPrincipalArn', expectedValue: 'arn:aws:iam::user/johndoe' },
{ name: 'getRequesterType', expectedValue: 'user' },
{ name: 'getSslEnabled', expectedValue: true },
{ name: 'getSignatureVersion', expectedValue: 'v4' },
{ name: 'getAuthType', expectedValue: 'REST-HEADER' },
{ name: 'getSignatureAge', expectedValue: 123456 },
{ name: 'getLocationConstraint', expectedValue: 'us-east-1' },
{ name: 'getAwsService', expectedValue: 's3' },
{ name: 'getTokenIssueTime', expectedValue: null },
{ name: 'getMultiFactorAuthPresent', expectedValue: null },
{ name: 'getMultiFactorAuthAge', expectedValue: null },
{ name: 'getSecurityToken', expectedValue: 'security-token' },
{ name: 'getPolicyArn', expectedValue: 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess' },
{ name: 'isQuotaCheckNeeded', expectedValue: false },
{ name: 'getRequestObjTags', expectedValue: 'reqTagOne=valueOne&reqTagTwo=valueTwo' },
{ name: 'getExistingObjTag', expectedValue: 'existingTagOne=valueOne&existingTagTwo=valueTwo' },
{ name: 'getNeedTagEval', expectedValue: true },
];
GetterTests.forEach(testCase => {
it(`getter:${testCase.name}`, () => {
const getterResult = rc[testCase.name]();
if (testCase.expectedValueToString) {
assert.strictEqual(getterResult.toString(), testCase.expectedValueToString);
} else {
assert.deepStrictEqual(getterResult, testCase.expectedValue);
}
});
});
const SerializedFields = {
action: 'objectGet',
apiMethod: 'GET',
authType: 'REST-HEADER',
awsService: 's3',
existingObjTag: 'existingTagOne=valueOne&existingTagTwo=valueTwo',
generalResource: 'general-resource',
headers: {
'some-header': 'some-value',
},
locationConstraint: 'us-east-1',
multiFactorAuthAge: null,
multiFactorAuthPresent: null,
needTagEval: true,
policyArn: 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess',
query: {
q1: 'v1',
q2: 'v2',
},
requesterInfo: {
accountId: 'JOHNACCOUNT',
arn: 'arn:aws:iam::user/johndoe',
principalType: 'user',
username: 'John Doe',
},
requestObjTags: 'reqTagOne=valueOne&reqTagTwo=valueTwo',
requesterIp: '127.0.0.1',
securityToken: 'security-token',
signatureAge: 123456,
signatureVersion: 'v4',
specificResource: 'specific-resource',
sslEnabled: true,
tokenIssueTime: null,
};
it('serialize()', () => {
assert.deepStrictEqual(JSON.parse(rc.serialize()), SerializedFields);
});
it('deSerialize()', () => {
// check that everything that was serialized is deserialized
// properly into a new RequestContext object by making sure
// the serialized version of the latter corresponds to the
// input
const serialized = JSON.stringify(SerializedFields);
const deserializedRC = RequestContext.deSerialize(serialized);
const newSerialized = JSON.parse(deserializedRC.serialize());
assert.deepStrictEqual(newSerialized, SerializedFields);
});
});