@@ -13,7 +13,11 @@ const operatorsWithVariables = ['StringEquals', 'StringNotEquals',
13
13
const operatorsWithNegation = [ 'StringNotEquals' ,
14
14
'StringNotEqualsIgnoreCase' , 'StringNotLike' , 'ArnNotEquals' ,
15
15
'ArnNotLike' , 'NumericNotEquals' ] ;
16
- const tagConditions = new Set ( [ 's3:ExistingObjectTag' , 's3:RequestObjectTagKey' , 's3:RequestObjectTagKeys' ] ) ;
16
+ const tagConditions = new Set ( [
17
+ 's3:ExistingObjectTag' ,
18
+ 's3:RequestObjectTagKey' ,
19
+ 's3:RequestObjectTagKeys' ,
20
+ ] ) ;
17
21
18
22
19
23
/**
@@ -24,11 +28,11 @@ const tagConditions = new Set(['s3:ExistingObjectTag', 's3:RequestObjectTagKey',
24
28
* @param log - logger
25
29
* @return true if applicable, false if not
26
30
*/
27
- export const isResourceApplicable = (
31
+ export function isResourceApplicable (
28
32
requestContext : RequestContext ,
29
33
statementResource : string | string [ ] ,
30
34
log : Logger ,
31
- ) : boolean => {
35
+ ) : boolean {
32
36
const resource = requestContext . getResource ( ) ;
33
37
if ( ! Array . isArray ( statementResource ) ) {
34
38
// eslint-disable-next-line no-param-reassign
@@ -59,7 +63,7 @@ export const isResourceApplicable = (
59
63
{ requestResource : resource } ) ;
60
64
// If no match found, no resource is applicable
61
65
return false ;
62
- } ;
66
+ }
63
67
64
68
/**
65
69
* Check whether action in policy statement applies to request
@@ -69,11 +73,11 @@ export const isResourceApplicable = (
69
73
* @param log - logger
70
74
* @return true if applicable, false if not
71
75
*/
72
- export const isActionApplicable = (
76
+ export function isActionApplicable (
73
77
requestAction : string ,
74
78
statementAction : string | string [ ] ,
75
79
log : Logger ,
76
- ) : boolean => {
80
+ ) : boolean {
77
81
if ( ! Array . isArray ( statementAction ) ) {
78
82
// eslint-disable-next-line no-param-reassign
79
83
statementAction = [ statementAction ] ;
@@ -95,28 +99,29 @@ export const isActionApplicable = (
95
99
{ requestAction } ) ;
96
100
// If no match found, return false
97
101
return false ;
98
- } ;
102
+ }
99
103
100
104
/**
101
105
* Check whether request meets policy conditions
102
- * @param requestContext - info about request
103
- * @param statementCondition - Condition statement from policy
104
- * @param log - logger
105
- * @return contains whether conditions are allowed and whether they
106
- * contain any tag condition keys
106
+ * @param {RequestContext } requestContext - info about request
107
+ * @param {object } statementCondition - Condition statement from policy
108
+ * @param {Logger } log - logger
109
+ * @return {boolean|null } a condition evaluation result, one of:
110
+ * - true: condition is met
111
+ * - false: condition is not met
112
+ * - null: condition evaluation requires additional info to be
113
+ * provided (namely, for tag conditions, request tags and/or object
114
+ * tags have to be provided to evaluate the condition)
107
115
*/
108
- export const meetConditions = (
116
+ export function meetConditions (
109
117
requestContext : RequestContext ,
110
118
statementCondition : any ,
111
119
log : Logger ,
112
- ) => {
120
+ ) : boolean | null {
121
+ let hasTagConditions = false ;
113
122
// The Condition portion of a policy is an object with different
114
123
// operators as keys
115
- const conditionEval = { } ;
116
- const operators = Object . keys ( statementCondition ) ;
117
- const length = operators . length ;
118
- for ( let i = 0 ; i < length ; i ++ ) {
119
- const operator = operators [ i ] ;
124
+ for ( const operator of Object . keys ( statementCondition ) ) {
120
125
const hasPrefix = operator . includes ( ':' ) ;
121
126
const hasIfExistsCondition = operator . endsWith ( 'IfExists' ) ;
122
127
// If has "IfExists" added to operator name, or operator has "ForAnyValue" or
@@ -135,10 +140,6 @@ export const meetConditions = (
135
140
// Note: this should be the actual operator name, not the bareOperator
136
141
const conditionsWithSameOperator = statementCondition [ operator ] ;
137
142
const conditionKeys = Object . keys ( conditionsWithSameOperator ) ;
138
- if ( conditionKeys . some ( key => tagConditions . has ( key ) ) && ! requestContext . getNeedTagEval ( ) ) {
139
- // @ts -expect-error
140
- conditionEval . tagConditions = true ;
141
- }
142
143
const conditionKeysLength = conditionKeys . length ;
143
144
for ( let j = 0 ; j < conditionKeysLength ; j ++ ) {
144
145
const key = conditionKeys [ j ] ;
@@ -155,6 +156,10 @@ export const meetConditions = (
155
156
// tag key is included in condition key and needs to be
156
157
// moved to value for evaluation, otherwise key/value are unchanged
157
158
const [ transformedKey , transformedValue ] = transformTagKeyValue ( key , value ) ;
159
+ if ( tagConditions . has ( transformedKey ) && ! requestContext . getNeedTagEval ( ) ) {
160
+ hasTagConditions = true ;
161
+ continue ;
162
+ }
158
163
// Pull key using requestContext
159
164
// TODO: If applicable to S3, handle policy set operations
160
165
// where a keyBasedOnRequestContext returns multiple values and
@@ -180,7 +185,7 @@ export const meetConditions = (
180
185
log . trace ( 'condition not satisfied due to ' +
181
186
'missing info' , { operator,
182
187
conditionKey : transformedKey , policyValue : transformedValue } ) ;
183
- return { allow : false } ;
188
+ return false ;
184
189
}
185
190
// If condition operator prefix is included, the key should be an array
186
191
if ( prefix && ! Array . isArray ( keyBasedOnRequestContext ) ) {
@@ -195,14 +200,16 @@ export const meetConditions = (
195
200
if ( ! operatorFunction ( keyBasedOnRequestContext , transformedValue , prefix ) ) {
196
201
log . trace ( 'did not satisfy condition' , { operator : bareOperator ,
197
202
keyBasedOnRequestContext, policyValue : transformedValue } ) ;
198
- return { allow : false } ;
203
+ return false ;
199
204
}
200
205
}
201
206
}
202
- // @ts -expect-error
203
- conditionEval . allow = true ;
204
- return conditionEval ;
205
- } ;
207
+ // one or more conditions required tag info to be evaluated
208
+ if ( hasTagConditions ) {
209
+ return null ;
210
+ }
211
+ return true ;
212
+ }
206
213
207
214
/**
208
215
* Evaluate whether a request is permitted under a policy.
@@ -215,13 +222,15 @@ export const meetConditions = (
215
222
* @return Allow if permitted, Deny if not permitted or Neutral
216
223
* if not applicable
217
224
*/
218
- export const evaluatePolicy = (
225
+ export function evaluatePolicy (
219
226
requestContext : RequestContext ,
220
227
policy : any ,
221
228
log : Logger ,
222
- ) : string => {
229
+ ) : string {
223
230
// TODO: For bucket policies need to add Principal evaluation
224
- let verdict = 'Neutral' ;
231
+ let allow = false ;
232
+ let allowWithTagCondition = false ;
233
+ let denyWithTagCondition = false ;
225
234
226
235
if ( ! Array . isArray ( policy . Statement ) ) {
227
236
// eslint-disable-next-line no-param-reassign
@@ -258,10 +267,18 @@ export const evaluatePolicy = (
258
267
}
259
268
const conditionEval = currentStatement . Condition ?
260
269
meetConditions ( requestContext , currentStatement . Condition , log ) :
261
- null ;
270
+ true ;
262
271
// If do not meet conditions move on to next statement
263
- // @ts -expect-error
264
- if ( conditionEval && ! conditionEval . allow ) {
272
+ if ( conditionEval === false ) {
273
+ continue ;
274
+ }
275
+ // If condition needs tag info to be evaluated, mark and move on to next statement
276
+ if ( conditionEval === null ) {
277
+ if ( currentStatement . Effect === 'Deny' ) {
278
+ denyWithTagCondition = true ;
279
+ } else {
280
+ allowWithTagCondition = true ;
281
+ }
265
282
continue ;
266
283
}
267
284
if ( currentStatement . Effect === 'Deny' ) {
@@ -270,17 +287,27 @@ export const evaluatePolicy = (
270
287
return 'Deny' ;
271
288
}
272
289
log . trace ( 'Allow statement applies' ) ;
273
- // If statement is applicable, conditions are met and Effect is
274
- // to Allow, set verdict to Allow
290
+ // statement is applicable, conditions are met and Effect is
291
+ // to Allow
292
+ allow = true ;
293
+ }
294
+ let verdict ;
295
+ if ( denyWithTagCondition ) {
296
+ // priority is on checking tags to potentially deny
297
+ verdict = 'DenyWithTagCondition' ;
298
+ } else if ( allow ) {
299
+ // at least one statement is an allow
275
300
verdict = 'Allow' ;
276
- // @ts -expect-error
277
- if ( conditionEval && conditionEval . tagConditions ) {
278
- verdict = 'NeedTagConditionEval' ;
279
- }
301
+ } else if ( allowWithTagCondition ) {
302
+ // all allow statements need tag checks
303
+ verdict = 'AllowWithTagCondition' ;
304
+ } else {
305
+ // no statement matched to allow or deny
306
+ verdict = 'Neutral' ;
280
307
}
281
308
log . trace ( 'result of evaluating single policy' , { verdict } ) ;
282
309
return verdict ;
283
- } ;
310
+ }
284
311
285
312
/**
286
313
* Evaluate whether a request is permitted under a policy.
@@ -293,24 +320,43 @@ export const evaluatePolicy = (
293
320
* @return Allow if permitted, Deny if not permitted.
294
321
* Default is to Deny. Deny overrides an Allow
295
322
*/
296
- export const evaluateAllPolicies = (
323
+ export function evaluateAllPolicies (
297
324
requestContext : RequestContext ,
298
325
allPolicies : any [ ] ,
299
326
log : Logger ,
300
- ) : string => {
327
+ ) : string {
301
328
log . trace ( 'evaluating all policies' ) ;
302
- let verdict = 'Deny' ;
329
+ let allow = false ;
330
+ let allowWithTagCondition = false ;
331
+ let denyWithTagCondition = false ;
303
332
for ( let i = 0 ; i < allPolicies . length ; i ++ ) {
304
- const singlePolicyVerdict =
305
- evaluatePolicy ( requestContext , allPolicies [ i ] , log ) ;
333
+ const singlePolicyVerdict = evaluatePolicy ( requestContext , allPolicies [ i ] , log ) ;
306
334
// If there is any Deny, just return Deny
307
335
if ( singlePolicyVerdict === 'Deny' ) {
308
336
return 'Deny' ;
309
337
}
310
338
if ( singlePolicyVerdict === 'Allow' ) {
339
+ allow = true ;
340
+ } else if ( singlePolicyVerdict === 'AllowWithTagCondition' ) {
341
+ allowWithTagCondition = true ;
342
+ } else if ( singlePolicyVerdict === 'DenyWithTagCondition' ) {
343
+ denyWithTagCondition = true ;
344
+ } // else 'Neutral'
345
+ }
346
+ let verdict ;
347
+ if ( allow ) {
348
+ if ( denyWithTagCondition ) {
349
+ verdict = 'NeedTagConditionEval' ;
350
+ } else {
311
351
verdict = 'Allow' ;
312
352
}
353
+ } else {
354
+ if ( allowWithTagCondition ) {
355
+ verdict = 'NeedTagConditionEval' ;
356
+ } else {
357
+ verdict = 'Deny' ;
358
+ }
313
359
}
314
- log . trace ( 'result of evaluating all pollicies ' , { verdict } ) ;
360
+ log . trace ( 'result of evaluating all policies ' , { verdict } ) ;
315
361
return verdict ;
316
- } ;
362
+ }
0 commit comments