Compare commits
10 Commits
c16d4e6954
...
23f7e5af6a
Author | SHA1 | Date |
---|---|---|
Jonathan Gramain | 23f7e5af6a | |
Jonathan Gramain | cb31a07d8d | |
Jonathan Gramain | 62be7f926d | |
Alexander Chan | 2d205f8131 | |
Jonathan Gramain | 0aa95823b7 | |
Jonathan Gramain | 5f1e91b44c | |
Jonathan Gramain | c5c736aa48 | |
Jonathan Gramain | bca37e4b06 | |
Nicolas Humbert | 5fd84e78e2 | |
williamlardier | dd603482f0 |
|
@ -271,4 +271,4 @@ class Delimiter extends Extension {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { Delimiter, getCommonPrefix };
|
module.exports = { Delimiter };
|
||||||
|
|
|
@ -33,8 +33,6 @@ class DelimiterMaster extends Delimiter {
|
||||||
this.prvKey = undefined;
|
this.prvKey = undefined;
|
||||||
this.prvPHDKey = undefined;
|
this.prvPHDKey = undefined;
|
||||||
this.inReplayPrefix = false;
|
this.inReplayPrefix = false;
|
||||||
this.prefixKeySeen = false;
|
|
||||||
this.prefixEndsWithDelim = this.prefix && this.prefix.endsWith(this.delimiter);
|
|
||||||
|
|
||||||
Object.assign(this, {
|
Object.assign(this, {
|
||||||
[BucketVersioningKeyFormat.v0]: {
|
[BucketVersioningKeyFormat.v0]: {
|
||||||
|
@ -64,10 +62,6 @@ class DelimiterMaster extends Delimiter {
|
||||||
let key = obj.key;
|
let key = obj.key;
|
||||||
const value = obj.value;
|
const value = obj.value;
|
||||||
|
|
||||||
if (key === this.prefix) {
|
|
||||||
this.prefixKeySeen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key.startsWith(DbPrefixes.Replay)) {
|
if (key.startsWith(DbPrefixes.Replay)) {
|
||||||
this.inReplayPrefix = true;
|
this.inReplayPrefix = true;
|
||||||
return FILTER_SKIP;
|
return FILTER_SKIP;
|
||||||
|
@ -101,8 +95,9 @@ class DelimiterMaster extends Delimiter {
|
||||||
* NextMarker to the common prefix instead of the whole key
|
* NextMarker to the common prefix instead of the whole key
|
||||||
* value. (TODO: remove this test once ZENKO-1048 is fixed)
|
* value. (TODO: remove this test once ZENKO-1048 is fixed)
|
||||||
* */
|
* */
|
||||||
if (key === this.prvKey || key === this[this.nextContinueMarker]
|
if (key === this.prvKey || key === this[this.nextContinueMarker] ||
|
||||||
|| (this.delimiter && key.startsWith(this[this.nextContinueMarker]))) {
|
(this.delimiter &&
|
||||||
|
key.startsWith(this[this.nextContinueMarker]))) {
|
||||||
/* master version already filtered */
|
/* master version already filtered */
|
||||||
return FILTER_SKIP;
|
return FILTER_SKIP;
|
||||||
}
|
}
|
||||||
|
@ -132,14 +127,6 @@ class DelimiterMaster extends Delimiter {
|
||||||
return FILTER_ACCEPT;
|
return FILTER_ACCEPT;
|
||||||
}
|
}
|
||||||
this.prvKey = key;
|
this.prvKey = key;
|
||||||
if (this.prefixEndsWithDelim) {
|
|
||||||
/* When the prefix ends with a delimiter, update nextContinueMarker
|
|
||||||
* to be able to skip ranges of the form prefix/subprefix/ as an optimization.
|
|
||||||
* The marker may also end up being prefix/, in which case .skipping will determine
|
|
||||||
* if a skip over the full range is allowed or a smaller skipping range of prefix/{VID_SEP}
|
|
||||||
* must be used. */
|
|
||||||
this[this.nextContinueMarker] = key.slice(0, key.lastIndexOf(this.delimiter) + 1);
|
|
||||||
}
|
|
||||||
return FILTER_SKIP;
|
return FILTER_SKIP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,27 +162,6 @@ class DelimiterMaster extends Delimiter {
|
||||||
return super.filter(obj);
|
return super.filter(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if a nextContinueMarker ending with a delimiter
|
|
||||||
* can be skipped.
|
|
||||||
* @returns {bool}
|
|
||||||
*/
|
|
||||||
|
|
||||||
allowDelimiterRangeSkip() {
|
|
||||||
if (!this.prefixKeySeen) {
|
|
||||||
// A prefix key is a master key equal to the prefix. If it has
|
|
||||||
// not been encountered, can skip.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const marker = this[this.nextContinueMarker];
|
|
||||||
// prefix = prefix, key = prefix/. Can skip since key will be part of commonPrefixes.
|
|
||||||
if (marker.length > this.prefix.length && marker.startsWith(this.prefix)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const lastIdx = marker.lastIndexOf(this.delimiter); // prefix/foo is a masterKey following a prefix key.
|
|
||||||
return marker.slice(0, lastIdx + 1) !== this.prefix; // cannot skip the full range prefix/ range.
|
|
||||||
}
|
|
||||||
|
|
||||||
skippingBase() {
|
skippingBase() {
|
||||||
if (this[this.nextContinueMarker]) {
|
if (this[this.nextContinueMarker]) {
|
||||||
// next marker or next continuation token:
|
// next marker or next continuation token:
|
||||||
|
@ -203,7 +169,7 @@ class DelimiterMaster extends Delimiter {
|
||||||
// - foo : skipping foo.
|
// - foo : skipping foo.
|
||||||
const index = this[this.nextContinueMarker].
|
const index = this[this.nextContinueMarker].
|
||||||
lastIndexOf(this.delimiter);
|
lastIndexOf(this.delimiter);
|
||||||
if (index === this[this.nextContinueMarker].length - 1 && this.allowDelimiterRangeSkip()) {
|
if (index === this[this.nextContinueMarker].length - 1) {
|
||||||
return this[this.nextContinueMarker];
|
return this[this.nextContinueMarker];
|
||||||
}
|
}
|
||||||
return this[this.nextContinueMarker] + VID_SEP;
|
return this[this.nextContinueMarker] + VID_SEP;
|
||||||
|
|
|
@ -166,7 +166,6 @@ export default class RequestContext {
|
||||||
_policyArn: string;
|
_policyArn: string;
|
||||||
_action?: string;
|
_action?: string;
|
||||||
_needQuota: boolean;
|
_needQuota: boolean;
|
||||||
_postXml?: string;
|
|
||||||
_requestObjTags: string | null;
|
_requestObjTags: string | null;
|
||||||
_existingObjTag: string | null;
|
_existingObjTag: string | null;
|
||||||
_needTagEval: boolean;
|
_needTagEval: boolean;
|
||||||
|
@ -190,7 +189,9 @@ export default class RequestContext {
|
||||||
securityToken: string,
|
securityToken: string,
|
||||||
policyArn: string,
|
policyArn: string,
|
||||||
action?: string,
|
action?: string,
|
||||||
postXml?: string,
|
requestObjTags?: string,
|
||||||
|
existingObjTag?: string,
|
||||||
|
needTagEval?: false,
|
||||||
) {
|
) {
|
||||||
this._headers = headers;
|
this._headers = headers;
|
||||||
this._query = query;
|
this._query = query;
|
||||||
|
@ -220,10 +221,9 @@ export default class RequestContext {
|
||||||
this._policyArn = policyArn;
|
this._policyArn = policyArn;
|
||||||
this._action = action;
|
this._action = action;
|
||||||
this._needQuota = _actionNeedQuotaCheck[apiMethod] === true;
|
this._needQuota = _actionNeedQuotaCheck[apiMethod] === true;
|
||||||
this._postXml = postXml;
|
this._requestObjTags = requestObjTags || null;
|
||||||
this._requestObjTags = null;
|
this._existingObjTag = existingObjTag || null;
|
||||||
this._existingObjTag = null;
|
this._needTagEval = needTagEval || false;
|
||||||
this._needTagEval = false;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +236,7 @@ export default class RequestContext {
|
||||||
apiMethod: this._apiMethod,
|
apiMethod: this._apiMethod,
|
||||||
headers: this._headers,
|
headers: this._headers,
|
||||||
query: this._query,
|
query: this._query,
|
||||||
requersterInfo: this._requesterInfo,
|
requesterInfo: this._requesterInfo,
|
||||||
requesterIp: this._requesterIp,
|
requesterIp: this._requesterIp,
|
||||||
sslEnabled: this._sslEnabled,
|
sslEnabled: this._sslEnabled,
|
||||||
awsService: this._awsService,
|
awsService: this._awsService,
|
||||||
|
@ -252,7 +252,6 @@ export default class RequestContext {
|
||||||
securityToken: this._securityToken,
|
securityToken: this._securityToken,
|
||||||
policyArn: this._policyArn,
|
policyArn: this._policyArn,
|
||||||
action: this._action,
|
action: this._action,
|
||||||
postXml: this._postXml,
|
|
||||||
requestObjTags: this._requestObjTags,
|
requestObjTags: this._requestObjTags,
|
||||||
existingObjTag: this._existingObjTag,
|
existingObjTag: this._existingObjTag,
|
||||||
needTagEval: this._needTagEval,
|
needTagEval: this._needTagEval,
|
||||||
|
@ -276,12 +275,27 @@ export default class RequestContext {
|
||||||
if (resource) {
|
if (resource) {
|
||||||
obj.specificResource = resource;
|
obj.specificResource = resource;
|
||||||
}
|
}
|
||||||
return new RequestContext(obj.headers, obj.query, obj.generalResource,
|
return new RequestContext(
|
||||||
obj.specificResource, obj.requesterIp, obj.sslEnabled,
|
obj.headers,
|
||||||
obj.apiMethod, obj.awsService, obj.locationConstraint,
|
obj.query,
|
||||||
obj.requesterInfo, obj.signatureVersion,
|
obj.generalResource,
|
||||||
obj.authType, obj.signatureAge, obj.securityToken, obj.policyArn,
|
obj.specificResource,
|
||||||
obj.action, obj.postXml);
|
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;
|
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
|
* Set request object tags
|
||||||
*
|
*
|
||||||
|
|
|
@ -13,7 +13,11 @@ const operatorsWithVariables = ['StringEquals', 'StringNotEquals',
|
||||||
const operatorsWithNegation = ['StringNotEquals',
|
const operatorsWithNegation = ['StringNotEquals',
|
||||||
'StringNotEqualsIgnoreCase', 'StringNotLike', 'ArnNotEquals',
|
'StringNotEqualsIgnoreCase', 'StringNotLike', 'ArnNotEquals',
|
||||||
'ArnNotLike', 'NumericNotEquals'];
|
'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
|
* @param log - logger
|
||||||
* @return true if applicable, false if not
|
* @return true if applicable, false if not
|
||||||
*/
|
*/
|
||||||
export const isResourceApplicable = (
|
export function isResourceApplicable(
|
||||||
requestContext: RequestContext,
|
requestContext: RequestContext,
|
||||||
statementResource: string | string[],
|
statementResource: string | string[],
|
||||||
log: Logger,
|
log: Logger,
|
||||||
): boolean => {
|
): boolean {
|
||||||
const resource = requestContext.getResource();
|
const resource = requestContext.getResource();
|
||||||
if (!Array.isArray(statementResource)) {
|
if (!Array.isArray(statementResource)) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
@ -59,7 +63,7 @@ export const isResourceApplicable = (
|
||||||
{ requestResource: resource });
|
{ requestResource: resource });
|
||||||
// If no match found, no resource is applicable
|
// If no match found, no resource is applicable
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether action in policy statement applies to request
|
* Check whether action in policy statement applies to request
|
||||||
|
@ -69,11 +73,11 @@ export const isResourceApplicable = (
|
||||||
* @param log - logger
|
* @param log - logger
|
||||||
* @return true if applicable, false if not
|
* @return true if applicable, false if not
|
||||||
*/
|
*/
|
||||||
export const isActionApplicable = (
|
export function isActionApplicable(
|
||||||
requestAction: string,
|
requestAction: string,
|
||||||
statementAction: string | string[],
|
statementAction: string | string[],
|
||||||
log: Logger,
|
log: Logger,
|
||||||
): boolean => {
|
): boolean {
|
||||||
if (!Array.isArray(statementAction)) {
|
if (!Array.isArray(statementAction)) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
statementAction = [statementAction];
|
statementAction = [statementAction];
|
||||||
|
@ -95,32 +99,33 @@ export const isActionApplicable = (
|
||||||
{ requestAction });
|
{ requestAction });
|
||||||
// If no match found, return false
|
// If no match found, return false
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether request meets policy conditions
|
* Check whether request meets policy conditions
|
||||||
* @param requestContext - info about request
|
* @param {RequestContext} requestContext - info about request
|
||||||
* @param statementCondition - Condition statement from policy
|
* @param {object} statementCondition - Condition statement from policy
|
||||||
* @param log - logger
|
* @param {Logger} log - logger
|
||||||
* @return contains whether conditions are allowed and whether they
|
* @return {boolean|null} a condition evaluation result, one of:
|
||||||
* contain any tag condition keys
|
* - 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,
|
requestContext: RequestContext,
|
||||||
statementCondition: any,
|
statementCondition: any,
|
||||||
log: Logger,
|
log: Logger,
|
||||||
) => {
|
): boolean | null {
|
||||||
|
let hasTagConditions = false;
|
||||||
// The Condition portion of a policy is an object with different
|
// The Condition portion of a policy is an object with different
|
||||||
// operators as keys
|
// operators as keys
|
||||||
const conditionEval = {};
|
for (const operator of Object.keys(statementCondition)) {
|
||||||
const operators = Object.keys(statementCondition);
|
|
||||||
const length = operators.length;
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
const operator = operators[i];
|
|
||||||
const hasPrefix = operator.includes(':');
|
const hasPrefix = operator.includes(':');
|
||||||
const hasIfExistsCondition = operator.endsWith('IfExists');
|
const hasIfExistsCondition = operator.endsWith('IfExists');
|
||||||
// If has "IfExists" added to operator name, or operator has "ForAnyValue" or
|
// 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) :
|
let bareOperator = hasIfExistsCondition ? operator.slice(0, -8) :
|
||||||
operator;
|
operator;
|
||||||
let prefix: string | undefined;
|
let prefix: string | undefined;
|
||||||
|
@ -135,10 +140,6 @@ export const meetConditions = (
|
||||||
// Note: this should be the actual operator name, not the bareOperator
|
// Note: this should be the actual operator name, not the bareOperator
|
||||||
const conditionsWithSameOperator = statementCondition[operator];
|
const conditionsWithSameOperator = statementCondition[operator];
|
||||||
const conditionKeys = Object.keys(conditionsWithSameOperator);
|
const conditionKeys = Object.keys(conditionsWithSameOperator);
|
||||||
if (conditionKeys.some(key => tagConditions.has(key)) && !requestContext.getNeedTagEval()) {
|
|
||||||
// @ts-expect-error
|
|
||||||
conditionEval.tagConditions = true;
|
|
||||||
}
|
|
||||||
const conditionKeysLength = conditionKeys.length;
|
const conditionKeysLength = conditionKeys.length;
|
||||||
for (let j = 0; j < conditionKeysLength; j++) {
|
for (let j = 0; j < conditionKeysLength; j++) {
|
||||||
const key = conditionKeys[j];
|
const key = conditionKeys[j];
|
||||||
|
@ -155,6 +156,10 @@ export const meetConditions = (
|
||||||
// tag key is included in condition key and needs to be
|
// tag key is included in condition key and needs to be
|
||||||
// moved to value for evaluation, otherwise key/value are unchanged
|
// moved to value for evaluation, otherwise key/value are unchanged
|
||||||
const [transformedKey, transformedValue] = transformTagKeyValue(key, value);
|
const [transformedKey, transformedValue] = transformTagKeyValue(key, value);
|
||||||
|
if (tagConditions.has(transformedKey) && !requestContext.getNeedTagEval()) {
|
||||||
|
hasTagConditions = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// Pull key using requestContext
|
// Pull key using requestContext
|
||||||
// TODO: If applicable to S3, handle policy set operations
|
// TODO: If applicable to S3, handle policy set operations
|
||||||
// where a keyBasedOnRequestContext returns multiple values and
|
// where a keyBasedOnRequestContext returns multiple values and
|
||||||
|
@ -180,11 +185,10 @@ export const meetConditions = (
|
||||||
log.trace('condition not satisfied due to ' +
|
log.trace('condition not satisfied due to ' +
|
||||||
'missing info', { operator,
|
'missing info', { operator,
|
||||||
conditionKey: transformedKey, policyValue: transformedValue });
|
conditionKey: transformedKey, policyValue: transformedValue });
|
||||||
return { allow: false };
|
return false;
|
||||||
}
|
}
|
||||||
// If condition operator prefix is included, the key should be an array
|
// If condition operator prefix is included, the key should be an array
|
||||||
if (prefix && !Array.isArray(keyBasedOnRequestContext)) {
|
if (prefix && !Array.isArray(keyBasedOnRequestContext)) {
|
||||||
// @ts-expect-error
|
|
||||||
keyBasedOnRequestContext = [keyBasedOnRequestContext];
|
keyBasedOnRequestContext = [keyBasedOnRequestContext];
|
||||||
}
|
}
|
||||||
// Transalate operator into function using bareOperator
|
// Transalate operator into function using bareOperator
|
||||||
|
@ -196,14 +200,16 @@ export const meetConditions = (
|
||||||
if (!operatorFunction(keyBasedOnRequestContext, transformedValue, prefix)) {
|
if (!operatorFunction(keyBasedOnRequestContext, transformedValue, prefix)) {
|
||||||
log.trace('did not satisfy condition', { operator: bareOperator,
|
log.trace('did not satisfy condition', { operator: bareOperator,
|
||||||
keyBasedOnRequestContext, policyValue: transformedValue });
|
keyBasedOnRequestContext, policyValue: transformedValue });
|
||||||
return { allow: false };
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// @ts-expect-error
|
// one or more conditions required tag info to be evaluated
|
||||||
conditionEval.allow = true;
|
if (hasTagConditions) {
|
||||||
return conditionEval;
|
return null;
|
||||||
};
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluate whether a request is permitted under a policy.
|
* 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
|
* @return Allow if permitted, Deny if not permitted or Neutral
|
||||||
* if not applicable
|
* if not applicable
|
||||||
*/
|
*/
|
||||||
export const evaluatePolicy = (
|
export function evaluatePolicy(
|
||||||
requestContext: RequestContext,
|
requestContext: RequestContext,
|
||||||
policy: any,
|
policy: any,
|
||||||
log: Logger,
|
log: Logger,
|
||||||
): string => {
|
): string {
|
||||||
// TODO: For bucket policies need to add Principal evaluation
|
// 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)) {
|
if (!Array.isArray(policy.Statement)) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
@ -259,10 +267,18 @@ export const evaluatePolicy = (
|
||||||
}
|
}
|
||||||
const conditionEval = currentStatement.Condition ?
|
const conditionEval = currentStatement.Condition ?
|
||||||
meetConditions(requestContext, currentStatement.Condition, log) :
|
meetConditions(requestContext, currentStatement.Condition, log) :
|
||||||
null;
|
true;
|
||||||
// If do not meet conditions move on to next statement
|
// If do not meet conditions move on to next statement
|
||||||
// @ts-expect-error
|
if (conditionEval === false) {
|
||||||
if (conditionEval && !conditionEval.allow) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
if (currentStatement.Effect === 'Deny') {
|
if (currentStatement.Effect === 'Deny') {
|
||||||
|
@ -271,17 +287,27 @@ export const evaluatePolicy = (
|
||||||
return 'Deny';
|
return 'Deny';
|
||||||
}
|
}
|
||||||
log.trace('Allow statement applies');
|
log.trace('Allow statement applies');
|
||||||
// If statement is applicable, conditions are met and Effect is
|
// statement is applicable, conditions are met and Effect is
|
||||||
// to Allow, set verdict to Allow
|
// 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';
|
verdict = 'Allow';
|
||||||
// @ts-expect-error
|
} else if (allowWithTagCondition) {
|
||||||
if (conditionEval && conditionEval.tagConditions) {
|
// all allow statements need tag checks
|
||||||
verdict = 'NeedTagConditionEval';
|
verdict = 'AllowWithTagCondition';
|
||||||
}
|
} else {
|
||||||
|
// no statement matched to allow or deny
|
||||||
|
verdict = 'Neutral';
|
||||||
}
|
}
|
||||||
log.trace('result of evaluating single policy', { verdict });
|
log.trace('result of evaluating single policy', { verdict });
|
||||||
return verdict;
|
return verdict;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluate whether a request is permitted under a policy.
|
* Evaluate whether a request is permitted under a policy.
|
||||||
|
@ -294,24 +320,43 @@ export const evaluatePolicy = (
|
||||||
* @return Allow if permitted, Deny if not permitted.
|
* @return Allow if permitted, Deny if not permitted.
|
||||||
* Default is to Deny. Deny overrides an Allow
|
* Default is to Deny. Deny overrides an Allow
|
||||||
*/
|
*/
|
||||||
export const evaluateAllPolicies = (
|
export function evaluateAllPolicies(
|
||||||
requestContext: RequestContext,
|
requestContext: RequestContext,
|
||||||
allPolicies: any[],
|
allPolicies: any[],
|
||||||
log: Logger,
|
log: Logger,
|
||||||
): string => {
|
): string {
|
||||||
log.trace('evaluating all policies');
|
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++) {
|
for (let i = 0; i < allPolicies.length; i++) {
|
||||||
const singlePolicyVerdict =
|
const singlePolicyVerdict = evaluatePolicy(requestContext, allPolicies[i], log);
|
||||||
evaluatePolicy(requestContext, allPolicies[i], log);
|
|
||||||
// If there is any Deny, just return Deny
|
// If there is any Deny, just return Deny
|
||||||
if (singlePolicyVerdict === 'Deny') {
|
if (singlePolicyVerdict === 'Deny') {
|
||||||
return 'Deny';
|
return 'Deny';
|
||||||
}
|
}
|
||||||
if (singlePolicyVerdict === 'Allow') {
|
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';
|
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;
|
return verdict;
|
||||||
};
|
}
|
||||||
|
|
|
@ -23,15 +23,22 @@ export default class Principal {
|
||||||
* @param statement - Statement policy field
|
* @param statement - Statement policy field
|
||||||
* @return True if meet conditions
|
* @return True if meet conditions
|
||||||
*/
|
*/
|
||||||
static _evaluateCondition(
|
static _evaluateStatement(
|
||||||
params: Params,
|
params: Params,
|
||||||
statement: Statement,
|
statement: Statement,
|
||||||
// TODO Fix return type
|
): 'Neutral' | 'Allow' | 'Deny' {
|
||||||
): any {
|
const reverse = !!statement.NotPrincipal;
|
||||||
if (statement.Condition) {
|
if (reverse) {
|
||||||
return meetConditions(params.rc, statement.Condition, params.log);
|
// 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,
|
statement: Statement,
|
||||||
valids: Valid,
|
valids: Valid,
|
||||||
): 'Neutral' | 'Allow' | 'Deny' {
|
): 'Neutral' | 'Allow' | 'Deny' {
|
||||||
const reverse = !!statement.NotPrincipal;
|
|
||||||
const principal = (statement.Principal || statement.NotPrincipal)!;
|
const principal = (statement.Principal || statement.NotPrincipal)!;
|
||||||
if (typeof principal === 'string' && principal === '*') {
|
const reverse = !!statement.NotPrincipal;
|
||||||
if (reverse) {
|
if (typeof principal === 'string') {
|
||||||
// In case of anonymous NotPrincipal, this will neutral everyone
|
if (principal === '*') {
|
||||||
return 'Neutral';
|
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';
|
return 'Deny';
|
||||||
}
|
}
|
||||||
let ref = [];
|
let ref = [];
|
||||||
|
@ -82,28 +82,8 @@ export default class Principal {
|
||||||
}
|
}
|
||||||
toCheck = Array.isArray(toCheck) ? toCheck : [toCheck];
|
toCheck = Array.isArray(toCheck) ? toCheck : [toCheck];
|
||||||
ref = Array.isArray(ref) ? ref : [ref];
|
ref = Array.isArray(ref) ? ref : [ref];
|
||||||
if (toCheck.indexOf('*') !== -1) {
|
if (toCheck.includes('*') || ref.some(r => toCheck.includes(r))) {
|
||||||
if (reverse) {
|
return Principal._evaluateStatement(params, statement);
|
||||||
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 (reverse) {
|
if (reverse) {
|
||||||
return statement.Effect;
|
return statement.Effect;
|
||||||
|
|
|
@ -4,14 +4,14 @@ const sharedActionMap = {
|
||||||
bucketDeleteEncryption: 's3:PutEncryptionConfiguration',
|
bucketDeleteEncryption: 's3:PutEncryptionConfiguration',
|
||||||
bucketDeletePolicy: 's3:DeleteBucketPolicy',
|
bucketDeletePolicy: 's3:DeleteBucketPolicy',
|
||||||
bucketDeleteWebsite: 's3:DeleteBucketWebsite',
|
bucketDeleteWebsite: 's3:DeleteBucketWebsite',
|
||||||
bucketDeleteTagging: 's3:DeleteBucketTagging',
|
bucketDeleteTagging: 's3:PutBucketTagging',
|
||||||
bucketGet: 's3:ListBucket',
|
bucketGet: 's3:ListBucket',
|
||||||
bucketGetACL: 's3:GetBucketAcl',
|
bucketGetACL: 's3:GetBucketAcl',
|
||||||
bucketGetCors: 's3:GetBucketCORS',
|
bucketGetCors: 's3:GetBucketCORS',
|
||||||
bucketGetEncryption: 's3:GetEncryptionConfiguration',
|
bucketGetEncryption: 's3:GetEncryptionConfiguration',
|
||||||
bucketGetLifecycle: 's3:GetLifecycleConfiguration',
|
bucketGetLifecycle: 's3:GetLifecycleConfiguration',
|
||||||
bucketGetLocation: 's3:GetBucketLocation',
|
bucketGetLocation: 's3:GetBucketLocation',
|
||||||
bucketGetNotification: 's3:GetBucketNotificationConfiguration',
|
bucketGetNotification: 's3:GetBucketNotification',
|
||||||
bucketGetObjectLock: 's3:GetBucketObjectLockConfiguration',
|
bucketGetObjectLock: 's3:GetBucketObjectLockConfiguration',
|
||||||
bucketGetPolicy: 's3:GetBucketPolicy',
|
bucketGetPolicy: 's3:GetBucketPolicy',
|
||||||
bucketGetReplication: 's3:GetReplicationConfiguration',
|
bucketGetReplication: 's3:GetReplicationConfiguration',
|
||||||
|
@ -23,7 +23,7 @@ const sharedActionMap = {
|
||||||
bucketPutCors: 's3:PutBucketCORS',
|
bucketPutCors: 's3:PutBucketCORS',
|
||||||
bucketPutEncryption: 's3:PutEncryptionConfiguration',
|
bucketPutEncryption: 's3:PutEncryptionConfiguration',
|
||||||
bucketPutLifecycle: 's3:PutLifecycleConfiguration',
|
bucketPutLifecycle: 's3:PutLifecycleConfiguration',
|
||||||
bucketPutNotification: 's3:PutBucketNotificationConfiguration',
|
bucketPutNotification: 's3:PutBucketNotification',
|
||||||
bucketPutObjectLock: 's3:PutBucketObjectLockConfiguration',
|
bucketPutObjectLock: 's3:PutBucketObjectLockConfiguration',
|
||||||
bucketPutPolicy: 's3:PutBucketPolicy',
|
bucketPutPolicy: 's3:PutBucketPolicy',
|
||||||
bucketPutReplication: 's3:PutReplicationConfiguration',
|
bucketPutReplication: 's3:PutReplicationConfiguration',
|
||||||
|
@ -55,8 +55,8 @@ const actionMapRQ = {
|
||||||
// see http://docs.aws.amazon.com/AmazonS3/latest/API/
|
// see http://docs.aws.amazon.com/AmazonS3/latest/API/
|
||||||
// RESTBucketDELETEcors.html
|
// RESTBucketDELETEcors.html
|
||||||
bucketDeleteCors: 's3:PutBucketCORS',
|
bucketDeleteCors: 's3:PutBucketCORS',
|
||||||
bucketDeleteReplication: 's3:DeleteReplicationConfiguration',
|
bucketDeleteReplication: 's3:PutReplicationConfiguration',
|
||||||
bucketDeleteLifecycle: 's3:DeleteLifecycleConfiguration',
|
bucketDeleteLifecycle: 's3:PutLifecycleConfiguration',
|
||||||
completeMultipartUpload: 's3:PutObject',
|
completeMultipartUpload: 's3:PutObject',
|
||||||
initiateMultipartUpload: 's3:PutObject',
|
initiateMultipartUpload: 's3:PutObject',
|
||||||
objectDeleteVersion: 's3:DeleteObjectVersion',
|
objectDeleteVersion: 's3:DeleteObjectVersion',
|
||||||
|
@ -72,6 +72,7 @@ const actionMapRQ = {
|
||||||
objectReplicate: 's3:ReplicateObject',
|
objectReplicate: 's3:ReplicateObject',
|
||||||
objectPutRetentionVersion: 's3:PutObjectVersionRetention',
|
objectPutRetentionVersion: 's3:PutObjectVersionRetention',
|
||||||
objectPutLegalHoldVersion: 's3:PutObjectVersionLegalHold',
|
objectPutLegalHoldVersion: 's3:PutObjectVersionLegalHold',
|
||||||
|
listObjectVersions: 's3:ListBucketVersions',
|
||||||
...sharedActionMap,
|
...sharedActionMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -104,7 +105,7 @@ const actionMonitoringMapS3 = {
|
||||||
bucketGetCors: 'GetBucketCors',
|
bucketGetCors: 'GetBucketCors',
|
||||||
bucketGetLifecycle: 'GetBucketLifecycleConfiguration',
|
bucketGetLifecycle: 'GetBucketLifecycleConfiguration',
|
||||||
bucketGetLocation: 'GetBucketLocation',
|
bucketGetLocation: 'GetBucketLocation',
|
||||||
bucketGetNotification: 'GetBucketNotificationConfiguration',
|
bucketGetNotification: 'GetBucketNotification',
|
||||||
bucketGetObjectLock: 'GetObjectLockConfiguration',
|
bucketGetObjectLock: 'GetObjectLockConfiguration',
|
||||||
bucketGetPolicy: 'GetBucketPolicy',
|
bucketGetPolicy: 'GetBucketPolicy',
|
||||||
bucketGetReplication: 'GetBucketReplication',
|
bucketGetReplication: 'GetBucketReplication',
|
||||||
|
@ -117,7 +118,7 @@ const actionMonitoringMapS3 = {
|
||||||
bucketPutACL: 'PutBucketAcl',
|
bucketPutACL: 'PutBucketAcl',
|
||||||
bucketPutCors: 'PutBucketCors',
|
bucketPutCors: 'PutBucketCors',
|
||||||
bucketPutLifecycle: 'PutBucketLifecycleConfiguration',
|
bucketPutLifecycle: 'PutBucketLifecycleConfiguration',
|
||||||
bucketPutNotification: 'PutBucketNotificationConfiguration',
|
bucketPutNotification: 'PutBucketNotification',
|
||||||
bucketPutObjectLock: 'PutObjectLockConfiguration',
|
bucketPutObjectLock: 'PutObjectLockConfiguration',
|
||||||
bucketPutPolicy: 'PutBucketPolicy',
|
bucketPutPolicy: 'PutBucketPolicy',
|
||||||
bucketPutReplication: 'PutBucketReplication',
|
bucketPutReplication: 'PutBucketReplication',
|
||||||
|
|
|
@ -11,31 +11,30 @@ import ipaddr from 'ipaddr.js';
|
||||||
* @param requestContext - info sent with request
|
* @param requestContext - info sent with request
|
||||||
* @return condition key value
|
* @return condition key value
|
||||||
*/
|
*/
|
||||||
export const findConditionKey = (
|
export function findConditionKey(
|
||||||
key: string,
|
key: string,
|
||||||
requestContext: RequestContext,
|
requestContext: RequestContext,
|
||||||
): string => {
|
): any {
|
||||||
// TODO: Consider combining with findVariable function if no benefit
|
// TODO: Consider combining with findVariable function if no benefit
|
||||||
// to keeping separate
|
// to keeping separate
|
||||||
const headers = requestContext.getHeaders();
|
const headers = requestContext.getHeaders();
|
||||||
const query = requestContext.getQuery();
|
const query = requestContext.getQuery();
|
||||||
const requesterInfo = requestContext.getRequesterInfo();
|
const requesterInfo = requestContext.getRequesterInfo();
|
||||||
|
|
||||||
const map = new Map();
|
|
||||||
// Possible AWS Condition keys (http://docs.aws.amazon.com/IAM/latest/
|
// Possible AWS Condition keys (http://docs.aws.amazon.com/IAM/latest/
|
||||||
// UserGuide/reference_policies_elements.html#AvailableKeys)
|
// UserGuide/reference_policies_elements.html#AvailableKeys)
|
||||||
|
switch (key) {
|
||||||
// aws:CurrentTime – Used for date/time conditions
|
// aws:CurrentTime – Used for date/time conditions
|
||||||
// (see Date Condition Operators).
|
// (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
|
// aws:EpochTime – Used for date/time conditions
|
||||||
// (see Date Condition Operators).
|
// (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
|
// aws:TokenIssueTime – Date/time that temporary security
|
||||||
// credentials were issued (see Date Condition Operators).
|
// credentials were issued (see Date Condition Operators).
|
||||||
// Only present in requests that are signed using temporary security
|
// Only present in requests that are signed using temporary security
|
||||||
// credentials.
|
// credentials.
|
||||||
map.set('aws:TokenIssueTime', requestContext.getTokenIssueTime());
|
case 'aws:TokenIssueTime': return requestContext.getTokenIssueTime();
|
||||||
// aws:MultiFactorAuthPresent – Used to check whether MFA was used
|
// aws:MultiFactorAuthPresent – Used to check whether MFA was used
|
||||||
// (see Boolean Condition Operators).
|
// (see Boolean Condition Operators).
|
||||||
// Note: This key is only present if MFA was used. So, the following
|
// Note: This key is only present if MFA was used. So, the following
|
||||||
|
@ -45,131 +44,132 @@ export const findConditionKey = (
|
||||||
// Instead use:
|
// Instead use:
|
||||||
// "Condition" :
|
// "Condition" :
|
||||||
// { "Null" : { "aws:MultiFactorAuthPresent" : true } }
|
// { "Null" : { "aws:MultiFactorAuthPresent" : true } }
|
||||||
map.set('aws:MultiFactorAuthPresent',
|
case 'aws:MultiFactorAuthPresent': return requestContext.getMultiFactorAuthPresent();
|
||||||
requestContext.getMultiFactorAuthPresent());
|
|
||||||
// aws:MultiFactorAuthAge – Used to check how many seconds since
|
// aws:MultiFactorAuthAge – Used to check how many seconds since
|
||||||
// MFA credentials were issued. If MFA was not used,
|
// MFA credentials were issued. If MFA was not used,
|
||||||
// this key is not present
|
// 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,
|
// aws:principaltype states whether the principal is an account,
|
||||||
// user, federated, or assumed role
|
// user, federated, or assumed role
|
||||||
// Note: Docs for conditions have "PrincipalType" but simulator
|
// Note: Docs for conditions have "PrincipalType" but simulator
|
||||||
// and docs for variables have lowercase
|
// 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
|
// aws:Referer – Used to check who referred the client browser to
|
||||||
// the address the request is being sent to. Only supported by some
|
// the address the request is being sent to. Only supported by some
|
||||||
// services, such as S3. Value comes from the referer header in the
|
// services, such as S3. Value comes from the referer header in the
|
||||||
// HTTPS request made to AWS.
|
// 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
|
// aws:SecureTransport – Used to check whether the request was sent
|
||||||
// using SSL (see Boolean Condition Operators).
|
// using SSL (see Boolean Condition Operators).
|
||||||
map.set('aws:SecureTransport',
|
case 'aws:SecureTransport': return requestContext.getSslEnabled() ? 'true' : 'false';
|
||||||
requestContext.getSslEnabled() ? 'true' : 'false');
|
|
||||||
// aws:SourceArn – Used check the source of the request,
|
// aws:SourceArn – Used check the source of the request,
|
||||||
// using the ARN of the source. N/A here.
|
// 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
|
// aws:SourceIp – Used to check the requester's IP address
|
||||||
// (see IP Address Condition Operators)
|
// (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:SourceVpc – Used to restrict access to a specific
|
||||||
// AWS Virtual Private Cloud. N/A here.
|
// 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
|
// aws:SourceVpce – Used to limit access to a specific VPC endpoint
|
||||||
// N/A here
|
// N/A here
|
||||||
map.set('aws:SourceVpce', undefined);
|
case 'aws:SourceVpce': return undefined;
|
||||||
// aws:UserAgent – Used to check the requester's client app.
|
// aws:UserAgent – Used to check the requester's client app.
|
||||||
// (see String Condition Operators)
|
// (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.
|
// aws:userid – Used to check the requester's unique user ID.
|
||||||
// (see String Condition Operators)
|
// (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.
|
// aws:username – Used to check the requester's friendly user name.
|
||||||
// (see String Condition Operators)
|
// (see String Condition Operators)
|
||||||
map.set('aws:username', requesterInfo.username);
|
case 'aws:username': return requesterInfo.username;
|
||||||
// Possible condition keys for S3:
|
// Possible condition keys for S3:
|
||||||
// s3:x-amz-acl is acl request for bucket or object put request
|
// 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:
|
// s3:x-amz-grant-PERMISSION (where permission can be:
|
||||||
// read, write, read-acp, write-acp or full-control)
|
// read, write, read-acp, write-acp or full-control)
|
||||||
// Value is the value of that header (ex. id of grantee)
|
// Value is the value of that header (ex. id of grantee)
|
||||||
map.set('s3:x-amz-grant-read', headers['x-amz-grant-read']);
|
case 's3:x-amz-grant-read': return headers['x-amz-grant-read'];
|
||||||
map.set('s3:x-amz-grant-write', headers['x-amz-grant-write']);
|
case 's3:x-amz-grant-write': return headers['x-amz-grant-write'];
|
||||||
map.set('s3:x-amz-grant-read-acp', headers['x-amz-grant-read-acp']);
|
case 's3:x-amz-grant-read-acp': return headers['x-amz-grant-read-acp'];
|
||||||
map.set('s3:x-amz-grant-write-acp', headers['x-amz-grant-write-acp']);
|
case 's3:x-amz-grant-write-acp': return 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-full-control': return headers['x-amz-grant-full-control'];
|
||||||
// s3:x-amz-copy-source is x-amz-copy-source header if applicable on
|
// s3:x-amz-copy-source is x-amz-copy-source header if applicable on
|
||||||
// a put object
|
// 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
|
// s3:x-amz-metadata-directive is x-amz-metadata-directive header if
|
||||||
// applicable on a put object copy. Determines whether metadata will
|
// applicable on a put object copy. Determines whether metadata will
|
||||||
// be copied from original object or replaced. Values or "COPY" or
|
// be copied from original object or replaced. Values or "COPY" or
|
||||||
// "REPLACE". Default is "COPY"
|
// "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
|
// s3:x-amz-server-side-encryption -- Used to require that object put
|
||||||
// use server side encryption. Value is the encryption algo such as
|
// use server side encryption. Value is the encryption algo such as
|
||||||
// "AES256"
|
// "AES256"
|
||||||
map.set('s3:x-amz-server-side-encryption',
|
case 's3:x-amz-server-side-encryption': return headers['x-amz-server-side-encryption'];
|
||||||
headers['x-amz-server-side-encryption']);
|
|
||||||
// s3:x-amz-storage-class -- x-amz-storage-class header value
|
// s3:x-amz-storage-class -- x-amz-storage-class header value
|
||||||
// (STANDARD, etc.)
|
// (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
|
// 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
|
// s3:LocationConstraint -- Used to restrict creation of bucket
|
||||||
// in certain region. Only applicable for CreateBucket
|
// 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
|
// 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
|
// 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
|
// 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
|
// s3 auth v4 additional condition keys
|
||||||
// (See http://docs.aws.amazon.com/AmazonS3/latest/API/
|
// (See http://docs.aws.amazon.com/AmazonS3/latest/API/
|
||||||
// bucket-policy-s3-sigv4-conditions.html)
|
// bucket-policy-s3-sigv4-conditions.html)
|
||||||
// s3:signatureversion -- Either "AWS" for v2 or
|
// s3:signatureversion -- Either "AWS" for v2 or
|
||||||
// "AWS4-HMAC-SHA256" for v4
|
// "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",
|
// s3:authType -- Method of authentication: either "REST-HEADER",
|
||||||
// "REST-QUERY-STRING" or "POST"
|
// "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,
|
// s3:signatureAge is the length of time, in milliseconds,
|
||||||
// that a signature is valid in an authenticated request. So,
|
// that a signature is valid in an authenticated request. So,
|
||||||
// can use this to limit the age to less than 7 days
|
// 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"
|
// 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
|
// so can use this in a deny policy to deny any requests that do not
|
||||||
// have a signed payload
|
// 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
|
// s3:ObjLocationConstraint is the location constraint set for an
|
||||||
// object on a PUT request using the "x-amz-meta-scal-location-constraint"
|
// object on a PUT request using the "x-amz-meta-scal-location-constraint"
|
||||||
// header
|
// header
|
||||||
map.set('s3:ObjLocationConstraint',
|
case 's3:ObjLocationConstraint': return headers['x-amz-meta-scal-location-constraint'];
|
||||||
headers['x-amz-meta-scal-location-constraint']);
|
case 'sts:ExternalId': return requestContext.getRequesterExternalId();
|
||||||
map.set('sts:ExternalId', requestContext.getRequesterExternalId());
|
case 'iam:PolicyArn': return requestContext.getPolicyArn();
|
||||||
map.set('iam:PolicyArn', requestContext.getPolicyArn());
|
|
||||||
// s3:ExistingObjectTag - Used to check that existing object tag has
|
// s3:ExistingObjectTag - Used to check that existing object tag has
|
||||||
// specific tag key and value. Extraction of correct tag key is done in CloudServer.
|
// 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,
|
// On first pass of policy evaluation, CloudServer information will not be included,
|
||||||
// so evaluation should be skipped
|
// 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
|
// s3:RequestObjectTag - Used to limit putting object tags to specific
|
||||||
// tag key and value. N/A here.
|
// tag key and value. N/A here.
|
||||||
// Requires information from CloudServer
|
// Requires information from CloudServer
|
||||||
// On first pass of policy evaluation, CloudServer information will not be included,
|
// On first pass of policy evaluation, CloudServer information will not be included,
|
||||||
// so evaluation should be skipped
|
// 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.
|
// s3:RequestObjectTagKeys - Used to limit putting object tags specific tag keys.
|
||||||
// Requires information from CloudServer.
|
// Requires information from CloudServer.
|
||||||
// On first pass of policy evaluation, CloudServer information will not be included,
|
// On first pass of policy evaluation, CloudServer information will not be included,
|
||||||
// so evaluation should be skipped
|
// so evaluation should be skipped
|
||||||
map.set('s3:RequestObjectTagKeys',
|
case 's3:RequestObjectTagKeys':
|
||||||
requestContext.getNeedTagEval() && requestContext.getRequestObjTags()
|
return requestContext.getNeedTagEval() && requestContext.getRequestObjTags()
|
||||||
? getTagKeys(requestContext.getRequestObjTags()!)
|
? getTagKeys(requestContext.getRequestObjTags()!)
|
||||||
: undefined,
|
: undefined;
|
||||||
);
|
default:
|
||||||
return map.get(key);
|
return undefined;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Wildcards are allowed in certain string comparison and arn comparisons
|
// Wildcards are allowed in certain string comparison and arn comparisons
|
||||||
|
@ -229,7 +229,7 @@ function convertToEpochTime(time: string | string[]) {
|
||||||
* reference_policies_elements.html)
|
* reference_policies_elements.html)
|
||||||
* @return true if condition passes and false if not
|
* @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
|
// Policy Validator checks that the condition operator
|
||||||
// is only one of these strings so should not have undefined
|
// is only one of these strings so should not have undefined
|
||||||
// or security issue with object assignment
|
// or security issue with object assignment
|
||||||
|
@ -444,4 +444,4 @@ export const convertConditionOperator = (operator: string): boolean => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return operatorMap[operator];
|
return operatorMap[operator];
|
||||||
};
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
},
|
},
|
||||||
"version": "7.10.29-1",
|
"version": "7.10.29-4",
|
||||||
"description": "Common utilities for the S3 project components",
|
"description": "Common utilities for the S3 project components",
|
||||||
"main": "build/index.js",
|
"main": "build/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -62,7 +62,6 @@
|
||||||
"@types/jest": "^27.4.1",
|
"@types/jest": "^27.4.1",
|
||||||
"@types/node": "^17.0.21",
|
"@types/node": "^17.0.21",
|
||||||
"@types/xml2js": "^0.4.11",
|
"@types/xml2js": "^0.4.11",
|
||||||
"chance": "^1.1.8",
|
|
||||||
"eslint": "^8.12.0",
|
"eslint": "^8.12.0",
|
||||||
"eslint-config-airbnb": "6.2.0",
|
"eslint-config-airbnb": "6.2.0",
|
||||||
"eslint-config-scality": "scality/Guidelines#7.10.2",
|
"eslint-config-scality": "scality/Guidelines#7.10.2",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict'; // eslint-disable-line strict
|
'use strict'; // eslint-disable-line strict
|
||||||
|
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const chance = require('chance').Chance(); // eslint-disable-line
|
|
||||||
const DelimiterMaster =
|
const DelimiterMaster =
|
||||||
require('../../../../lib/algos/list/delimiterMaster').DelimiterMaster;
|
require('../../../../lib/algos/list/delimiterMaster').DelimiterMaster;
|
||||||
const {
|
const {
|
||||||
|
@ -16,6 +16,8 @@ const Version = require('../../../../lib/versioning/Version').Version;
|
||||||
const { generateVersionId } = require('../../../../lib/versioning/VersionID');
|
const { generateVersionId } = require('../../../../lib/versioning/VersionID');
|
||||||
const { DbPrefixes } = VSConst;
|
const { DbPrefixes } = VSConst;
|
||||||
const zpad = require('../../helpers').zpad;
|
const zpad = require('../../helpers').zpad;
|
||||||
|
|
||||||
|
|
||||||
const VID_SEP = VSConst.VersionId.Separator;
|
const VID_SEP = VSConst.VersionId.Separator;
|
||||||
const EmptyResult = {
|
const EmptyResult = {
|
||||||
CommonPrefixes: [],
|
CommonPrefixes: [],
|
||||||
|
@ -486,336 +488,6 @@ function getListingKey(key, vFormat) {
|
||||||
// ...it should return to skipping by prefix as usual
|
// ...it should return to skipping by prefix as usual
|
||||||
assert.strictEqual(delimiter.skipping(), `${inc(DbPrefixes.Replay)}foo/`);
|
assert.strictEqual(delimiter.skipping(), `${inc(DbPrefixes.Replay)}foo/`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not skip over whole prefix when a key equals the prefix and ends with delimiter', () => {
|
|
||||||
for (const prefix of ['prefix/', 'prefix/subprefix/']) {
|
|
||||||
const delimiter = new DelimiterMaster({
|
|
||||||
prefix,
|
|
||||||
delimiter: '/',
|
|
||||||
}, fakeLogger, vFormat);
|
|
||||||
for (const testEntry of [
|
|
||||||
{
|
|
||||||
key: prefix,
|
|
||||||
expectedRes: FILTER_ACCEPT,
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}${VID_SEP}v1`,
|
|
||||||
value: '{}',
|
|
||||||
expectedRes: FILTER_SKIP, // versions get skipped after master
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}deleted`,
|
|
||||||
isDeleteMarker: true,
|
|
||||||
expectedRes: FILTER_SKIP, // delete markers get skipped
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}deleted${VID_SEP}v1`,
|
|
||||||
isDeleteMarker: true,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}deleted${VID_SEP}v2`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}notdeleted`,
|
|
||||||
expectedRes: FILTER_ACCEPT,
|
|
||||||
expectedSkipping: `${prefix}notdeleted${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}notdeleted${VID_SEP}v1`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}notdeleted${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}subprefix1/key-1`,
|
|
||||||
expectedRes: FILTER_ACCEPT,
|
|
||||||
expectedSkipping: `${prefix}subprefix1/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}subprefix1/key-1${VID_SEP}v1`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}subprefix1/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}subprefix1/key-2`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}subprefix1/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}subprefix1/key-2${VID_SEP}v1`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}subprefix1/`,
|
|
||||||
},
|
|
||||||
]) {
|
|
||||||
const entry = {
|
|
||||||
key: testEntry.key,
|
|
||||||
};
|
|
||||||
if (testEntry.isDeleteMarker) {
|
|
||||||
entry.value = '{"isDeleteMarker":true}';
|
|
||||||
} else {
|
|
||||||
entry.value = '{}';
|
|
||||||
}
|
|
||||||
const res = delimiter.filter(entry);
|
|
||||||
const skipping = delimiter.skipping();
|
|
||||||
assert.strictEqual(res, testEntry.expectedRes);
|
|
||||||
assert.strictEqual(skipping, testEntry.expectedSkipping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should skip over whole prefix when a key equals the prefix and does not end with delimiter', () => {
|
|
||||||
for (const prefix of ['prefix', 'prefix/subprefix']) {
|
|
||||||
const delimiter = new DelimiterMaster({
|
|
||||||
prefix,
|
|
||||||
delimiter: '/',
|
|
||||||
}, fakeLogger, vFormat);
|
|
||||||
for (const testEntry of [
|
|
||||||
{
|
|
||||||
key: prefix,
|
|
||||||
expectedRes: FILTER_ACCEPT,
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}${VID_SEP}v1`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}/`,
|
|
||||||
expectedRes: FILTER_ACCEPT,
|
|
||||||
expectedSkipping: `${prefix}/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}/${VID_SEP}v1`,
|
|
||||||
value: '{}',
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}/`, // common prefix already seen
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}/deleted`,
|
|
||||||
isDeleteMarker: true, // skipped delete marker
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}/`, // already added to common prefix
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}/notdeleted`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}/`, // already added to common prefix
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}ed`,
|
|
||||||
isDeleteMarker: false,
|
|
||||||
expectedRes: FILTER_ACCEPT, // new master key seen
|
|
||||||
expectedSkipping: `${prefix}ed${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}ed/`,
|
|
||||||
expectedRes: FILTER_ACCEPT, // new master key ending with prefix
|
|
||||||
expectedSkipping: `${prefix}ed/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}ed/subprefix1/key-1`,
|
|
||||||
expectedRes: FILTER_SKIP, // already have prefixed/ common prefix
|
|
||||||
expectedSkipping: `${prefix}ed/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}ed/subprefix1/key-1${VID_SEP}v1`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}ed/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}ed/subprefix1/key-2`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}ed/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}ed/subprefix1/key-2${VID_SEP}v1`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}ed/`,
|
|
||||||
},
|
|
||||||
]) {
|
|
||||||
const entry = {
|
|
||||||
key: testEntry.key,
|
|
||||||
};
|
|
||||||
if (testEntry.isDeleteMarker) {
|
|
||||||
entry.value = '{"isDeleteMarker":true}';
|
|
||||||
} else {
|
|
||||||
entry.value = '{}';
|
|
||||||
}
|
|
||||||
const res = delimiter.filter(entry);
|
|
||||||
const skipping = delimiter.skipping();
|
|
||||||
assert.strictEqual(res, testEntry.expectedRes);
|
|
||||||
assert.strictEqual(skipping, testEntry.expectedSkipping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not skip over whole prefix when key equals the prefix and prefix key has delete marker ' +
|
|
||||||
'and prefix ends with delimiter', () => {
|
|
||||||
for (const prefix of ['prefix/', 'prefix/subprefix/']) {
|
|
||||||
const delimiter = new DelimiterMaster({
|
|
||||||
prefix,
|
|
||||||
delimiter: '/',
|
|
||||||
}, fakeLogger, vFormat);
|
|
||||||
for (const testEntry of [
|
|
||||||
{
|
|
||||||
key: prefix,
|
|
||||||
isDeleteMarker: true,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}${VID_SEP}v1`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}subprefix-1`,
|
|
||||||
expectedRes: FILTER_ACCEPT,
|
|
||||||
expectedSkipping: `${prefix}subprefix-1${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}subprefix-1/foo`,
|
|
||||||
expectedRes: FILTER_ACCEPT,
|
|
||||||
expectedSkipping: `${prefix}subprefix-1/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}subprefix-1/bar`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}subprefix-1/`, // already added to common prefix
|
|
||||||
},
|
|
||||||
]) {
|
|
||||||
const entry = {
|
|
||||||
key: testEntry.key,
|
|
||||||
};
|
|
||||||
if (testEntry.isDeleteMarker) {
|
|
||||||
entry.value = '{"isDeleteMarker":true}';
|
|
||||||
} else {
|
|
||||||
entry.value = '{}';
|
|
||||||
}
|
|
||||||
const res = delimiter.filter(entry);
|
|
||||||
const skipping = delimiter.skipping();
|
|
||||||
assert.strictEqual(res, testEntry.expectedRes);
|
|
||||||
assert.strictEqual(skipping, testEntry.expectedSkipping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should skip over whole prefix when key equals the prefix, prefix key has delete marker ' +
|
|
||||||
'and prefix does not end with delimiter', () => {
|
|
||||||
for (const prefix of ['prefix', 'prefix/subprefix']) {
|
|
||||||
const delimiter = new DelimiterMaster({
|
|
||||||
prefix,
|
|
||||||
delimiter: '/',
|
|
||||||
}, fakeLogger, vFormat);
|
|
||||||
for (const testEntry of [
|
|
||||||
{
|
|
||||||
key: prefix,
|
|
||||||
expectedRes: FILTER_ACCEPT,
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}${VID_SEP}v1`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}/`,
|
|
||||||
isDeleteMarker: true,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}/subprefix-1`,
|
|
||||||
expectedRes: FILTER_ACCEPT,
|
|
||||||
expectedSkipping: `${prefix}/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}/subprefix-1/foo`,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}aa`,
|
|
||||||
expectedRes: FILTER_ACCEPT,
|
|
||||||
expectedSkipping: `${prefix}aa${VID_SEP}`, // already added to common prefix
|
|
||||||
},
|
|
||||||
]) {
|
|
||||||
const entry = {
|
|
||||||
key: testEntry.key,
|
|
||||||
};
|
|
||||||
if (testEntry.isDeleteMarker) {
|
|
||||||
entry.value = '{"isDeleteMarker":true}';
|
|
||||||
} else {
|
|
||||||
entry.value = '{}';
|
|
||||||
}
|
|
||||||
const res = delimiter.filter(entry);
|
|
||||||
const skipping = delimiter.skipping();
|
|
||||||
assert.strictEqual(res, testEntry.expectedRes);
|
|
||||||
assert.strictEqual(skipping, testEntry.expectedSkipping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to skip subprefixes within deleteMarker keys when a prefix key ' +
|
|
||||||
'ending with delimiter is seen', () => {
|
|
||||||
for (const prefix of ['prefix/', 'prefix/subprefix/']) {
|
|
||||||
const delimiter = new DelimiterMaster({
|
|
||||||
prefix,
|
|
||||||
delimiter: '/',
|
|
||||||
}, fakeLogger, vFormat);
|
|
||||||
for (const testEntry of [
|
|
||||||
{
|
|
||||||
key: prefix,
|
|
||||||
expectedRes: FILTER_ACCEPT,
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}${VID_SEP}v1`,
|
|
||||||
// isDeleteMarker: true,
|
|
||||||
expectedRes: FILTER_SKIP, // versions get skipped after master
|
|
||||||
expectedSkipping: `${prefix}${VID_SEP}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}foo/`,
|
|
||||||
isDeleteMarker: true,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}foo/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}foo/1`,
|
|
||||||
isDeleteMarker: true,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}foo/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${prefix}foo/2`,
|
|
||||||
isDeleteMarker: true,
|
|
||||||
expectedRes: FILTER_SKIP,
|
|
||||||
expectedSkipping: `${prefix}foo/`,
|
|
||||||
},
|
|
||||||
]) {
|
|
||||||
const entry = {
|
|
||||||
key: testEntry.key,
|
|
||||||
};
|
|
||||||
if (testEntry.isDeleteMarker) {
|
|
||||||
entry.value = '{"isDeleteMarker":true}';
|
|
||||||
} else {
|
|
||||||
entry.value = '{}';
|
|
||||||
}
|
|
||||||
const res = delimiter.filter(entry);
|
|
||||||
const skipping = delimiter.skipping();
|
|
||||||
assert.strictEqual(res, testEntry.expectedRes);
|
|
||||||
assert.strictEqual(skipping, testEntry.expectedSkipping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1162,30 +1162,6 @@ describe('policyEvaluator', () => {
|
||||||
check(requestContext, rcModifiers, policy, 'Neutral');
|
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', () => {
|
it('should allow with ForAnyValue prefix if meet condition', () => {
|
||||||
policy.Statement.Condition = {
|
policy.Statement.Condition = {
|
||||||
'ForAnyValue:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
|
'ForAnyValue:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
|
||||||
|
@ -1208,7 +1184,7 @@ describe('policyEvaluator', () => {
|
||||||
check(requestContext, rcModifiers, policy, 'Allow');
|
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 = {
|
policy.Statement.Condition = {
|
||||||
'ForAnyValue:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
|
'ForAnyValue:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
|
||||||
};
|
};
|
||||||
|
@ -1219,12 +1195,12 @@ describe('policyEvaluator', () => {
|
||||||
check(requestContext, rcModifiers, policy, 'Neutral');
|
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 = {
|
policy.Statement.Condition = {
|
||||||
'ForAllValues:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
|
'ForAllValues:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
|
||||||
};
|
};
|
||||||
const rcModifiers = {
|
const rcModifiers = {
|
||||||
_requestObjTags: 'tagThree=keyThree&tagFour=keyFour',
|
_requestObjTags: 'tagOne=keyOne&tagThree=keyThree',
|
||||||
_needTagEval: true,
|
_needTagEval: true,
|
||||||
};
|
};
|
||||||
check(requestContext, rcModifiers, policy, 'Neutral');
|
check(requestContext, rcModifiers, policy, 'Neutral');
|
||||||
|
@ -1241,6 +1217,203 @@ describe('policyEvaluator', () => {
|
||||||
check(requestContext, rcModifiers, policy, 'Neutral');
|
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', () => {
|
describe('evaluate multiple policies', () => {
|
||||||
|
@ -1266,17 +1439,152 @@ describe('policyEvaluator', () => {
|
||||||
assert.strictEqual(result, 'Deny');
|
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({}, {},
|
requestContext = new RequestContext({}, {},
|
||||||
'notbucket', undefined,
|
'my_favorite_bucket', undefined,
|
||||||
undefined, undefined, 'objectGet', 's3');
|
undefined, undefined, 'objectGet', 's3');
|
||||||
requestContext.setRequesterInfo({});
|
requestContext.setRequesterInfo({});
|
||||||
const result = evaluateAllPolicies(requestContext,
|
const result = evaluateAllPolicies(
|
||||||
[samples['Multi-Statement Policy'],
|
requestContext,
|
||||||
samples['Variable Bucket Policy']], log);
|
testCase.policiesToEvaluate.map(policyName => TestMatrixPolicies[policyName]),
|
||||||
assert.strictEqual(result, 'Deny');
|
log);
|
||||||
|
assert.strictEqual(result, testCase.expectedPolicyEvaluation);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
|
@ -2144,11 +2144,6 @@ chalk@^4.0.0:
|
||||||
ansi-styles "^4.1.0"
|
ansi-styles "^4.1.0"
|
||||||
supports-color "^7.1.0"
|
supports-color "^7.1.0"
|
||||||
|
|
||||||
chance@^1.1.8:
|
|
||||||
version "1.1.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/chance/-/chance-1.1.8.tgz#5d6c2b78c9170bf6eb9df7acdda04363085be909"
|
|
||||||
integrity sha512-v7fi5Hj2VbR6dJEGRWLmJBA83LJMS47pkAbmROFxHWd9qmE1esHRZW8Clf1Fhzr3rjxnNZVCjOEv/ivFxeIMtg==
|
|
||||||
|
|
||||||
char-regex@^1.0.2:
|
char-regex@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
|
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
|
||||||
|
|
Loading…
Reference in New Issue