Compare commits
4 Commits
developmen
...
feature/S3
Author | SHA1 | Date |
---|---|---|
Dora Korpar | 0632028ea3 | |
Dora Korpar | 65e5fe33a4 | |
Dora Korpar | fae2163c16 | |
Dora Korpar | 129f74d558 |
|
@ -234,7 +234,7 @@ class RequestContext {
|
||||||
requesterIp, sslEnabled, apiMethod,
|
requesterIp, sslEnabled, apiMethod,
|
||||||
awsService, locationConstraint, requesterInfo,
|
awsService, locationConstraint, requesterInfo,
|
||||||
signatureVersion, authType, signatureAge, securityToken, policyArn,
|
signatureVersion, authType, signatureAge, securityToken, policyArn,
|
||||||
action) {
|
action, postXml) {
|
||||||
this._headers = headers;
|
this._headers = headers;
|
||||||
this._query = query;
|
this._query = query;
|
||||||
this._requesterIp = requesterIp;
|
this._requesterIp = requesterIp;
|
||||||
|
@ -263,6 +263,10 @@ 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 = null;
|
||||||
|
this._existingObjTag = null;
|
||||||
|
this._needTagEval = false;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,6 +295,10 @@ 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,
|
||||||
|
existingObjTag: this._existingObjTag,
|
||||||
|
needTagEval: this._needTagEval,
|
||||||
};
|
};
|
||||||
return JSON.stringify(requestInfo);
|
return JSON.stringify(requestInfo);
|
||||||
}
|
}
|
||||||
|
@ -316,7 +324,7 @@ class RequestContext {
|
||||||
obj.apiMethod, obj.awsService, obj.locationConstraint,
|
obj.apiMethod, obj.awsService, obj.locationConstraint,
|
||||||
obj.requesterInfo, obj.signatureVersion,
|
obj.requesterInfo, obj.signatureVersion,
|
||||||
obj.authType, obj.signatureAge, obj.securityToken, obj.policyArn,
|
obj.authType, obj.signatureAge, obj.securityToken, obj.policyArn,
|
||||||
obj.action);
|
obj.action, obj.postXml);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -659,6 +667,86 @@ class RequestContext {
|
||||||
isQuotaCheckNeeded() {
|
isQuotaCheckNeeded() {
|
||||||
return this._needQuota;
|
return this._needQuota;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set request post
|
||||||
|
*
|
||||||
|
* @param {string} postXml - request post
|
||||||
|
* @return {RequestContext} itself
|
||||||
|
*/
|
||||||
|
setPostXml(postXml) {
|
||||||
|
this._postXml = postXml;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get request post
|
||||||
|
*
|
||||||
|
* @return {string} request post
|
||||||
|
*/
|
||||||
|
getPostXml() {
|
||||||
|
return this._postXml;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set request object tags
|
||||||
|
*
|
||||||
|
* @param {string} requestObjTags - object tag(s) included in request in query string form
|
||||||
|
* @return {RequestContext} itself
|
||||||
|
*/
|
||||||
|
setRequestObjTags(requestObjTags) {
|
||||||
|
this._requestObjTags = requestObjTags;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get request object tags
|
||||||
|
*
|
||||||
|
* @return {string} request object tag(s)
|
||||||
|
*/
|
||||||
|
getRequestObjTags() {
|
||||||
|
return this._requestObjTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set info on existing tag on object included in request
|
||||||
|
*
|
||||||
|
* @param {string} existingObjTag - existing object tag in query string form
|
||||||
|
* @return {RequestContext} itself
|
||||||
|
*/
|
||||||
|
setExistingObjTag(existingObjTag) {
|
||||||
|
this._existingObjTag = existingObjTag;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get existing object tag
|
||||||
|
*
|
||||||
|
* @return {string} existing object tag
|
||||||
|
*/
|
||||||
|
getExistingObjTag() {
|
||||||
|
return this._existingObjTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether IAM policy tag condition keys should be evaluated
|
||||||
|
*
|
||||||
|
* @param {boolean} needTagEval - whether to evaluate tags
|
||||||
|
* @return {RequestContext} itself
|
||||||
|
*/
|
||||||
|
setNeedTagEval(needTagEval) {
|
||||||
|
this._needTagEval = needTagEval;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get needTagEval param
|
||||||
|
*
|
||||||
|
* @return {boolean} needTagEval - whether IAM policy tags condition keys should be evaluated
|
||||||
|
*/
|
||||||
|
getNeedTagEval() {
|
||||||
|
return this._needTagEval;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = RequestContext;
|
module.exports = RequestContext;
|
||||||
|
|
|
@ -6,6 +6,7 @@ const conditions = require('./utils/conditions.js');
|
||||||
const findConditionKey = conditions.findConditionKey;
|
const findConditionKey = conditions.findConditionKey;
|
||||||
const convertConditionOperator = conditions.convertConditionOperator;
|
const convertConditionOperator = conditions.convertConditionOperator;
|
||||||
const checkArnMatch = require('./utils/checkArnMatch.js');
|
const checkArnMatch = require('./utils/checkArnMatch.js');
|
||||||
|
const { transformTagKeyValue } = require('./utils/objectTags');
|
||||||
|
|
||||||
const evaluators = {};
|
const evaluators = {};
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ const operatorsWithVariables = ['StringEquals', 'StringNotEquals',
|
||||||
const operatorsWithNegation = ['StringNotEquals',
|
const operatorsWithNegation = ['StringNotEquals',
|
||||||
'StringNotEqualsIgnoreCase', 'StringNotLike', 'ArnNotEquals',
|
'StringNotEqualsIgnoreCase', 'StringNotLike', 'ArnNotEquals',
|
||||||
'ArnNotLike', 'NumericNotEquals'];
|
'ArnNotLike', 'NumericNotEquals'];
|
||||||
|
const tagConditions = ['s3:ExistingObjectTag', 's3:RequestObjectTagKey', 's3:RequestObjectTagKeys'];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -96,20 +98,27 @@ evaluators.isActionApplicable = (requestAction, statementAction, log) => {
|
||||||
* @param {RequestContext} requestContext - info about request
|
* @param {RequestContext} requestContext - info about request
|
||||||
* @param {Object} statementCondition - Condition statement from policy
|
* @param {Object} statementCondition - Condition statement from policy
|
||||||
* @param {Object} log - logger
|
* @param {Object} log - logger
|
||||||
* @return {boolean} true if meet conditions, false if not
|
* @return {Object} contains whether conditions are allowed and whether they
|
||||||
|
* contain any tag condition keys
|
||||||
*/
|
*/
|
||||||
evaluators.meetConditions = (requestContext, statementCondition, log) => {
|
evaluators.meetConditions = (requestContext, statementCondition, log) => {
|
||||||
// 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 = {};
|
||||||
const operators = Object.keys(statementCondition);
|
const operators = Object.keys(statementCondition);
|
||||||
const length = operators.length;
|
const length = operators.length;
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
const operator = operators[i];
|
const operator = operators[i];
|
||||||
|
const hasPrefix = operator.startsWith('ForAnyValue') || operator.startsWith('ForAllValues');
|
||||||
const hasIfExistsCondition = operator.endsWith('IfExists');
|
const hasIfExistsCondition = operator.endsWith('IfExists');
|
||||||
// If has "IfExists" added to operator name, find operator name
|
// If has "IfExists" added to operator name, or operator has "ForAnyValue" or
|
||||||
// without "IfExists"
|
// "For All Values" prefix, find operator name without "IfExists" or prefix
|
||||||
const bareOperator = hasIfExistsCondition ? operator.slice(0, -8) :
|
let bareOperator = hasIfExistsCondition ? operator.slice(0, -8) :
|
||||||
operator;
|
operator;
|
||||||
|
let prefix;
|
||||||
|
if (hasPrefix) {
|
||||||
|
[prefix, bareOperator] = bareOperator.split(':');
|
||||||
|
}
|
||||||
const operatorCanHaveVariables =
|
const operatorCanHaveVariables =
|
||||||
operatorsWithVariables.indexOf(bareOperator) > -1;
|
operatorsWithVariables.indexOf(bareOperator) > -1;
|
||||||
const isNegationOperator =
|
const isNegationOperator =
|
||||||
|
@ -118,6 +127,9 @@ evaluators.meetConditions = (requestContext, statementCondition, log) => {
|
||||||
// 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.includes(key))) {
|
||||||
|
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];
|
||||||
|
@ -130,14 +142,18 @@ evaluators.meetConditions = (requestContext, statementCondition, log) => {
|
||||||
value = value.map(item =>
|
value = value.map(item =>
|
||||||
substituteVariables(item, requestContext));
|
substituteVariables(item, requestContext));
|
||||||
}
|
}
|
||||||
|
// if condition key is RequestObjectTag or ExistingObjectTag,
|
||||||
|
// 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);
|
||||||
// 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
|
||||||
// condition has "ForAnyValue" or "ForAllValues".
|
// condition has "ForAnyValue" or "ForAllValues".
|
||||||
// (see http://docs.aws.amazon.com/IAM/latest/UserGuide/
|
// (see http://docs.aws.amazon.com/IAM/latest/UserGuide/
|
||||||
// reference_policies_multi-value-conditions.html)
|
// reference_policies_multi-value-conditions.html)
|
||||||
const keyBasedOnRequestContext =
|
let keyBasedOnRequestContext =
|
||||||
findConditionKey(key, requestContext);
|
findConditionKey(transformedKey, requestContext);
|
||||||
// Handle IfExists and negation operators
|
// Handle IfExists and negation operators
|
||||||
if ((keyBasedOnRequestContext === undefined ||
|
if ((keyBasedOnRequestContext === undefined ||
|
||||||
keyBasedOnRequestContext === null) &&
|
keyBasedOnRequestContext === null) &&
|
||||||
|
@ -154,22 +170,27 @@ evaluators.meetConditions = (requestContext, statementCondition, log) => {
|
||||||
bareOperator !== 'Null') {
|
bareOperator !== 'Null') {
|
||||||
log.trace('condition not satisfied due to ' +
|
log.trace('condition not satisfied due to ' +
|
||||||
'missing info', { operator,
|
'missing info', { operator,
|
||||||
conditionKey: key, policyValue: value });
|
conditionKey: transformedKey, policyValue: transformedValue });
|
||||||
return false;
|
return { allow: false };
|
||||||
|
}
|
||||||
|
// If condition operator prefix is included, the key should be an array
|
||||||
|
if (prefix && !Array.isArray(keyBasedOnRequestContext)) {
|
||||||
|
keyBasedOnRequestContext = [keyBasedOnRequestContext];
|
||||||
}
|
}
|
||||||
// Transalate operator into function using bareOperator
|
// Transalate operator into function using bareOperator
|
||||||
const operatorFunction = convertConditionOperator(bareOperator);
|
const operatorFunction = convertConditionOperator(bareOperator);
|
||||||
// Note: Wildcards are handled in the comparison operator function
|
// Note: Wildcards are handled in the comparison operator function
|
||||||
// itself since StringLike, StringNotLike, ArnLike and ArnNotLike
|
// itself since StringLike, StringNotLike, ArnLike and ArnNotLike
|
||||||
// are the only operators where wildcards are allowed
|
// are the only operators where wildcards are allowed
|
||||||
if (!operatorFunction(keyBasedOnRequestContext, value)) {
|
if (!operatorFunction(keyBasedOnRequestContext, transformedValue, prefix)) {
|
||||||
log.trace('did not satisfy condition', { operator: bareOperator,
|
log.trace('did not satisfy condition', { operator: bareOperator,
|
||||||
keyBasedOnRequestContext, policyValue: value });
|
keyBasedOnRequestContext, policyValue: transformedValue });
|
||||||
return false;
|
return { allow: false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
conditionEval.allow = true;
|
||||||
|
return conditionEval;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -220,10 +241,10 @@ evaluators.evaluatePolicy = (requestContext, policy, log) => {
|
||||||
currentStatement.NotAction, log)) {
|
currentStatement.NotAction, log)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const conditionEval = evaluators.meetConditions(requestContext,
|
||||||
|
currentStatement.Condition, log);
|
||||||
// If do not meet conditions move on to next statement
|
// If do not meet conditions move on to next statement
|
||||||
if (currentStatement.Condition &&
|
if (currentStatement.Condition && !conditionEval.allow) {
|
||||||
!evaluators.meetConditions(requestContext,
|
|
||||||
currentStatement.Condition, log)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (currentStatement.Effect === 'Deny') {
|
if (currentStatement.Effect === 'Deny') {
|
||||||
|
@ -235,6 +256,9 @@ evaluators.evaluatePolicy = (requestContext, policy, log) => {
|
||||||
// If statement is applicable, conditions are met and Effect is
|
// If statement is applicable, conditions are met and Effect is
|
||||||
// to Allow, set verdict to Allow
|
// to Allow, set verdict to Allow
|
||||||
verdict = 'Allow';
|
verdict = 'Allow';
|
||||||
|
if (conditionEval.tagConditions) {
|
||||||
|
verdict = 'NeedTagConditionEval';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.trace('result of evaluating single policy', { verdict });
|
log.trace('result of evaluating single policy', { verdict });
|
||||||
return verdict;
|
return verdict;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
const checkIPinRangeOrMatch = require('../../ipCheck').checkIPinRangeOrMatch;
|
const checkIPinRangeOrMatch = require('../../ipCheck').checkIPinRangeOrMatch;
|
||||||
const handleWildcards = require('./wildcards.js').handleWildcards;
|
const handleWildcards = require('./wildcards.js').handleWildcards;
|
||||||
const checkArnMatch = require('./checkArnMatch.js');
|
const checkArnMatch = require('./checkArnMatch.js');
|
||||||
|
const { getTagKeys } = require('./objectTags');
|
||||||
const conditions = {};
|
const conditions = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,6 +30,11 @@ conditions.findConditionKey = (key, requestContext) => {
|
||||||
// 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());
|
map.set('aws:EpochTime', Date.now().toString());
|
||||||
|
// aws: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('aws:ExistingObjectTag', requestContext.getNeedTagEval() ? requestContext.getExistingObjTag() : undefined);
|
||||||
// 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
|
||||||
|
@ -59,6 +65,18 @@ conditions.findConditionKey = (key, requestContext) => {
|
||||||
// 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);
|
map.set('aws:referer', headers.referer);
|
||||||
|
// aws: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('aws:RequestObjectTag', requestContext.getNeedTagEval() ? requestContext.getRequestObjTags() : undefined);
|
||||||
|
// aws: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('aws:RequestObjectTagKeys',
|
||||||
|
requestContext.getNeedTagEval() ? getTagKeys(requestContext.getRequestObjTags()) : undefined);
|
||||||
// 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',
|
map.set('aws:SecureTransport',
|
||||||
|
@ -232,12 +250,21 @@ conditions.convertConditionOperator = operator => {
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
return !operatorMap.StringEqualsIgnoreCase(key, value);
|
return !operatorMap.StringEqualsIgnoreCase(key, value);
|
||||||
},
|
},
|
||||||
StringLike: function stringLike(key, value) {
|
StringLike: function stringLike(key, value, prefix) {
|
||||||
return value.some(item => {
|
function policyValRegex(testKey) {
|
||||||
const wildItem = handleWildcards(item);
|
return value.some(item => {
|
||||||
const wildRegEx = new RegExp(wildItem);
|
const wildItem = handleWildcards(item);
|
||||||
return wildRegEx.test(key);
|
const wildRegEx = new RegExp(wildItem);
|
||||||
});
|
return wildRegEx.test(testKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (prefix === 'ForAnyValue') {
|
||||||
|
return key.some(k => policyValRegex(k));
|
||||||
|
}
|
||||||
|
if (prefix === 'ForAllValues') {
|
||||||
|
return key.every(k => policyValRegex(k));
|
||||||
|
}
|
||||||
|
return policyValRegex(key);
|
||||||
},
|
},
|
||||||
StringNotLike: function stringNotLike(key, value) {
|
StringNotLike: function stringNotLike(key, value) {
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* Removes tag key value from condition key and adds it to value if needed
|
||||||
|
* @param {string} key - condition key
|
||||||
|
* @param {string} value - condition value
|
||||||
|
* @return {array} key/value pair to use
|
||||||
|
*/
|
||||||
|
function transformTagKeyValue(key, value) {
|
||||||
|
if (!key.includes('/')) {
|
||||||
|
return [key, value];
|
||||||
|
}
|
||||||
|
// if key is RequestObjectTag or ExistingObjectTag,
|
||||||
|
// remove tag key from condition key and add to value
|
||||||
|
// and transform value into query string
|
||||||
|
const [conditionKey, tagKey] = key.split('/');
|
||||||
|
const transformedValue = [tagKey, value].join('=');
|
||||||
|
return [conditionKey, [transformedValue]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets array of tag key names from request tag query string
|
||||||
|
* @param {string} tagQuery - request tags in query string format
|
||||||
|
* @return {array} array of tag key names
|
||||||
|
*/
|
||||||
|
function getTagKeys(tagQuery) {
|
||||||
|
const tagsArray = tagQuery.split(',');
|
||||||
|
const keysArray = tagsArray.map(tag => tag.split('=')[0]);
|
||||||
|
return keysArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
transformTagKeyValue,
|
||||||
|
getTagKeys,
|
||||||
|
};
|
Loading…
Reference in New Issue