WIP: chain: Introduce new condition operations #93
|
@ -111,6 +111,20 @@ const (
|
||||||
|
|
||||||
CondIPAddress
|
CondIPAddress
|
||||||
CondNotIPAddress
|
CondNotIPAddress
|
||||||
|
|
||||||
|
CondStringEqualsIfExists
|
||||||
|
CondStringEqualsIgnoreCaseIfExists
|
||||||
|
CondStringLikeIfExists
|
||||||
|
CondStringLessThanIfExists
|
||||||
|
CondStringLessThanEqualsIfExists
|
||||||
|
CondStringGreaterThanIfExists
|
||||||
|
CondStringGreaterThanEqualsIfExists
|
||||||
|
|
||||||
|
CondNumericEqualsIfExists
|
||||||
|
CondNumericLessThanIfExists
|
||||||
|
CondNumericLessThanEqualsIfExists
|
||||||
|
CondNumericGreaterThanIfExists
|
||||||
|
CondNumericGreaterThanEqualsIfExists
|
||||||
)
|
)
|
||||||
|
|
||||||
var condToStr = []struct {
|
var condToStr = []struct {
|
||||||
|
@ -127,12 +141,24 @@ var condToStr = []struct {
|
||||||
{CondStringLessThanEquals, "StringLessThanEquals"},
|
{CondStringLessThanEquals, "StringLessThanEquals"},
|
||||||
{CondStringGreaterThan, "StringGreaterThan"},
|
{CondStringGreaterThan, "StringGreaterThan"},
|
||||||
{CondStringGreaterThanEquals, "StringGreaterThanEquals"},
|
{CondStringGreaterThanEquals, "StringGreaterThanEquals"},
|
||||||
|
{CondStringEqualsIfExists, "StringEqualsIfExists"},
|
||||||
|
{CondStringEqualsIgnoreCaseIfExists, "StringEqualsIgnoreCaseIfExists"},
|
||||||
|
{CondStringLikeIfExists, "StringLikeIfExists"},
|
||||||
|
{CondStringLessThanIfExists, "StringLessThanIfExists"},
|
||||||
|
{CondStringLessThanEqualsIfExists, "StringLessThanEqualsIfExists"},
|
||||||
|
{CondStringGreaterThanIfExists, "StringGreaterThanIfExists"},
|
||||||
|
{CondStringGreaterThanEqualsIfExists, "StringGreaterThanEqualsIfExists"},
|
||||||
{CondNumericEquals, "NumericEquals"},
|
{CondNumericEquals, "NumericEquals"},
|
||||||
{CondNumericNotEquals, "NumericNotEquals"},
|
{CondNumericNotEquals, "NumericNotEquals"},
|
||||||
{CondNumericLessThan, "NumericLessThan"},
|
{CondNumericLessThan, "NumericLessThan"},
|
||||||
{CondNumericLessThanEquals, "NumericLessThanEquals"},
|
{CondNumericLessThanEquals, "NumericLessThanEquals"},
|
||||||
{CondNumericGreaterThan, "NumericGreaterThan"},
|
{CondNumericGreaterThan, "NumericGreaterThan"},
|
||||||
{CondNumericGreaterThanEquals, "NumericGreaterThanEquals"},
|
{CondNumericGreaterThanEquals, "NumericGreaterThanEquals"},
|
||||||
|
{CondNumericEqualsIfExists, "NumericEqualsIfExists"},
|
||||||
|
{CondNumericLessThanIfExists, "NumericLessThanIfExists"},
|
||||||
|
{CondNumericLessThanEqualsIfExists, "NumericLessThanEqualsIfExists"},
|
||||||
|
{CondNumericGreaterThanIfExists, "NumericGreaterThanIfExists"},
|
||||||
|
{CondNumericGreaterThanEqualsIfExists, "NumericGreaterThanEqualsIfExists"},
|
||||||
{CondSliceContains, "SliceContains"},
|
{CondSliceContains, "SliceContains"},
|
||||||
{CondIPAddress, "IPAddress"},
|
{CondIPAddress, "IPAddress"},
|
||||||
{CondNotIPAddress, "NotIPAddress"},
|
{CondNotIPAddress, "NotIPAddress"},
|
||||||
|
@ -157,11 +183,12 @@ func FormCondSliceContainsValue(values []string) string {
|
||||||
|
|
||||||
func (c *Condition) Match(req resource.Request) bool {
|
func (c *Condition) Match(req resource.Request) bool {
|
||||||
var val string
|
var val string
|
||||||
|
var exists bool
|
||||||
switch c.Kind {
|
switch c.Kind {
|
||||||
case KindResource:
|
case KindResource:
|
||||||
val = req.Resource().Property(c.Key)
|
val, exists = req.Resource().Property(c.Key)
|
||||||
case KindRequest:
|
case KindRequest:
|
||||||
val = req.Property(c.Key)
|
val, exists = req.Property(c.Key)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unknown condition type: %d", c.Kind))
|
panic(fmt.Sprintf("unknown condition type: %d", c.Kind))
|
||||||
}
|
}
|
||||||
|
@ -170,30 +197,47 @@ func (c *Condition) Match(req resource.Request) bool {
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unimplemented: %d", c.Op))
|
panic(fmt.Sprintf("unimplemented: %d", c.Op))
|
||||||
case CondStringEquals:
|
case CondStringEquals:
|
||||||
return val == c.Value
|
return exists && val == c.Value
|
||||||
|
|||||||
|
case CondStringEqualsIfExists:
|
||||||
|
return !exists || val == c.Value
|
||||||
case CondStringNotEquals:
|
case CondStringNotEquals:
|
||||||
return val != c.Value
|
return exists && val != c.Value
|
||||||
fyrchik
commented
Why have you changed this? Why have you changed this?
aarifullin
commented
The reason why this PR has been created: The [reason](https://git.frostfs.info/TrueCloudLab/frostfs-node/issues/1406#issuecomment-53462) why this PR has been created:
`CondStringNotEquals` works out even key **is not** present (`"" != value -> true) - and we don't want this complicating behavior. If we don't need these changes, then the PR is not needed at all and policy must explicitly specify that a value (got by a key) can't be empty
fyrchik
commented
We need this change for positive conditions, not the negative ones. We need this change for positive conditions, not the negative ones.
As you can see below, negative conditions are handled differently.
aarifullin
commented
Oh, sorry! I misunderstood this - have been keeping the idea about Oh, sorry! I misunderstood this - have been keeping the idea about `...IfExists` operators in my mind
|
|||||||
case CondStringEqualsIgnoreCase:
|
case CondStringEqualsIgnoreCase:
|
||||||
return strings.EqualFold(val, c.Value)
|
return exists && strings.EqualFold(val, c.Value)
|
||||||
|
case CondStringEqualsIgnoreCaseIfExists:
|
||||||
|
return !exists || strings.EqualFold(val, c.Value)
|
||||||
case CondStringNotEqualsIgnoreCase:
|
case CondStringNotEqualsIgnoreCase:
|
||||||
return !strings.EqualFold(val, c.Value)
|
return exists && !strings.EqualFold(val, c.Value)
|
||||||
case CondStringLike:
|
case CondStringLike:
|
||||||
return util.GlobMatch(val, c.Value)
|
return exists && util.GlobMatch(val, c.Value)
|
||||||
|
case CondStringLikeIfExists:
|
||||||
|
return !exists || util.GlobMatch(val, c.Value)
|
||||||
fyrchik
commented
@alexvanin @dkirillov could you recheck this bit? I am baffled by this sentence from https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_IfExists:
It seems we behave differently. What am I missing? @alexvanin @dkirillov could you recheck this bit?
I am baffled by this sentence from https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_IfExists:
> If you are using an "Effect": "Deny" element with a negated condition operator like StringNotEqualsIfExists, the request is still denied even if the condition key is not present.
It seems we behave differently. What am I missing?
aarifullin
commented
In current implementation:
Anyway, the current implementation fits to this requirement even for
BTW, this is a consequence of this statement ```json
{
"Effect": "Deny",
"Action": "s3:PutObject",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "AES256"
}
}
}
```
In current implementation:
1. If the property `s3:x-amz-server-side-encryption` is _not_ set, then `val_found && StringNotEquals(val, "s3:x-amz-server-side-encryption") -> false` that seems fair;
2. If the property `s3:x-amz-server-side-encryption` is set, then `val_found && StringNotEquals(val, "s3:x-amz-server-side-encryption") -> true/false` that seems fair;
3. If the property `s3:x-amz-server-side-encryption` is **not** set, then `StringNotEqualsIfExists(...) -> !val_found || StringNotEquals(val, "s3:x-amz-server-side-encryption") -> true`
4. If the property `s3:x-amz-server-side-encryption` is set, then `StringNotEqualsIfExists(...) -> !val_found || StringNotEquals(val, "s3:x-amz-server-side-encryption") -> true/false`. If `"s3:x-amz-server-side-encryption"` is set to `""`, then it returns `true`
Anyway, the current implementation fits to this requirement even for `StringNotEquals`:
```
If you are using an "Effect": "Deny" element with a negated condition operator like StringNotEqualsIfExists, the request is still denied even if the condition key is not present.
```
BTW, this is a consequence of this statement `IfExists" suffix specifies the following: if the condition key is present in the context of the request, process the key as specified in the policy. If the key is not present, evaluate the condition element as true`
dkirillov
commented
I'm not sure. The behavior being changed in this PR. Currently such condition evaluates to We shouldn't change this behavior I suppose. Moreover AWS behaves the same way Using this policy
I can put object only if provide tag > If the property s3:x-amz-server-side-encryption is not set, then val_found && StringNotEquals(val, "s3:x-amz-server-side-encryption") -> false that seems fair;
I'm not sure. The behavior being changed in this PR. Currently such condition evaluates to `true` because missing key has "" (empty) value that doesn't equal to "AES256" (in example)
We shouldn't change this behavior I suppose. Moreover AWS behaves the same way
Using this policy
```json
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::bucket/*",
"Condition": {
"StringNotEquals": {
"s3:RequestObjectTag/environment": "production"
}
}
}
}
```
I can put object only if provide tag `environment=production` in request
fyrchik
commented
It it difficult to add a test here and check with other implementations to be sure? It it difficult to add a test here and check with other implementations to be sure?
https://git.frostfs.info/TrueCloudLab/s3-tests/src/commit/aa82bd16aef7c8b37cbc5e95ee06ff198d6270a8/s3tests_boto3/functional/test_s3.py
aarifullin
commented
Probably, I incorrectly pointed to "current implementation". I meant - "current" like in this PR, not > I'm not sure. The behavior being changed in this PR. Currently such condition evaluates to true because missing key has "" (empty) value that doesn't equal to "AES256" (in example)
Probably, I incorrectly pointed to "current implementation". I meant - "current" like in this PR, not `master`
dkirillov
commented
I suppose it's possible, but first we need to know what policies we are about to test > It it difficult to add a test here and check with other implementations to be sure?
aa82bd16ae/s3tests_boto3/functional/test_s3.py
I suppose it's possible, but first we need to know what policies we are about to test
fyrchik
commented
```
TEST 1
{"Version": "2012-10-17","Statement": {"Effect": "Allow","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket/*","Condition":{"StringNotEquals":{"s3:RequestObjectTag/environment": "production"}}}}
ENV: s3:RequestObjectTag/environment: production
TEST 2
{"Version": "2012-10-17","Statement": {"Effect": "Allow","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket/*","Condition":{"StringNotEquals":{"s3:RequestObjectTag/environment": "production"}}}}
ENV: s3:RequestObjectTag/environment: development
TEST 3
{"Version": "2012-10-17","Statement": {"Effect": "Allow","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket/*","Condition":{"StringNotEquals":{"s3:RequestObjectTag/environment": "production"}}}}
ENV: s3:RequestObjectTag/environment: <MISSING>
TEST 4
{"Version": "2012-10-17","Statement": {"Effect": "Allow","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket/*","Condition":{"StringEquals":{"s3:RequestObjectTag/environment": "production"}}}}
ENV: s3:RequestObjectTag/environment: production
TEST 5
{"Version": "2012-10-17","Statement": {"Effect": "Allow","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket/*","Condition":{"StringEquals":{"s3:RequestObjectTag/environment": "production"}}}}
ENV: s3:RequestObjectTag/environment: development
TEST 6
{"Version": "2012-10-17","Statement": {"Effect": "Allow","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket/*","Condition":{"StringEquals":{"s3:RequestObjectTag/environment": "production"}}}}
ENV: s3:RequestObjectTag/environment: <MISSING>
TEST 7
{"Version": "2012-10-17","Statement": {"Effect": "Deny","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket/*","Condition":{"StringNotEquals":{"s3:RequestObjectTag/environment": "production"}}}}
ENV: s3:RequestObjectTag/environment: production
TEST 8
{"Version": "2012-10-17","Statement": {"Effect": "Deny","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket/*","Condition":{"StringNotEquals":{"s3:RequestObjectTag/environment": "production"}}}}
ENV: s3:RequestObjectTag/environment: development
TEST 9
{"Version": "2012-10-17","Statement": {"Effect": "Deny","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket/*","Condition":{"StringNotEquals":{"s3:RequestObjectTag/environment": "production"}}}}
ENV: s3:RequestObjectTag/environment: <MISSING>
TEST 10
{"Version": "2012-10-17","Statement": {"Effect": "Deny","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket/*","Condition":{"StringEquals":{"s3:RequestObjectTag/environment": "production"}}}}
ENV: s3:RequestObjectTag/environment: production
TEST 11
{"Version": "2012-10-17","Statement": {"Effect": "Deny","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket/*","Condition":{"StringEquals":{"s3:RequestObjectTag/environment": "production"}}}}
ENV: s3:RequestObjectTag/environment: development
TEST 12
{"Version": "2012-10-17","Statement": {"Effect": "Deny","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket/*","Condition":{"StringEquals":{"s3:RequestObjectTag/environment": "production"}}}}
ENV: s3:RequestObjectTag/environment: <MISSING>
```
dkirillov
commented
Currently I have results only for deny policies. In AWS there are some problem make bucket public.
These results for AWS. Currently I have results only for deny policies. In AWS there are some problem make bucket public.
* TEST 7: not matched
* TEST 8: matched
* TEST 9: matched
* TEST 10: matched
* TEST 11: not matched
* TEST 12: not matched
These results for AWS.
Yandex badly support policies and we always get AccessDenied
dkirillov
commented
I couldn't validate policies with Allow statement and
Results:
I couldn't validate policies with Allow statement and `PutObject` but I could validate it with `PutObjectTagging`.
So policy was
```json
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObjectTagging",
"Resource": [
"arn:aws:s3:::bucket",
"arn:aws:s3:::bucket/*"
],
"Condition":{
"StringEquals": {
"s3:RequestObjectTag/Environment": "production"
}
}
}
}
```
Results:
* TEST 1: not matched -> AccessDenied
* TEST 2: matched -> Allow
* TEST 3: matched -> Allow
* TEST 4: matched -> Allow
* TEST 5: not matched -> AccessDenied
* TEST 6: not matched -> AccessDenied
dkirillov
commented
branch with tests https://git.frostfs.info/dkirillov/s3-tests/src/branch/feature/ape_if_exists_conditions branch with tests https://git.frostfs.info/dkirillov/s3-tests/src/branch/feature/ape_if_exists_conditions
fyrchik
commented
This clarifies the quoted sentence from the IAM doc:
For tests 7-12 (ALLOW) we have the behaviour similar to (DENY) in terms of matching. But I don't understand what is the different between Here are the same tests, but for
This clarifies the quoted sentence from the IAM doc:
1. `TEST 3: matched -> Allow`, this means that if the attribute is missing then `NotEquals` treats it as if it was empty. Which is logical, given that tests 4-6 have the opposite results (and the opposite operation).
For tests 7-12 (ALLOW) we have the behaviour similar to (DENY) in terms of matching.
But I don't understand what is the different between `StringNotEquals` and `StringNotEqualsIfExists`?
Probably `IfExists` will NOT match in the test 3?
Here are the same tests, but for `IfExists`, could you check them too?
```
TEST 1
{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "StringNotEqualsIfExists": { "s3:RequestObjectTag/environment": "production" } } } }
ENV: s3:RequestObjectTag/environment: production
TEST 2
{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "StringNotEqualsIfExists": { "s3:RequestObjectTag/environment": "production" } } } }
ENV: s3:RequestObjectTag/environment: development
TEST 3
{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "StringNotEqualsIfExists": { "s3:RequestObjectTag/environment": "production" } } } }
ENV: s3:RequestObjectTag/environment: <MISSING>
TEST 4
{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "StringEqualsIfExists": { "s3:RequestObjectTag/environment": "production" } } } }
ENV: s3:RequestObjectTag/environment: production
TEST 5
{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "StringEqualsIfExists": { "s3:RequestObjectTag/environment": "production" } } } }
ENV: s3:RequestObjectTag/environment: development
TEST 6
{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "StringEqualsIfExists": { "s3:RequestObjectTag/environment": "production" } } } }
ENV: s3:RequestObjectTag/environment: <MISSING>
TEST 7
{ "Version": "2012-10-17", "Statement": { "Effect": "Deny", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "StringNotEqualsIfExists": { "s3:RequestObjectTag/environment": "production" } } } }
ENV: s3:RequestObjectTag/environment: production
TEST 8
{ "Version": "2012-10-17", "Statement": { "Effect": "Deny", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "StringNotEqualsIfExists": { "s3:RequestObjectTag/environment": "production" } } } }
ENV: s3:RequestObjectTag/environment: development
TEST 9
{ "Version": "2012-10-17", "Statement": { "Effect": "Deny", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "StringNotEqualsIfExists": { "s3:RequestObjectTag/environment": "production" } } } }
ENV: s3:RequestObjectTag/environment: <MISSING>
TEST 10
{ "Version": "2012-10-17", "Statement": { "Effect": "Deny", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "StringEqualsIfExists": { "s3:RequestObjectTag/environment": "production" } } } }
ENV: s3:RequestObjectTag/environment: production
TEST 11
{ "Version": "2012-10-17", "Statement": { "Effect": "Deny", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "StringEqualsIfExists": { "s3:RequestObjectTag/environment": "production" } } } }
ENV: s3:RequestObjectTag/environment: development
TEST 12
{ "Version": "2012-10-17", "Statement": { "Effect": "Deny", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "StringEqualsIfExists": { "s3:RequestObjectTag/environment": "production" } } } }
ENV: s3:RequestObjectTag/environment: <MISSING>
```
dkirillov
commented
The same as previous (TEST 1-6 used PutObjectTagging instead of PutObject, TEST 7-12 used PubObject)
P.S. For the history tests 1-6 for the PutObject (It looks weird, probably I missed something)
The same as previous (TEST 1-6 used PutObjectTagging instead of PutObject, TEST 7-12 used PubObject)
* TEST 1: not matched -> AccessDenied
* TEST 2: matched -> Allow
* TEST 3: matched -> Allow
* TEST 4: matched -> Allow
* TEST 5: not matched -> AccessDenied
* TEST 6: matched -> Allow
*
* TEST 7: not matched -> Allow
* TEST 8: matched -> AccessDenied
* TEST 9: matched -> AccessDenied
* TEST 10: matched -> AccessDenied
* TEST 11: not matched -> Allow
* TEST 12: matched -> AccessDenied
P.S. For the history tests 1-6 for the PutObject (It looks weird, probably I missed something)
* TEST 1: not matched -> AccessDenied
* TEST 2: not matched -> AccessDenied
* TEST 3: matched -> Allow
* TEST 4: not matched -> AccessDenied
* TEST 5: not matched -> AccessDenied
* TEST 6: not matched -> AccessDenied
fyrchik
commented
So we have different results in test 6 Tests 7-12 seem to differ a lot from your previous comment, is there some copy-paste error here? So we have different results in test 6
Tests 7-12 seem to differ a lot from your previous comment, is there some copy-paste error here?
dkirillov
commented
No, there shouldn't be any mistakes. As for me we got the same match result for pair tests
and it's expected in general (I means symmetric results for Allow/Deny Equals/NotEquals). No, there shouldn't be any mistakes. As for me we got the same match result for pair tests
* TEST 1,7 - not matched
* TEST 2,8 - matched
* TEST 3,9 - matched
* TEST 4,10 - matched
* TEST 5,11 - not matched
* TEST 6,12 - matched
and it's expected in general (I means symmetric results for Allow/Deny Equals/NotEquals).
fyrchik
commented
So So `NotEquals` doesn't differ from `NotEqualsIfExists`, right?
`Equals` differs from `EqualsIfExists`, though.
fyrchik
commented
Hm, I think I have found the discriminator:
Basically NotEquals/NotEqualsIfExists when 0 or 1 key is missing and the second match is false.
Hm, I think I have found the discriminator:
```DISCRIMINATOR 1:
{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*",
"Condition": {
"StringNotEqualsIfExists": { "s3:RequestObjectTag/environment": "production" },
"StringNotEqualsIfExists": { "s3:RequestObjectTag/anotherparameter": "xxx" }
}}}
ENV:
s3:RequestObjectTag/environment: development
s3:RequestObjectTag/anotherparameter: xxx
DISCRIMINATOR 2:
{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::bucket/*",
"Condition": {
"StringNotEqualsIfExists": { "s3:RequestObjectTag/environment": "production" },
"StringNotEqualsIfExists": { "s3:RequestObjectTag/anotherparameter": "xxx" }
}}}
ENV:
s3:RequestObjectTag/anotherparameter: xxx
```
Basically NotEquals/NotEqualsIfExists when 0 or 1 key is missing and the second match is false.
If my understanding is correct, the results should be the following:
```
DISCRIMINATOR 1: false (both values exist, one of them is equal and thus unmatched)
DISCRIMINATOR 2: true (only 1 value exists. It is unmatched, but the second IfExists makes ALL condition true)
```
fyrchik
commented
or no, my brain has melted or no, my brain has melted
dkirillov
commented
It seems no.
And I get the following results:
It seems no.
I have the following policy (`StringNotEqualsIfExists` can be added only once):
```json
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObjectTagging",
"Resource": [
"arn:aws:s3:::yvo6fckpk92rhcsz",
"arn:aws:s3:::yvo6fckpk92rhcsz/*"
],
"Condition":{
"StringNotEqualsIfExists": {
"s3:RequestObjectTag/environment": "production" ,
"s3:RequestObjectTag/anotherparameter": "xxx"
}
}
}
}
```
And I get the following results:
```shell
$ aws s3api put-object-tagging --bucket yvo6fckpk92rhcsz --key tmp --no-sign-request --tagging '{"TagSet": [ { "Key": "anotherparameter", "Value": "xxx" } ]}'
An error occurred (AccessDenied) when calling the PutObjectTagging operation (reached max retries: 0): Access Denied
$ aws s3api put-object-tagging --bucket yvo6fckpk92rhcsz --key tmp --no-sign-request --tagging '{"TagSet": [ { "Key": "anotherparameter", "Value": "xxx2" } ]}'
{
"VersionId": "null"
}
$ aws s3api put-object-tagging --bucket yvo6fckpk92rhcsz --key tmp --no-sign-request --tagging '{"TagSet": [ { "Key": "anotherparameter", "Value": "xxx2" }, {"Key": "environment", "Value": "production"} ]}'
An error occurred (AccessDenied) when calling the PutObjectTagging operation (reached max retries: 0): Access Denied
$ aws s3api put-object-tagging --bucket yvo6fckpk92rhcsz --key tmp --no-sign-request --tagging '{"TagSet": [ ]}'
{
"VersionId": "null"
}
```
aarifullin
commented
Currently, in this pull-request - it doesn't, but I believe that's why the doc clarifies:
So, status should be propagated to > So `NotEquals` doesn't differ from `NotEqualsIfExists`, right?
Currently, in this pull-request - it doesn't, but I believe that's why the [doc](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_IfExists) clarifies:
> If you are using an "Effect": "Deny" element with a negated condition operator like StringNotEqualsIfExists, the request is still denied even if the condition key is not present.
So, status should be propagated to `Match` function to check `...NotEqualsIfExists` operators
fyrchik
commented
Hm, so you have understood this sentence (I have not). Could you give an example of policy, where notequal and notequalifexists behave differently? Anyway, let's not use negative ifexists in this PR. >So, status should be propagated
Hm, so you have understood this sentence (I have not). Could you give an example of policy, where notequal and notequalifexists behave differently?
Anyway, let's not use negative ifexists in this PR.
aarifullin
commented
OK. I have already removed these operators and updated PR but let me check the tests provided by @dkirillov first
I mean, I confirmed that you was right - these both operators, actually, had had no difference if key is present.
So, it must be interpreted only as (corresponding to the specification):
while
Anyway, let's keep these negated operators far away for the goodness sake > let's not use negative ifexists in this PR
OK. I have already removed these operators and updated PR but let me check the tests provided by @dkirillov first
> Could you give an example of policy, where notequal and notequalifexists behave differently?
I mean, I confirmed that you was right - these both operators, actually, had had no difference if key is present.
- `StringNotEquals` returns `false` if key is not present (seems OK)
- `StringEqualsIfExists` returns `true` if key is not present ("If the key is not present, evaluate the condition element as true.")
- `StringNotEqualsIfExists`: returns `true` if key is not present. **Okay, I was wrong about "propagating status" - it doesn't make sense**. This mind-blowing sentence that confuses us declares the same behavior for `...Not...IfExists` operators ("If the key is not present, evaluate the condition element as true."). Possibly, that's because negated could be **mistaken** for
```
StringNotEqualsIfExists(x, y) = ¬StringEqualsIfExists(x, y) =
= ¬(¬Present(Properties, Key_X) OR Value(Key_X) == Y)) =
= Present(Properties, Key_X) && Value(Key_X) != Y = StringNotEquals(x,y)-> condition won't work out if key is not present
```
So, it must be interpreted only as (corresponding to the specification):
```
StringNotEqualsIfExists(x, y) = ¬Present(Properties, Key_X) || Value(Key_X) != Y
```
while
```
StringNotEquals(x,y) = Present(Properties, Key_X) && Value(Key_X) != Y
```
**Anyway**, let's keep these negated operators far away for the goodness sake
|
|||||||
case CondStringNotLike:
|
case CondStringNotLike:
|
||||||
return !util.GlobMatch(val, c.Value)
|
return exists && !util.GlobMatch(val, c.Value)
|
||||||
case CondStringLessThan:
|
case CondStringLessThan:
|
||||||
return val < c.Value
|
return exists && val < c.Value
|
||||||
|
case CondStringLessThanIfExists:
|
||||||
|
return !exists || val < c.Value
|
||||||
case CondStringLessThanEquals:
|
case CondStringLessThanEquals:
|
||||||
return val <= c.Value
|
return exists && val <= c.Value
|
||||||
|
case CondStringLessThanEqualsIfExists:
|
||||||
|
return !exists || val <= c.Value
|
||||||
case CondStringGreaterThan:
|
case CondStringGreaterThan:
|
||||||
return val > c.Value
|
return exists && val > c.Value
|
||||||
|
case CondStringGreaterThanIfExists:
|
||||||
|
return !exists || val > c.Value
|
||||||
case CondStringGreaterThanEquals:
|
case CondStringGreaterThanEquals:
|
||||||
return val >= c.Value
|
return exists && val >= c.Value
|
||||||
|
case CondStringGreaterThanEqualsIfExists:
|
||||||
|
return !exists || val >= c.Value
|
||||||
case CondSliceContains:
|
case CondSliceContains:
|
||||||
return slices.Contains(strings.Split(val, condSliceContainsDelimiter), c.Value)
|
return slices.Contains(strings.Split(val, condSliceContainsDelimiter), c.Value)
|
||||||
case CondNumericEquals, CondNumericNotEquals, CondNumericLessThan, CondNumericLessThanEquals, CondNumericGreaterThan,
|
case CondNumericEquals, CondNumericNotEquals, CondNumericLessThan, CondNumericLessThanEquals, CondNumericGreaterThan,
|
||||||
CondNumericGreaterThanEquals:
|
CondNumericGreaterThanEquals:
|
||||||
return c.matchNumeric(val)
|
return exists && c.matchNumeric(val)
|
||||||
|
case CondNumericEqualsIfExists, CondNumericLessThanIfExists, CondNumericLessThanEqualsIfExists, CondNumericGreaterThanIfExists,
|
||||||
|
CondNumericGreaterThanEqualsIfExists:
|
||||||
|
return !exists || c.matchNumeric(val)
|
||||||
case CondIPAddress, CondNotIPAddress:
|
case CondIPAddress, CondNotIPAddress:
|
||||||
return c.matchIP(val)
|
return c.matchIP(val)
|
||||||
}
|
}
|
||||||
|
@ -213,17 +257,17 @@ func (c *Condition) matchNumeric(val string) bool {
|
||||||
switch c.Op {
|
switch c.Op {
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unimplemented: %d", c.Op))
|
panic(fmt.Sprintf("unimplemented: %d", c.Op))
|
||||||
case CondNumericEquals:
|
case CondNumericEquals, CondNumericEqualsIfExists:
|
||||||
return valDecimal.Equal(condVal)
|
return valDecimal.Equal(condVal)
|
||||||
case CondNumericNotEquals:
|
case CondNumericNotEquals:
|
||||||
return !valDecimal.Equal(condVal)
|
return !valDecimal.Equal(condVal)
|
||||||
case CondNumericLessThan:
|
case CondNumericLessThan, CondNumericLessThanIfExists:
|
||||||
return valDecimal.LessThan(condVal)
|
return valDecimal.LessThan(condVal)
|
||||||
case CondNumericLessThanEquals:
|
case CondNumericLessThanEquals, CondNumericLessThanEqualsIfExists:
|
||||||
return valDecimal.LessThan(condVal) || valDecimal.Equal(condVal)
|
return valDecimal.LessThan(condVal) || valDecimal.Equal(condVal)
|
||||||
case CondNumericGreaterThan:
|
case CondNumericGreaterThan, CondNumericGreaterThanIfExists:
|
||||||
return valDecimal.GreaterThan(condVal)
|
return valDecimal.GreaterThan(condVal)
|
||||||
case CondNumericGreaterThanEquals:
|
case CondNumericGreaterThanEquals, CondNumericGreaterThanEqualsIfExists:
|
||||||
return valDecimal.GreaterThan(condVal) || valDecimal.Equal(condVal)
|
return valDecimal.GreaterThan(condVal) || valDecimal.Equal(condVal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -398,6 +398,25 @@ func testNumericConditionsMatch(t *testing.T) {
|
||||||
value: "50",
|
value: "50",
|
||||||
status: Allow,
|
status: Allow,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "value if exists from interval",
|
||||||
|
conditions: []Condition{
|
||||||
|
{
|
||||||
|
Op: CondNumericLessThanIfExists,
|
||||||
|
Kind: KindRequest,
|
||||||
|
Key: propKey,
|
||||||
|
Value: "100",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Op: CondNumericGreaterThanIfExists,
|
||||||
|
Kind: KindRequest,
|
||||||
|
Key: propKey,
|
||||||
|
Value: "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: "90",
|
||||||
|
status: Allow,
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
|
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
|
||||||
|
@ -411,6 +430,16 @@ func testNumericConditionsMatch(t *testing.T) {
|
||||||
}}}
|
}}}
|
||||||
st, _ := ch.Match(request)
|
st, _ := ch.Match(request)
|
||||||
require.Equal(t, tc.status.String(), st.String())
|
require.Equal(t, tc.status.String(), st.String())
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found := ch.Match(emptyPropsRequest)
|
||||||
|
if strings.HasSuffix(tc.conditions[0].Op.String(), "IfExists") {
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, tc.status.String(), st.String())
|
||||||
|
} else {
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, st, NoRuleFound)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -448,6 +477,47 @@ func testStringConiditionsMatch(t *testing.T) {
|
||||||
st, found = ch.Match(request)
|
st, found = ch.Match(request)
|
||||||
require.False(t, found)
|
require.False(t, found)
|
||||||
require.Equal(t, NoRuleFound, st)
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
_, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(CondStringEqualsIfExists.String(), func(t *testing.T) {
|
||||||
|
ch := Chain{Rules: []Rule{{
|
||||||
|
Status: Allow,
|
||||||
|
Actions: Actions{Names: []string{native.MethodPutObject}},
|
||||||
|
Resources: Resources{Names: []string{native.ResourceFormatRootContainers}},
|
||||||
|
Condition: []Condition{{
|
||||||
|
Op: CondStringEqualsIfExists,
|
||||||
|
Kind: KindRequest,
|
||||||
|
Key: propKey,
|
||||||
|
Value: val,
|
||||||
|
}},
|
||||||
|
}}}
|
||||||
|
|
||||||
|
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
|
||||||
|
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: val,
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found := ch.Match(request)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
|
|
||||||
|
request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: "distort_tag_value" + val,
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found = ch.Match(request)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run(CondStringNotEquals.String(), func(t *testing.T) {
|
t.Run(CondStringNotEquals.String(), func(t *testing.T) {
|
||||||
|
@ -479,6 +549,11 @@ func testStringConiditionsMatch(t *testing.T) {
|
||||||
st, found = ch.Match(request)
|
st, found = ch.Match(request)
|
||||||
require.False(t, found)
|
require.False(t, found)
|
||||||
require.Equal(t, NoRuleFound, st)
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
_, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run(CondStringEqualsIgnoreCase.String(), func(t *testing.T) {
|
t.Run(CondStringEqualsIgnoreCase.String(), func(t *testing.T) {
|
||||||
|
@ -510,6 +585,47 @@ func testStringConiditionsMatch(t *testing.T) {
|
||||||
st, found = ch.Match(request)
|
st, found = ch.Match(request)
|
||||||
require.False(t, found)
|
require.False(t, found)
|
||||||
require.Equal(t, NoRuleFound, st)
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
_, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(CondStringEqualsIgnoreCaseIfExists.String(), func(t *testing.T) {
|
||||||
|
ch := Chain{Rules: []Rule{{
|
||||||
|
Status: Allow,
|
||||||
|
Actions: Actions{Names: []string{native.MethodPutObject}},
|
||||||
|
Resources: Resources{Names: []string{native.ResourceFormatRootContainers}},
|
||||||
|
Condition: []Condition{{
|
||||||
|
Op: CondStringEqualsIgnoreCaseIfExists,
|
||||||
|
Kind: KindRequest,
|
||||||
|
Key: propKey,
|
||||||
|
Value: val,
|
||||||
|
}},
|
||||||
|
}}}
|
||||||
|
|
||||||
|
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
|
||||||
|
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: strings.ToUpper(val),
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found := ch.Match(request)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
|
|
||||||
|
request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: strings.ToUpper("distort_tag_value" + val),
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found = ch.Match(request)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run(CondStringNotEqualsIgnoreCase.String(), func(t *testing.T) {
|
t.Run(CondStringNotEqualsIgnoreCase.String(), func(t *testing.T) {
|
||||||
|
@ -541,6 +657,11 @@ func testStringConiditionsMatch(t *testing.T) {
|
||||||
st, found = ch.Match(request)
|
st, found = ch.Match(request)
|
||||||
require.False(t, found)
|
require.False(t, found)
|
||||||
require.Equal(t, NoRuleFound, st)
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run(CondStringLike.String(), func(t *testing.T) {
|
t.Run(CondStringLike.String(), func(t *testing.T) {
|
||||||
|
@ -572,6 +693,47 @@ func testStringConiditionsMatch(t *testing.T) {
|
||||||
st, found = ch.Match(request)
|
st, found = ch.Match(request)
|
||||||
require.False(t, found)
|
require.False(t, found)
|
||||||
require.Equal(t, NoRuleFound, st)
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(CondStringLikeIfExists.String(), func(t *testing.T) {
|
||||||
|
ch := Chain{Rules: []Rule{{
|
||||||
|
Status: Allow,
|
||||||
|
Actions: Actions{Names: []string{native.MethodPutObject}},
|
||||||
|
Resources: Resources{Names: []string{native.ResourceFormatRootContainers}},
|
||||||
|
Condition: []Condition{{
|
||||||
|
Op: CondStringLikeIfExists,
|
||||||
|
Kind: KindRequest,
|
||||||
|
Key: propKey,
|
||||||
|
Value: val + "*",
|
||||||
|
}},
|
||||||
|
}}}
|
||||||
|
|
||||||
|
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
|
||||||
|
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: val + "suffix",
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found := ch.Match(request)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
|
|
||||||
|
request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: string([]byte(val)[:len(val)-1]), //cut last letter
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found = ch.Match(request)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run(CondStringNotLike.String(), func(t *testing.T) {
|
t.Run(CondStringNotLike.String(), func(t *testing.T) {
|
||||||
|
@ -603,6 +765,11 @@ func testStringConiditionsMatch(t *testing.T) {
|
||||||
st, found = ch.Match(request)
|
st, found = ch.Match(request)
|
||||||
require.False(t, found)
|
require.False(t, found)
|
||||||
require.Equal(t, NoRuleFound, st)
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run(CondStringLessThan.String(), func(t *testing.T) {
|
t.Run(CondStringLessThan.String(), func(t *testing.T) {
|
||||||
|
@ -634,6 +801,47 @@ func testStringConiditionsMatch(t *testing.T) {
|
||||||
st, found = ch.Match(request)
|
st, found = ch.Match(request)
|
||||||
require.False(t, found)
|
require.False(t, found)
|
||||||
require.Equal(t, NoRuleFound, st)
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(CondStringLessThanIfExists.String(), func(t *testing.T) {
|
||||||
|
ch := Chain{Rules: []Rule{{
|
||||||
|
Status: Allow,
|
||||||
|
Actions: Actions{Names: []string{native.MethodPutObject}},
|
||||||
|
Resources: Resources{Names: []string{native.ResourceFormatRootContainers}},
|
||||||
|
Condition: []Condition{{
|
||||||
|
Op: CondStringLessThanIfExists,
|
||||||
|
Kind: KindRequest,
|
||||||
|
Key: propKey,
|
||||||
|
Value: val + "b",
|
||||||
|
}},
|
||||||
|
}}}
|
||||||
|
|
||||||
|
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
|
||||||
|
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: val + "a",
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found := ch.Match(request)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
|
|
||||||
|
request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: val + "c",
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found = ch.Match(request)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run(CondStringLessThanEquals.String(), func(t *testing.T) {
|
t.Run(CondStringLessThanEquals.String(), func(t *testing.T) {
|
||||||
|
@ -665,6 +873,47 @@ func testStringConiditionsMatch(t *testing.T) {
|
||||||
st, found = ch.Match(request)
|
st, found = ch.Match(request)
|
||||||
require.True(t, found)
|
require.True(t, found)
|
||||||
require.Equal(t, Allow, st)
|
require.Equal(t, Allow, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(CondStringLessThanEqualsIfExists.String(), func(t *testing.T) {
|
||||||
|
ch := Chain{Rules: []Rule{{
|
||||||
|
Status: Allow,
|
||||||
|
Actions: Actions{Names: []string{native.MethodPutObject}},
|
||||||
|
Resources: Resources{Names: []string{native.ResourceFormatRootContainers}},
|
||||||
|
Condition: []Condition{{
|
||||||
|
Op: CondStringLessThanEqualsIfExists,
|
||||||
|
Kind: KindRequest,
|
||||||
|
Key: propKey,
|
||||||
|
Value: val + "b",
|
||||||
|
}},
|
||||||
|
}}}
|
||||||
|
|
||||||
|
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
|
||||||
|
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: val + "a",
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found := ch.Match(request)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
|
|
||||||
|
request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: val + "b",
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found = ch.Match(request)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run(CondStringGreaterThan.String(), func(t *testing.T) {
|
t.Run(CondStringGreaterThan.String(), func(t *testing.T) {
|
||||||
|
@ -696,6 +945,47 @@ func testStringConiditionsMatch(t *testing.T) {
|
||||||
st, found = ch.Match(request)
|
st, found = ch.Match(request)
|
||||||
require.False(t, found)
|
require.False(t, found)
|
||||||
require.Equal(t, NoRuleFound, st)
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(CondStringGreaterThanIfExists.String(), func(t *testing.T) {
|
||||||
|
ch := Chain{Rules: []Rule{{
|
||||||
|
Status: Allow,
|
||||||
|
Actions: Actions{Names: []string{native.MethodPutObject}},
|
||||||
|
Resources: Resources{Names: []string{native.ResourceFormatRootContainers}},
|
||||||
|
Condition: []Condition{{
|
||||||
|
Op: CondStringGreaterThanIfExists,
|
||||||
|
Kind: KindRequest,
|
||||||
|
Key: propKey,
|
||||||
|
Value: val + "b",
|
||||||
|
}},
|
||||||
|
}}}
|
||||||
|
|
||||||
|
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
|
||||||
|
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: val + "c",
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found := ch.Match(request)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
|
|
||||||
|
request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: val + "b",
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found = ch.Match(request)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run(CondStringGreaterThanEquals.String(), func(t *testing.T) {
|
t.Run(CondStringGreaterThanEquals.String(), func(t *testing.T) {
|
||||||
|
@ -727,6 +1017,47 @@ func testStringConiditionsMatch(t *testing.T) {
|
||||||
st, found = ch.Match(request)
|
st, found = ch.Match(request)
|
||||||
require.True(t, found)
|
require.True(t, found)
|
||||||
require.Equal(t, Allow, st)
|
require.Equal(t, Allow, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, NoRuleFound, st)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(CondStringGreaterThanEqualsIfExists.String(), func(t *testing.T) {
|
||||||
|
ch := Chain{Rules: []Rule{{
|
||||||
|
Status: Allow,
|
||||||
|
Actions: Actions{Names: []string{native.MethodPutObject}},
|
||||||
|
Resources: Resources{Names: []string{native.ResourceFormatRootContainers}},
|
||||||
|
Condition: []Condition{{
|
||||||
|
Op: CondStringGreaterThanEqualsIfExists,
|
||||||
|
Kind: KindRequest,
|
||||||
|
Key: propKey,
|
||||||
|
Value: val + "b",
|
||||||
|
}},
|
||||||
|
}}}
|
||||||
|
|
||||||
|
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
|
||||||
|
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: val + "c",
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found := ch.Match(request)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
|
|
||||||
|
request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
|
||||||
|
propKey: val + "b",
|
||||||
|
})
|
||||||
|
|
||||||
|
st, found = ch.Match(request)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
|
|
||||||
|
emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{})
|
||||||
|
st, found = ch.Match(emptyPropsRequest)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, Allow, st)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,17 @@ type Request interface {
|
||||||
// Name is the operation name, such as Object.Put. Must not include wildcards.
|
// Name is the operation name, such as Object.Put. Must not include wildcards.
|
||||||
Operation() string
|
Operation() string
|
||||||
// Property returns request properties, such as IP address of the origin.
|
// Property returns request properties, such as IP address of the origin.
|
||||||
Property(string) string
|
// The second return boolean value determines if the specified value exists within the properties.
|
||||||
|
Property(string) (string, bool)
|
||||||
// Resource returns resource the operation is applied to.
|
// Resource returns resource the operation is applied to.
|
||||||
Resource() Resource
|
Resource() Resource
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resource represents the resource operation is applied to.
|
// Resource represents the resource operation is applied to.
|
||||||
type Resource interface {
|
type Resource interface {
|
||||||
|
// Name is the resource name.
|
||||||
Name() string
|
Name() string
|
||||||
Property(string) string
|
// Property returns resource properties, such as object type etc.
|
||||||
|
// The second return boolean value determines if the specified value exists within the properties.
|
||||||
|
Property(string) (string, bool)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,9 @@ func (r *Resource) Name() string {
|
||||||
return r.name
|
return r.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) Property(name string) string {
|
func (r *Resource) Property(name string) (val string, exists bool) {
|
||||||
return r.properties[name]
|
val, exists = r.properties[name]
|
||||||
dkirillov marked this conversation as resolved
Outdated
dkirillov
commented
Why don't we write just Why don't we write just `return r.properties[name]` ?
aarifullin
commented
Because compiler doesn't allow this:
Because compiler doesn't allow this:
```
not enough return values
have (string)
want (string, bool)
```
|
|||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResource(name string, properties map[string]string) *Resource {
|
func NewResource(name string, properties map[string]string) *Resource {
|
||||||
|
@ -40,8 +41,9 @@ func (r *Request) Resource() resourcepkg.Resource {
|
||||||
return r.resource
|
return r.resource
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Request) Property(name string) string {
|
func (r *Request) Property(name string) (val string, exists bool) {
|
||||||
return r.properties[name]
|
val, exists = r.properties[name]
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRequest(op string, r *Resource, properties map[string]string) *Request {
|
func NewRequest(op string, r *Resource, properties map[string]string) *Request {
|
||||||
|
|
Why do we change this?
#93 (comment)