Compare commits

..

1 commit

Author SHA1 Message Date
aarifullin
4e69174715 [#71] docs: Introduce APE overview
All checks were successful
DCO action / DCO (pull_request) Successful in 1m4s
Tests and linters / Tests (1.21) (pull_request) Successful in 1m9s
Tests and linters / Tests (1.20) (pull_request) Successful in 1m18s
Tests and linters / Staticcheck (pull_request) Successful in 1m30s
Tests and linters / Tests with -race (pull_request) Successful in 1m43s
Tests and linters / Lint (pull_request) Successful in 2m22s
Signed-off-by: Airat Arifullin <aarifullin@yadro.com>
2024-05-07 16:51:45 +03:00
29 changed files with 453 additions and 1999 deletions

View file

@ -13,7 +13,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.23'
go-version: '1.21'
- name: Run commit format checker
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v3

View file

@ -11,7 +11,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '1.23'
go-version: '1.21'
cache: true
- name: Install linters
@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go_versions: [ '1.22', '1.23' ]
go_versions: [ '1.20', '1.21' ]
fail-fast: false
steps:
- uses: actions/checkout@v3
@ -48,7 +48,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '1.23'
go-version: '1.21'
cache: true
- name: Run tests
@ -63,7 +63,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '1.23'
go-version: '1.21'
cache: true
- name: Install staticcheck

View file

@ -12,8 +12,7 @@ run:
# output configuration options
output:
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
formats:
- format: tab
format: tab
# all available settings of specific linters
linters-settings:

View file

@ -2,6 +2,13 @@ ci:
autofix_prs: false
repos:
- repo: https://github.com/jorisroovers/gitlint
rev: v0.19.1
hooks:
- id: gitlint
stages: [commit-msg]
- id: gitlint-ci
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
@ -35,7 +42,7 @@ repos:
hooks:
- id: go-unit-tests
name: go unit tests
entry: make test GOFLAGS=''
entry: make test
pass_filenames: false
types: [go]
language: system

View file

@ -1,3 +0,0 @@
.* @TrueCloudLab/storage-core-committers @TrueCloudLab/storage-core-developers @TrueCloudLab/storage-services-committers @TrueCloudLab/storage-services-developers
.forgejo/.* @potyarkin
Makefile @potyarkin

View file

@ -1,9 +1,9 @@
#!/usr/bin/make -f
TRUECLOUDLAB_LINT_VERSION ?= 0.0.6
TRUECLOUDLAB_LINT_VERSION ?= 0.0.2
TMP_DIR := .cache
OUTPUT_LINT_DIR ?= $(shell pwd)/bin
LINT_VERSION ?= 1.60.1
LINT_VERSION ?= 1.55.1
LINT_DIR = $(OUTPUT_LINT_DIR)/golangci-lint-$(LINT_VERSION)-v$(TRUECLOUDLAB_LINT_VERSION)
EASYJSON_VERSION ?= $(shell go list -f '{{.Version}}' -m github.com/mailru/easyjson)
EASYJSON_DIR ?= $(shell pwd)/bin/easyjson-$(EASYJSON_VERSION)
@ -22,10 +22,9 @@ imports:
@goimports -w .
# Run Unit Test with go test
test: GOFLAGS ?= "-count=1"
test:
@echo "⇒ Running go test"
@GOFLAGS="$(GOFLAGS)" go test ./...
@go test ./... -count=1
# Activate pre-commit hooks
pre-commit:

View file

@ -1,439 +0,0 @@
# Policy converters
This repository contains converters that provide opportunities to
transform [AWS IAM policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html) to inner
FrostFS policy format. This document describes such transformations.
## FrostFS
As it was mentioned there are converters that transform AWS IAM policies to FrostFS.
Here common examples of AWS:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": "*"
}
]
}
```
and FrostFS:
```json
{
"ID": "c29tZS1pZA==",
"Rules": [
{
"Status": "Allow",
"Actions": {
"Inverted": false,
"Names": [
"s3:*"
]
},
"Resources": {
"Inverted": false,
"Names": [
"*"
]
},
"Any": false,
"Condition": null
}
],
"MatchType": "DenyPriority"
}
```
policies.
Despite there is only one FrostFS format, we have two converters (`s3` and `native`). The reason is S3 gateway and
Storage node have different actions and resource naming:
* S3 has [a lot of methods](https://docs.aws.amazon.com/AmazonS3/latest/API/API_Operations.html) and operates with
bucket/object
* Storage node has only 6 container and 7 object methods and operates container/object (that has different format)
The following sections describe each transformation more precisely ([common](#common) sections contains shared concepts)
### Common
#### Fields
Rough json main fields mapping:
| AWS policy field | FrostFS policy field | Comment |
|------------------|----------------------|------------------------------------------------------------------|
| `Version` | - | Not applicable |
| `Statement` | `Rules` | |
| `Effect` | `Status` | |
| `Action` | `Actions.Names` | `Actions.Inverted` = false |
| `NotAction` | `Actions.Names` | `Actions.Inverted` = true |
| `Resource` | `Resources.Names` | `Resources.Inverted` = false |
| `NotResource` | `Resources.Names` | `Resources.Inverted` = true |
| `Condition` | `Condition` | `Any` = false, that means the conditions must be hold altogether |
| `Principal` | - | Expressed via conditions (depends on s3/native converters) |
### Conditions
Each condition in FrostFS policy can add requirements to some request/resource properties
and consists of the following fields:
| Field | Description |
|----------|-------------------------------------------------------------------------------------------|
| `Op` | Condition type operation (`StringEqual`, `NumericEqual` etc) |
| `Object` | Property type to which condition can be applied (`Request` property, `Resource` property) |
| `Key` | Property key |
| `Value` | Property value |
Conditions operators:
| AWS conditions operator | FrostFS condition operator | Comment |
|-----------------------------|-----------------------------|-------------------------------------------------------------------|
| `StringEquals` | `StringEquals` | |
| `StringNotEquals` | `StringNotEquals` | |
| `StringEqualsIgnoreCase` | `StringEqualsIgnoreCase` | |
| `StringNotEqualsIgnoreCase` | `StringNotEqualsIgnoreCase` | |
| `StringLike` | `StringLike` | |
| `StringNotLike` | `StringNotLike` | |
| `NumericEquals` | `NumericEquals` | |
| `NumericNotEquals` | `NumericNotEquals` | |
| `NumericLessThan` | `NumericLessThan` | |
| `NumericLessThanEquals` | `NumericLessThanEquals` | |
| `NumericGreaterThan` | `NumericGreaterThan` | |
| `NumericGreaterThanEquals` | `NumericGreaterThanEquals` | |
| `DateEquals` | `StringEquals` | Date transforms to unix timestamp to be compared as string |
| `DateNotEquals` | `StringNotEquals` | Date transforms to unix timestamp to be compared as string |
| `DateLessThan` | `StringEqualsIgnoreCase` | Date transforms to unix timestamp to be compared as string |
| `DateLessThanEquals` | `StringNotEqualsIgnoreCase` | Date transforms to unix timestamp to be compared as string |
| `DateGreaterThan` | `StringLike` | Date transforms to unix timestamp to be compared as string |
| `DateGreaterThanEquals` | `StringNotLike` | Date transforms to unix timestamp to be compared as string |
| `Bool` | `StringEqualsIgnoreCase` | |
| `IpAddress` | `IPAddress` | |
| `NotIpAddress` | `NotIPAddress` | |
| `ArnEquals` | `StringEquals` | |
| `ArnLike` | `StringLike` | |
| `ArnNotEquals` | `StringNotEquals` | |
| `ArnNotLike` | `StringNotLike` | |
| `SliceContains` | `SliceContains` | AWS spec doesn't contain such operator. This is FrostFS extension |
For example, AWS conditions:
```json
{
"Condition": {
"ArnEquals": {"key16": ["val16"]},
"ArnNotEquals": {"key18": ["val18"]},
"ArnNotLike": {"key19": ["val19"]},
"Bool": {"key13": ["True"]},
"DateEquals": {"key7": ["2006-01-02T15:04:05+07:00"]},
"DateGreaterThan": {"key11": ["2006-01-02T15:04:05-01:00"]},
"DateGreaterThanEquals": {"key12": ["2006-01-02T15:04:05-03:00"]},
"DateLessThan": {"key9": ["2006-01-02T15:04:05+06:00"]},
"DateLessThanEquals": {"key10": ["2006-01-02T15:04:05+03:00"]},
"DateNotEquals": {"key8": ["2006-01-02T15:04:05Z"]},
"NumericEquals": {"key20": ["-20"]},
"NumericGreaterThan": {"key24": ["-24.24"]},
"NumericGreaterThanEquals": {"key25": ["+25.25"]},
"NumericLessThan": {"key22": ["0"]},
"NumericLessThanEquals": {"key23": ["23.23"]},
"NumericNotEquals": {"key21": ["+21"]},
"StringEquals": {"key1": ["val0"]},
"StringEqualsIgnoreCase": {"key3": ["val3"]},
"StringLike": {"key5": ["val5"]},
"StringNotEquals": {"key2": ["val2"]},
"StringNotEqualsIgnoreCase": {"key4": ["val4"]},
"StringNotLike": {"key6": ["val6"]}
}
}
```
transforms to FrostFS conditions:
```json
{
"Condition": [
{"Op": "StringLike", "Object": "Request", "Key": "key5", "Value": "val5"},
{"Op": "StringNotEquals", "Object": "Request", "Key": "key2", "Value": "val2"},
{"Op": "StringGreaterThan", "Object": "Request", "Key": "key11", "Value": "1136217845"},
{"Op": "StringGreaterThanEquals", "Object": "Request", "Key": "key12", "Value": "1136225045"},
{"Op": "StringLessThan", "Object": "Request", "Key": "key9", "Value": "1136192645"},
{"Op": "StringEqualsIgnoreCase", "Object": "Request", "Key": "key3", "Value": "val3"},
{"Op": "StringEquals", "Object": "Request", "Key": "key16", "Value": "val16"},
{"Op": "NumericLessThanEquals", "Object": "Request", "Key": "key23", "Value": "23.23"},
{"Op": "StringNotEqualsIgnoreCase", "Object": "Request", "Key": "key4", "Value": "val4"},
{"Op": "StringEquals", "Object": "Request", "Key": "key1", "Value": "val0"},
{"Op": "StringLessThanEquals", "Object": "Request", "Key": "key10", "Value": "1136203445"},
{"Op": "NumericGreaterThan", "Object": "Request", "Key": "key24", "Value": "-24.24"},
{"Op": "NumericGreaterThanEquals", "Object": "Request", "Key": "key25", "Value": "+25.25"},
{"Op": "NumericLessThan", "Object": "Request", "Key": "key22", "Value": "0"},
{"Op": "StringNotEquals", "Object": "Request", "Key": "key8", "Value": "1136214245"},
{"Op": "NumericEquals", "Object": "Request", "Key": "key20", "Value": "-20"},
{"Op": "NumericNotEquals", "Object": "Request", "Key": "key21", "Value": "+21"},
{"Op": "StringNotLike", "Object": "Request", "Key": "key6", "Value": "val6"},
{"Op": "StringNotEquals", "Object": "Request", "Key": "key18", "Value": "val18"},
{"Op": "StringNotLike", "Object": "Request", "Key": "key19", "Value": "val19"},
{"Op": "StringEqualsIgnoreCase", "Object": "Request", "Key": "key13", "Value": "True"},
{"Op": "StringEquals", "Object": "Request", "Key": "key7", "Value": "1136189045"}
]
}
```
### S3
#### Actions
Each action allows some s3-gw methods, so we must transform action to specific method names
(you can see exact mapping in table in [this file](../iam/converter_s3.go)).
For example the following actions:
```json
{
"Action": [
"s3:DeleteObject",
"iam:CreateUser"
]
}
```
transforms to
```json
{
"Actions": {
"Inverted": false,
"Names": [
"s3:DeleteObject",
"s3:DeleteMultipleObjects",
"iam:CreateUser"
]
}
}
```
As we can see any `iam:*` action transformed as it is. But `s3:*` actions transforms according to
[spec rules](https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazons3.html) and s3-gw
[method names](https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/src/commit/2ab655b909c40db6f7a4e41e07d8b99167f791bd/api/middleware/constants.go#L3-L76).
#### Resources
Resource is transformed as it is:
```json
{
"Resource": [
"arn:aws:s3:::bucket/object"
]
}
```
```json
{
"Resources": {
"Inverted": false,
"Names": [
"arn:aws:s3:::bucket/object"
]
}
}
```
#### Principals
To check user s3-gw uses special condition request property (`Owner`), so when AWS policy contains principal field
it transforms to rule with appropriate condition. To get correct `Owner` property value special user resolver
(`S3Resolver` interface in [converter_s3 file](../iam/converter_s3.go)) must be provided into convert function.
For example such AWS json statement:
```json
{
"Effect": "Allow",
"Action": "*",
"Resource": "*",
"Principal": {
"AWS": "arn:aws:iam::111122223333:user/JohnDoe"
}
}
```
transforms to the following FrostFS rule:
```json
{
"Status": "Allow",
"Actions": {
"Inverted": false,
"Names": [
"*"
]
},
"Resources": {
"Inverted": false,
"Names": [
"*"
]
},
"Any": false,
"Condition": [
{
"Op": "StringEquals",
"Object": "Request",
"Key": "Owner",
"Value": "NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM"
}
]
}
```
### Native
#### Actions
Each action allows some frostfs methods, so we must transform action to specific method names
(you can see exact mapping in table in [this file](../iam/converter_native.go)).
For example the following actions:
```json
{
"Action": [
"s3:DeleteObject",
"iam:CreateUser"
]
}
```
transforms to
```json
{
"Actions": {
"Inverted": false,
"Names": [
"PutObject",
"HeadObject",
"GetObject",
"RangeObject",
"GetContainer",
"DeleteObject"
]
}
}
```
> **Note:** Only subset of s3:* actions can be transformed (exact value you can see in mapping table mentioned before).
> If all provided actions is not applicable converter function returns appropriate error.
Native methods (to which original actions are transformed) depend on which methods are invoked by appropriate s3-gw
[method](https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/src/commit/2ab655b909c40db6f7a4e41e07d8b99167f791bd/api/middleware/constants.go#L3-L76).
So in example above s3-gw during performing DeleteObject methods invokes the following methods:
`["PutObject","HeadObject","GetObject","RangeObject","GetContainer","DeleteObject"]`
#### Resources
To transform resources the following is being performed:
* Bucket name is resoled to container id (by providing `NativeResolver` interface implementation to converter)
* Object name is transformed to condition with special `FilePath` attribute
(that present on every object that was uploaded via s3-gw)
For example, the following AWS policy statement:
```json
{
"Principal": "*",
"Effect": "Allow",
"Action": "*",
"Resource": "arn:aws:s3:::bucket/object"
}
```
transforms to FrostFS native policy rule:
```json
{
"Status": "Allow",
"Actions": {
"Inverted": false,
"Names": [
"*"
]
},
"Resources": {
"Inverted": false,
"Names": [
"native:object//bucket/HFq67qbfhFEiEL7qDXqayo3F78yAvxXSXzwSa2hKM9bH/*",
"native:container//bucket/HFq67qbfhFEiEL7qDXqayo3F78yAvxXSXzwSa2hKM9bH"
]
},
"Any": false,
"Condition": [
{
"Op": "StringLike",
"Object": "Resource",
"Key": "FilePath",
"Value": "object"
}
]
}
```
#### Principals
To check user s3-gw uses special condition request property (`$Actor:publicKey`), so when AWS policy contains principal
field it transforms to rule with appropriate condition. To get correct `$Actor:publicKey` property value
special user resolver (`NativeResolver` interface in [converter_native file](../iam/converter_native.go)) must be
provided into convert function.
For example such AWS json statement:
```json
{
"Effect": "Allow",
"Action": "*",
"Resource": "*",
"Principal": {
"AWS": "arn:aws:iam::111122223333:user/JohnDoe"
}
}
```
transforms to the following FrostFS rule:
```json
{
"Status": "Allow",
"Actions": {
"Inverted": false,
"Names": [
"*"
]
},
"Resources": {
"Inverted": false,
"Names": [
"native:object/*",
"native:container/*"
]
},
"Any": false,
"Condition": [
{
"Op": "StringEquals",
"Object": "Request",
"Key": "$Actor:publicKey",
"Value": "031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a"
}
]
}
```

4
go.mod
View file

@ -1,9 +1,9 @@
module git.frostfs.info/TrueCloudLab/policy-engine
go 1.22
go 1.20
require (
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240409111539-e7a05a49ff45
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0
github.com/google/uuid v1.3.1
github.com/mailru/easyjson v0.7.7
github.com/nspcc-dev/neo-go v0.105.0

29
go.sum
View file

@ -1,17 +1,11 @@
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240409111539-e7a05a49ff45 h1:Tp4I+XOLp3VCJORfxSamQtj3RZNISbaLM4WD5iIzXxg=
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240409111539-e7a05a49ff45/go.mod h1:F/fe1OoIDKr5Bz99q4sriuHDuf3aZefZy9ZsCqEtgxc=
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0 h1:FzurjElUwC7InY9v5rzXReKbfBL5yRJKSWJPq6BKhH0=
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0/go.mod h1:F/fe1OoIDKr5Bz99q4sriuHDuf3aZefZy9ZsCqEtgxc=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb h1:f0BMgIjhZy4lSRHCXFbQst85f5agZAjtDMixQqBWNpc=
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
@ -28,7 +22,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -37,20 +30,16 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/nspcc-dev/go-ordered-json v0.0.0-20231123160306-3374ff1e7a3c h1:OOQeE613BH93ICPq3eke5N78gWNeMjcBWkmD2NKyXVg=
@ -73,17 +62,11 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs=
@ -91,13 +74,11 @@ github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKk
github.com/twmb/murmur3 v1.1.5 h1:i9OLS9fkuLzBXjt6dptlAEyk58fJsSTXbRg3SgVyqgk=
github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c=
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
@ -109,13 +90,11 @@ golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
@ -132,7 +111,6 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -140,7 +118,6 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E=
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -151,7 +128,6 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@ -163,4 +139,3 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=

View file

@ -3,7 +3,6 @@ package iam
import (
"errors"
"fmt"
"net/netip"
"strconv"
"strings"
"time"
@ -63,15 +62,11 @@ const (
s3ActionPutObjectTagging = "s3:PutObjectTagging"
s3ActionPutObjectVersionACL = "s3:PutObjectVersionAcl"
s3ActionPutObjectVersionTagging = "s3:PutObjectVersionTagging"
s3ActionPatchObject = "s3:PatchObject"
)
const (
condKeyAWSPrincipalARN = "aws:PrincipalArn"
condKeyAWSSourceIP = "aws:SourceIp"
condKeyAWSPrincipalTagPrefix = "aws:PrincipalTag/"
condKeyAWSRequestTagPrefix = "aws:RequestTag/"
condKeyAWSResourceTagPrefix = "aws:ResourceTag/"
userClaimTagPrefix = "tag-"
)
@ -185,7 +180,7 @@ func convertToChainCondition(c Conditions) ([]GroupedConditions, error) {
group.Conditions[i] = chain.Condition{
Op: condType,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: transformKey(key),
Value: converted,
}
@ -203,11 +198,6 @@ func transformKey(key string) string {
return fmt.Sprintf(common.PropertyKeyFormatFrostFSIDUserClaim, userClaimTagPrefix+tagName)
}
switch key {
case condKeyAWSSourceIP:
return common.PropertyKeyFrostFSSourceIP
}
return key
}
@ -265,9 +255,13 @@ func getConditionTypeAndConverter(op string) (chain.ConditionType, convertFuncti
case op == CondBool:
return chain.CondStringEqualsIgnoreCase, noConvertFunction, nil
case op == CondIPAddress:
return chain.CondIPAddress, ipConvertFunction, nil
// todo consider using converters
// "203.0.113.0/24" -> "203.0.113.*",
// "2001:DB8:1234:5678::/64" -> "2001:DB8:1234:5678:*"
// or having specific condition type for IP
return chain.CondStringLike, noConvertFunction, nil
case op == CondNotIPAddress:
return chain.CondNotIPAddress, ipConvertFunction, nil
return chain.CondStringNotLike, noConvertFunction, nil
case op == CondSliceContains:
return chain.CondSliceContains, noConvertFunction, nil
default:
@ -308,17 +302,6 @@ func numericConvertFunction(val string) (string, error) {
return "", fmt.Errorf("invalid numeric value: '%s'", val)
}
func ipConvertFunction(val string) (string, error) {
if _, err := netip.ParsePrefix(val); err != nil {
if _, err = netip.ParseAddr(val); err != nil {
return "", err
}
val += "/32"
}
return val, nil
}
func dateConvertFunction(val string) (string, error) {
if _, err := strconv.ParseInt(val, 10, 64); err == nil {
return val, nil

View file

@ -1,7 +1,6 @@
package iam
import (
"errors"
"fmt"
"strings"
@ -29,7 +28,7 @@ var actionToNativeOpMap = map[string][]string{
s3ActionGetBucketPolicyStatus: {native.MethodGetContainer},
s3ActionGetBucketTagging: {native.MethodGetContainer, native.MethodGetObject},
s3ActionGetBucketVersioning: {native.MethodGetContainer, native.MethodGetObject},
s3ActionGetLifecycleConfiguration: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject},
s3ActionGetLifecycleConfiguration: { /*not implemented yet*/ },
s3ActionGetObject: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
s3ActionGetObjectACL: {native.MethodGetContainer, native.MethodGetContainerEACL, native.MethodGetObject, native.MethodHeadObject},
s3ActionGetObjectAttributes: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject},
@ -52,7 +51,7 @@ var actionToNativeOpMap = map[string][]string{
s3ActionPutBucketPolicy: {native.MethodGetContainer},
s3ActionPutBucketTagging: {native.MethodGetContainer, native.MethodGetObject, native.MethodPutObject},
s3ActionPutBucketVersioning: {native.MethodGetContainer, native.MethodGetObject, native.MethodPutObject},
s3ActionPutLifecycleConfiguration: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodPutObject, native.MethodDeleteObject},
s3ActionPutLifecycleConfiguration: { /*not implemented yet*/ },
s3ActionPutObject: {native.MethodGetContainer, native.MethodPutObject, native.MethodGetObject, native.MethodHeadObject, native.MethodRangeObject},
s3ActionPutObjectACL: {native.MethodGetContainer, native.MethodGetContainerEACL, native.MethodSetContainerEACL, native.MethodGetObject, native.MethodHeadObject},
s3ActionPutObjectLegalHold: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject},
@ -60,7 +59,6 @@ var actionToNativeOpMap = map[string][]string{
s3ActionPutObjectTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject},
s3ActionPutObjectVersionACL: {native.MethodGetContainer, native.MethodGetContainerEACL, native.MethodSetContainerEACL, native.MethodGetObject, native.MethodHeadObject},
s3ActionPutObjectVersionTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject},
s3ActionPatchObject: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodPatchObject, native.MethodPutObject, native.MethodRangeObject},
}
var containerNativeOperations = map[string]struct{}{
@ -82,8 +80,6 @@ var objectNativeOperations = map[string]struct{}{
native.MethodHashObject: {},
}
var errConditionKeyNotApplicable = errors.New("condition key is not applicable")
type NativeResolver interface {
GetUserKey(account, name string) (string, error)
GetBucketInfo(bucket string) (*BucketInfo, error)
@ -127,9 +123,6 @@ func ConvertToNativeChain(p Policy, resolver NativeResolver) (*chain.Chain, erro
groupedConditions, err := convertToNativeChainCondition(statement.Conditions, resolver)
if err != nil {
if errors.Is(err, errConditionKeyNotApplicable) {
continue
}
return nil, err
}
splitConditions := splitGroupedConditions(groupedConditions)
@ -216,7 +209,7 @@ func getNativePrincipalsAndConditionFunc(statement Statement, resolver NativeRes
return principals, func(principal string) chain.Condition {
return chain.Condition{
Op: op,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: native.PropertyKeyActorPublicKey,
Value: principal,
}
@ -225,33 +218,18 @@ func getNativePrincipalsAndConditionFunc(statement Statement, resolver NativeRes
func convertToNativeChainCondition(c Conditions, resolver NativeResolver) ([]GroupedConditions, error) {
return convertToChainConditions(c, func(gr GroupedConditions) (GroupedConditions, error) {
res := GroupedConditions{
Conditions: make([]chain.Condition, 0, len(gr.Conditions)),
Any: gr.Any,
}
for i := range gr.Conditions {
switch {
case gr.Conditions[i].Key == condKeyAWSMFAPresent:
return GroupedConditions{}, errConditionKeyNotApplicable
case gr.Conditions[i].Key == condKeyAWSPrincipalARN:
if gr.Conditions[i].Key == condKeyAWSPrincipalARN {
gr.Conditions[i].Key = native.PropertyKeyActorPublicKey
val, err := formPrincipalKey(gr.Conditions[i].Value, resolver)
if err != nil {
return GroupedConditions{}, err
}
gr.Conditions[i].Value = val
res.Conditions = append(res.Conditions, gr.Conditions[i])
case strings.HasPrefix(gr.Conditions[i].Key, condKeyAWSRequestTagPrefix) ||
strings.HasPrefix(gr.Conditions[i].Key, condKeyAWSResourceTagPrefix):
// Tags exist only in S3 requests, so native protocol should not process such conditions.
continue
default:
res.Conditions = append(res.Conditions, gr.Conditions[i])
}
}
return res, nil
return gr, nil
})
}
@ -328,7 +306,7 @@ func formNativeResourceNamesAndConditions(names []string, resolver NativeResolve
Conditions: []chain.Condition{
{
Op: chain.CondStringLike,
Kind: chain.KindResource,
Object: chain.ObjectResource,
Key: PropertyKeyFilePath,
Value: obj,
},

View file

@ -2,14 +2,11 @@ package iam
import (
"fmt"
"strings"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/s3"
)
const condKeyAWSMFAPresent = "aws:MultiFactorAuthPresent"
var actionToS3OpMap = map[string][]string{
s3ActionAbortMultipartUpload: {s3ActionAbortMultipartUpload},
s3ActionCreateBucket: {s3ActionCreateBucket},
@ -32,7 +29,6 @@ var actionToS3OpMap = map[string][]string{
s3ActionPutObjectLegalHold: {s3ActionPutObjectLegalHold},
s3ActionPutObjectRetention: {s3ActionPutObjectRetention},
s3ActionPutObjectTagging: {s3ActionPutObjectTagging},
s3ActionPatchObject: {s3ActionPatchObject},
s3ActionListAllMyBuckets: {"s3:ListBuckets"},
s3ActionListBucket: {"s3:HeadBucket", "s3:GetBucketLocation", "s3:ListObjectsV1", "s3:ListObjectsV2"},
@ -161,7 +157,7 @@ func getS3PrincipalsAndConditionFunc(statement Statement, resolver S3Resolver) (
return principals, func(principal string) chain.Condition {
return chain.Condition{
Op: op,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: s3.PropertyKeyOwner,
Value: principal,
}
@ -171,19 +167,13 @@ func getS3PrincipalsAndConditionFunc(statement Statement, resolver S3Resolver) (
func convertToS3ChainCondition(c Conditions, resolver S3Resolver) ([]GroupedConditions, error) {
return convertToChainConditions(c, func(gr GroupedConditions) (GroupedConditions, error) {
for i := range gr.Conditions {
switch {
case gr.Conditions[i].Key == condKeyAWSPrincipalARN:
if gr.Conditions[i].Key == condKeyAWSPrincipalARN {
gr.Conditions[i].Key = s3.PropertyKeyOwner
val, err := formPrincipalOwner(gr.Conditions[i].Value, resolver)
if err != nil {
return GroupedConditions{}, err
}
gr.Conditions[i].Value = val
case gr.Conditions[i].Key == condKeyAWSMFAPresent:
gr.Conditions[i].Key = s3.PropertyKeyAccessBoxAttrMFA
case strings.HasPrefix(gr.Conditions[i].Key, condKeyAWSResourceTagPrefix):
gr.Conditions[i].Kind = chain.KindResource
}
}

View file

@ -103,13 +103,13 @@ func TestConverters(t *testing.T) {
Condition: []chain.Condition{
{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: s3.PropertyKeyOwner,
Value: mockResolver.users[user],
},
{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "s3:RequestObjectTag/Department",
Value: "Finance",
},
@ -147,7 +147,7 @@ func TestConverters(t *testing.T) {
Condition: []chain.Condition{
{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: native.PropertyKeyActorPublicKey,
Value: mockResolver.users[user],
},
@ -181,7 +181,7 @@ func TestConverters(t *testing.T) {
Condition: []chain.Condition{
{
Op: chain.CondStringNotEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: s3.PropertyKeyOwner,
Value: mockResolver.users[user],
},
@ -226,13 +226,13 @@ func TestConverters(t *testing.T) {
Condition: []chain.Condition{
{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: native.PropertyKeyActorPublicKey,
Value: mockResolver.users[user],
},
{
Op: chain.CondStringLike,
Kind: chain.KindResource,
Object: chain.ObjectResource,
Key: PropertyKeyFilePath,
Value: objName,
},
@ -251,7 +251,7 @@ func TestConverters(t *testing.T) {
}},
Condition: []chain.Condition{{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: native.PropertyKeyActorPublicKey,
Value: mockResolver.users[user],
}},
@ -346,7 +346,7 @@ func TestConverters(t *testing.T) {
Resources: chain.Resources{Names: []string{"*"}},
Condition: []chain.Condition{{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: s3.PropertyKeyOwner,
Value: mockResolver.users[user],
}},
@ -362,7 +362,7 @@ func TestConverters(t *testing.T) {
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}},
Condition: []chain.Condition{{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: native.PropertyKeyActorPublicKey,
Value: mockResolver.users[user],
}},
@ -391,6 +391,8 @@ func TestConvertToChainCondition(t *testing.T) {
CondDateGreaterThan: {"key11": {"2006-01-02T15:04:05-01:00"}},
CondDateGreaterThanEquals: {"key12": {"2006-01-02T15:04:05-03:00"}},
CondBool: {"key13": {"True"}},
CondIPAddress: {"key14": {"val14"}},
CondNotIPAddress: {"key15": {"val15"}},
CondArnEquals: {"key16": {"val16"}},
CondArnLike: {condKeyAWSPrincipalARN: {principal}},
CondArnNotEquals: {"key18": {"val18"}},
@ -409,13 +411,13 @@ func TestConvertToChainCondition(t *testing.T) {
Conditions: []chain.Condition{
{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key1",
Value: "val0",
},
{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key1",
Value: "val1",
},
@ -424,7 +426,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringNotEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key2",
Value: "val2",
}},
@ -432,7 +434,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringEqualsIgnoreCase,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key3",
Value: "val3",
}},
@ -440,7 +442,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringNotEqualsIgnoreCase,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key4",
Value: "val4",
}},
@ -448,7 +450,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringLike,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key5",
Value: "val5",
}},
@ -456,7 +458,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringNotLike,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key6",
Value: "val6",
}},
@ -464,7 +466,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key7",
Value: "1136189045",
}},
@ -472,7 +474,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringNotEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key8",
Value: "1136214245",
}},
@ -480,7 +482,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringLessThan,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key9",
Value: "1136192645",
}},
@ -488,7 +490,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringLessThanEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key10",
Value: "1136203445",
}},
@ -496,7 +498,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringGreaterThan,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key11",
Value: "1136217845",
}},
@ -504,7 +506,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringGreaterThanEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key12",
Value: "1136225045",
}},
@ -512,15 +514,31 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringEqualsIgnoreCase,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key13",
Value: "True",
}},
},
{
Conditions: []chain.Condition{{
Op: chain.CondStringLike,
Object: chain.ObjectRequest,
Key: "key14",
Value: "val14",
}},
},
{
Conditions: []chain.Condition{{
Op: chain.CondStringNotLike,
Object: chain.ObjectRequest,
Key: "key15",
Value: "val15",
}},
},
{
Conditions: []chain.Condition{{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key16",
Value: "val16",
}},
@ -528,7 +546,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringLike,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: condKeyAWSPrincipalARN,
Value: principal,
}},
@ -536,7 +554,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringNotEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key18",
Value: "val18",
}},
@ -544,7 +562,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondStringNotLike,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key19",
Value: "val19",
}},
@ -552,7 +570,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondNumericEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key20",
Value: "-20",
}},
@ -560,7 +578,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondNumericNotEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key21",
Value: "+21",
}},
@ -568,7 +586,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondNumericLessThan,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key22",
Value: "0",
}},
@ -576,7 +594,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondNumericLessThanEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key23",
Value: "23.23",
}},
@ -584,7 +602,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondNumericGreaterThan,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key24",
Value: "-24.24",
}},
@ -592,7 +610,7 @@ func TestConvertToChainCondition(t *testing.T) {
{
Conditions: []chain.Condition{{
Op: chain.CondNumericGreaterThanEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "key25",
Value: "+25.25",
}},
@ -621,114 +639,6 @@ func TestConvertToChainCondition(t *testing.T) {
}
}
func TestIPConditions(t *testing.T) {
t.Run("ip converters", func(t *testing.T) {
for _, tc := range []struct {
ip string
error bool
expected string
}{
{ip: "203.0.113.0/24", expected: "203.0.113.0/24"},
{ip: "203.0.113.1", expected: "203.0.113.1/32"},
{ip: "203.0.113.1/", error: true},
{ip: "203.0.113.1/33", error: true},
{ip: "192.168.0.1/24", expected: "192.168.0.1/24"},
{ip: "10.10.0.1/24", expected: "10.10.0.1/24"},
{ip: "172.16.0.1/24", expected: "172.16.0.1/24"},
{ip: "2001:DB8:1234:5678::/64", expected: "2001:DB8:1234:5678::/64"},
{ip: "2001:DB8:1234:5678::", expected: "2001:DB8:1234:5678::/32"},
{ip: "2001:DB8:1234:5678::/", error: true},
{ip: "2001:DB8:1234:5678::/129", error: true},
{ip: "FC00::/64", expected: "FC00::/64"},
} {
t.Run("", func(t *testing.T) {
actual, err := ipConvertFunction(tc.ip)
if tc.error {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.expected, actual)
})
}
})
t.Run("chain converters", func(t *testing.T) {
policy := `{"Version":"2012-10-17",
"Statement":{"Effect":"Allow","Principal": "*","Action":"s3:*","Resource":"*","Condition": {"IpAddress": {"aws:SourceIp": "203.0.113.0/24"}}}
}`
var p Policy
err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
s3Expected := &chain.Chain{
Rules: []chain.Rule{{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{"s3:*"}},
Resources: chain.Resources{Names: []string{Wildcard}},
Condition: []chain.Condition{{
Op: chain.CondIPAddress,
Kind: chain.KindRequest,
Key: common.PropertyKeyFrostFSSourceIP,
Value: "203.0.113.0/24",
}},
}},
}
s3Chain, err := ConvertToS3Chain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err)
require.Equal(t, s3Expected, s3Chain)
nativeExpected := &chain.Chain{
Rules: []chain.Rule{{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{Wildcard}},
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}},
Condition: []chain.Condition{{
Op: chain.CondIPAddress,
Kind: chain.KindRequest,
Key: common.PropertyKeyFrostFSSourceIP,
Value: "203.0.113.0/24",
}},
}},
}
nativeChain, err := ConvertToNativeChain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err)
require.Equal(t, nativeExpected, nativeChain)
})
t.Run("matching rules", func(t *testing.T) {
policy := `{"Version":"2012-10-17",
"Statement":{"Effect":"Allow","Principal": "*","Action":"s3:*","Resource":"*","Condition": {"IpAddress": {"aws:SourceIp": "203.0.113.0/24"}}}
}`
var p Policy
err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
s3Chain, err := ConvertToS3Chain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err)
s := inmemory.NewInMemory()
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chain.S3, engine.NamespaceTarget(""), s3Chain)
require.NoError(t, err)
req := testutil.NewRequest("s3:CreateBucket", testutil.NewResource(fmt.Sprintf(s3.ResourceFormatS3Bucket, "bkt"), nil),
map[string]string{common.PropertyKeyFrostFSSourceIP: "203.0.113.128"})
status, _, err := s.IsAllowed(chain.S3, engine.NewRequestTargetWithNamespace(""), req)
require.NoError(t, err)
require.Equal(t, chain.Allow.String(), status.String())
req = testutil.NewRequest("s3:CreateBucket", testutil.NewResource(fmt.Sprintf(s3.ResourceFormatS3Bucket, "bkt"), nil),
map[string]string{common.PropertyKeyFrostFSSourceIP: "203.0.114.0"})
status, _, err = s.IsAllowed(chain.S3, engine.NewRequestTargetWithNamespace(""), req)
require.NoError(t, err)
require.Equal(t, chain.NoRuleFound.String(), status.String())
})
}
func TestParsePrincipalARN(t *testing.T) {
for i, tc := range []struct {
principal string
@ -828,12 +738,12 @@ func TestComplexNativeConditions(t *testing.T) {
expectedResource1 := chain.Resources{Names: []string{nativeResource1, nativeResource1cnr}}
expectedResource23 := chain.Resources{Names: []string{nativeResource2, nativeResource2cnr, nativeResource3, nativeResource3cnr}}
user1Condition := chain.Condition{Op: chain.CondStringEquals, Kind: chain.KindRequest, Key: native.PropertyKeyActorPublicKey, Value: mockResolver.users[user1]}
user2Condition := chain.Condition{Op: chain.CondStringEquals, Kind: chain.KindRequest, Key: native.PropertyKeyActorPublicKey, Value: mockResolver.users[user2]}
objectName1Condition := chain.Condition{Op: chain.CondStringLike, Kind: chain.KindResource, Key: PropertyKeyFilePath, Value: objName1}
key1val0Condition := chain.Condition{Op: chain.CondStringEquals, Kind: chain.KindRequest, Key: key1, Value: val0}
key1val1Condition := chain.Condition{Op: chain.CondStringEquals, Kind: chain.KindRequest, Key: key1, Value: val1}
key2val2Condition := chain.Condition{Op: chain.CondStringLike, Kind: chain.KindRequest, Key: key2, Value: val2}
user1Condition := chain.Condition{Op: chain.CondStringEquals, Object: chain.ObjectRequest, Key: native.PropertyKeyActorPublicKey, Value: mockResolver.users[user1]}
user2Condition := chain.Condition{Op: chain.CondStringEquals, Object: chain.ObjectRequest, Key: native.PropertyKeyActorPublicKey, Value: mockResolver.users[user2]}
objectName1Condition := chain.Condition{Op: chain.CondStringLike, Object: chain.ObjectResource, Key: PropertyKeyFilePath, Value: objName1}
key1val0Condition := chain.Condition{Op: chain.CondStringEquals, Object: chain.ObjectRequest, Key: key1, Value: val0}
key1val1Condition := chain.Condition{Op: chain.CondStringEquals, Object: chain.ObjectRequest, Key: key1, Value: val1}
key2val2Condition := chain.Condition{Op: chain.CondStringLike, Object: chain.ObjectRequest, Key: key2, Value: val2}
expected := &chain.Chain{Rules: []chain.Rule{
{
@ -1138,11 +1048,11 @@ func TestComplexS3Conditions(t *testing.T) {
expectedActions := chain.Actions{Names: []string{action, action2}}
expectedResources := chain.Resources{Names: []string{resource1, resource2, resource3}}
user1Condition := chain.Condition{Op: chain.CondStringEquals, Kind: chain.KindRequest, Key: s3.PropertyKeyOwner, Value: mockResolver.users[user1]}
user2Condition := chain.Condition{Op: chain.CondStringEquals, Kind: chain.KindRequest, Key: s3.PropertyKeyOwner, Value: mockResolver.users[user2]}
key1val0Condition := chain.Condition{Op: chain.CondStringEquals, Kind: chain.KindRequest, Key: key1, Value: val0}
key1val1Condition := chain.Condition{Op: chain.CondStringEquals, Kind: chain.KindRequest, Key: key1, Value: val1}
key2val2Condition := chain.Condition{Op: chain.CondStringLike, Kind: chain.KindRequest, Key: key2, Value: val2}
user1Condition := chain.Condition{Op: chain.CondStringEquals, Object: chain.ObjectRequest, Key: s3.PropertyKeyOwner, Value: mockResolver.users[user1]}
user2Condition := chain.Condition{Op: chain.CondStringEquals, Object: chain.ObjectRequest, Key: s3.PropertyKeyOwner, Value: mockResolver.users[user2]}
key1val0Condition := chain.Condition{Op: chain.CondStringEquals, Object: chain.ObjectRequest, Key: key1, Value: val0}
key1val1Condition := chain.Condition{Op: chain.CondStringEquals, Object: chain.ObjectRequest, Key: key1, Value: val1}
key2val2Condition := chain.Condition{Op: chain.CondStringLike, Object: chain.ObjectRequest, Key: key2, Value: val2}
expected := &chain.Chain{Rules: []chain.Rule{
{
@ -1696,36 +1606,27 @@ func TestTagsConditions(t *testing.T) {
}
`
expectedS3Conditions := []chain.Condition{
expectedConditions := []chain.Condition{
{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: fmt.Sprintf(common.PropertyKeyFormatFrostFSIDUserClaim, "tag-department"),
Value: "hr",
},
{
Op: chain.CondStringEquals,
Kind: chain.KindResource,
Object: chain.ObjectRequest,
Key: fmt.Sprintf(s3.PropertyKeyFormatResourceTag, "owner"),
Value: "hr-admin",
},
{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: fmt.Sprintf(s3.PropertyKeyFormatRequestTag, "scope"),
Value: "*",
},
}
expectedNativeConditions := []chain.Condition{
{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Key: fmt.Sprintf(common.PropertyKeyFormatFrostFSIDUserClaim, "tag-department"),
Value: "hr",
},
}
var p Policy
err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
@ -1733,86 +1634,12 @@ func TestTagsConditions(t *testing.T) {
s3Chain, err := ConvertToS3Chain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err)
require.Len(t, s3Chain.Rules, 1)
require.ElementsMatch(t, expectedS3Conditions, s3Chain.Rules[0].Condition)
require.ElementsMatch(t, expectedConditions, s3Chain.Rules[0].Condition)
nativeChain, err := ConvertToNativeChain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err)
require.Len(t, nativeChain.Rules, 1)
require.ElementsMatch(t, expectedNativeConditions, nativeChain.Rules[0].Condition)
}
func TestMFACondition(t *testing.T) {
policy := `
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "*",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
`
expectedConditions := []chain.Condition{
{
Op: chain.CondStringEqualsIgnoreCase,
Kind: chain.KindRequest,
Key: s3.PropertyKeyAccessBoxAttrMFA,
Value: "true",
},
}
var p Policy
err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
s3Chain, err := ConvertToS3Chain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err)
require.Len(t, s3Chain.Rules, 1)
require.ElementsMatch(t, expectedConditions, s3Chain.Rules[0].Condition)
_, err = ConvertToNativeChain(p, newMockUserResolver(nil, nil, ""))
require.ErrorIs(t, err, ErrActionsNotApplicable)
policy = `
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "*",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
`
expectedConditions[0].Value = "false"
err = json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
s3Chain, err = ConvertToS3Chain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err)
require.Len(t, s3Chain.Rules, 1)
require.ElementsMatch(t, expectedConditions, s3Chain.Rules[0].Condition)
_, err = ConvertToNativeChain(p, newMockUserResolver(nil, nil, ""))
require.ErrorIs(t, err, ErrActionsNotApplicable)
require.ElementsMatch(t, expectedConditions, nativeChain.Rules[0].Condition)
}
func requireChainRulesMatch(t *testing.T, expected, actual []chain.Rule) {

View file

@ -2,7 +2,6 @@ package chain
import (
"fmt"
"net/netip"
"strings"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
@ -69,16 +68,18 @@ type Resources struct {
type Condition struct {
Op ConditionType
Kind ConditionKindType
Object ObjectType
Key string
Value string
}
type ConditionKindType byte
type ObjectType byte
const (
KindResource ConditionKindType = iota
KindRequest
ObjectResource ObjectType = iota
ObjectRequest
ContainerResource
ContainerRequest
)
type ConditionType byte
@ -108,9 +109,6 @@ const (
CondNumericGreaterThanEquals
CondSliceContains
CondIPAddress
CondNotIPAddress
)
var condToStr = []struct {
@ -134,8 +132,6 @@ var condToStr = []struct {
{CondNumericGreaterThan, "NumericGreaterThan"},
{CondNumericGreaterThanEquals, "NumericGreaterThanEquals"},
{CondSliceContains, "SliceContains"},
{CondIPAddress, "IPAddress"},
{CondNotIPAddress, "NotIPAddress"},
}
func (c ConditionType) String() string {
@ -157,13 +153,13 @@ func FormCondSliceContainsValue(values []string) string {
func (c *Condition) Match(req resource.Request) bool {
var val string
switch c.Kind {
case KindResource:
switch c.Object {
case ObjectResource:
val = req.Resource().Property(c.Key)
case KindRequest:
case ObjectRequest:
val = req.Property(c.Key)
default:
panic(fmt.Sprintf("unknown condition type: %d", c.Kind))
panic(fmt.Sprintf("unknown condition type: %d", c.Object))
}
switch c.Op {
@ -194,8 +190,6 @@ func (c *Condition) Match(req resource.Request) bool {
case CondNumericEquals, CondNumericNotEquals, CondNumericLessThan, CondNumericLessThanEquals, CondNumericGreaterThan,
CondNumericGreaterThanEquals:
return c.matchNumeric(val)
case CondIPAddress, CondNotIPAddress:
return c.matchIP(val)
}
}
@ -228,27 +222,6 @@ func (c *Condition) matchNumeric(val string) bool {
}
}
func (c *Condition) matchIP(val string) bool {
ipAddr, err := netip.ParseAddr(val)
if err != nil {
return false
}
prefix, err := netip.ParsePrefix(c.Value)
if err != nil {
return false
}
switch c.Op {
default:
panic(fmt.Sprintf("unimplemented: %d", c.Op))
case CondIPAddress:
return prefix.Contains(ipAddr)
case CondNotIPAddress:
return !prefix.Contains(ipAddr)
}
}
func (r *Rule) Match(req resource.Request) (status Status, matched bool) {
found := len(r.Resources.Names) == 0
for i := range r.Resources.Names {

View file

@ -257,8 +257,8 @@ func easyjsonE2758465DecodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain4(in *j
switch key {
case "Op":
(out.Op).UnmarshalEasyJSON(in)
case "Kind":
(out.Kind).UnmarshalEasyJSON(in)
case "Object":
(out.Object).UnmarshalEasyJSON(in)
case "Key":
out.Key = string(in.String())
case "Value":
@ -283,9 +283,9 @@ func easyjsonE2758465EncodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain4(out *
(in.Op).MarshalEasyJSON(out)
}
{
const prefix string = ",\"Kind\":"
const prefix string = ",\"Object\":"
out.RawString(prefix)
(in.Kind).MarshalEasyJSON(out)
(in.Object).MarshalEasyJSON(out)
}
{
const prefix string = ",\"Key\":"

View file

@ -1,8 +1,6 @@
package chain
import (
"fmt"
"strings"
"testing"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource/testutil"
@ -52,7 +50,7 @@ func TestEncodeDecode(t *testing.T) {
require.Equal(t, expected, actual)
}
func TestChainMatch(t *testing.T) {
func TestReturnFirstMatch(t *testing.T) {
ch := Chain{
Rules: []Rule{
{
@ -89,195 +87,9 @@ func TestChainMatch(t *testing.T) {
require.True(t, found)
require.Equal(t, Allow, st)
})
t.Run("unknown match", func(t *testing.T) {
ch.MatchType = MatchType(255)
request := testutil.NewRequest(native.MethodGetObject, resource, nil)
require.PanicsWithValue(t, "unknown MatchType 255", func() {
ch.Match(request)
})
})
t.Run("no rule found", func(t *testing.T) {
ch.MatchType = MatchTypeFirstMatch
request := testutil.NewRequest(native.MethodGetObject, resource, nil)
st, found := ch.Match(request)
require.False(t, found)
require.Equal(t, NoRuleFound, st)
})
}
func TestAnyAllConditionMatch(t *testing.T) {
ch := Chain{
Rules: []Rule{
{
Status: Allow,
Actions: Actions{Names: []string{native.MethodPutObject}},
Resources: Resources{Names: []string{native.ResourceFormatRootContainers}},
Condition: []Condition{
{
Op: CondIPAddress,
Kind: KindRequest,
Key: common.PropertyKeyFrostFSSourceIP,
Value: "192.92.1.1/20",
},
{
Op: CondStringEquals,
Kind: KindRequest,
Key: native.PropertyKeyActorRole,
Value: "owner",
},
{
Op: CondStringEquals,
Kind: KindResource,
Key: native.PropertyKeyObjectID,
Value: "79xGoKYwhyJQhrDNb7bHhY1WCvN6trHJPTjKkw24c6W9",
},
},
},
}}
t.Run("match by all conditions", func(t *testing.T) {
ch.Rules[0].Any = false
resource := testutil.NewResource(native.ResourceFormatRootContainers, map[string]string{
native.PropertyKeyObjectID: "79xGoKYwhyJQhrDNb7bHhY1WCvN6trHJPTjKkw24c6W9",
})
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.92.1.91",
native.PropertyKeyActorRole: "owner",
})
st, found := ch.Match(request)
require.True(t, found)
require.Equal(t, Allow, st)
request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.93.1.91",
})
st, found = ch.Match(request)
require.False(t, found)
require.Equal(t, NoRuleFound, st)
})
t.Run("match by any condition", func(t *testing.T) {
ch.Rules[0].Any = true
resource := testutil.NewResource(native.ResourceFormatRootContainers, map[string]string{
native.PropertyKeyObjectID: "79xGoKYwhyJQhrDNb7bHhY1WCvN6trHJPTjKkw24c6W9",
})
request := testutil.NewRequest(native.MethodPutObject, resource, nil)
st, found := ch.Match(request)
require.True(t, found)
require.Equal(t, Allow, st)
request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
native.PropertyKeyActorRole: "owner",
})
st, found = ch.Match(request)
require.True(t, found)
require.Equal(t, Allow, st)
resource = testutil.NewResource(native.ResourceFormatRootContainers, map[string]string{})
request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.92.1.91",
})
st, found = ch.Match(request)
require.True(t, found)
require.Equal(t, Allow, st)
request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.93.1.91",
})
st, found = ch.Match(request)
require.False(t, found)
require.Equal(t, NoRuleFound, st)
})
}
func TestConditionMatch(t *testing.T) {
t.Run("condition types", func(t *testing.T) {
t.Run("slice condition type", testCondSliceContainsMatch)
t.Run("numeric condition types", testNumericConditionsMatch)
t.Run("string condition types", testStringConiditionsMatch)
t.Run("ip conidition types", testIPConditionMatch)
t.Run("unknown condition type", 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: ConditionType(255),
Kind: KindRequest,
Key: common.PropertyKeyFrostFSSourceIP,
Value: "192.92.1.1/20",
}},
}}}
resource := testutil.NewResource(native.ResourceFormatRootContainers, map[string]string{
native.PropertyKeyObjectID: "79xGoKYwhyJQhrDNb7bHhY1WCvN6trHJPTjKkw24c6W9",
})
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.92.1.91",
native.PropertyKeyActorRole: "owner",
})
require.PanicsWithValue(t, "unimplemented: 255", func() {
ch.Match(request)
})
})
})
t.Run("kind", func(t *testing.T) {
resource := testutil.NewResource(native.ResourceFormatRootContainers, map[string]string{
native.PropertyKeyObjectID: "79xGoKYwhyJQhrDNb7bHhY1WCvN6trHJPTjKkw24c6W9",
})
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.92.1.91",
native.PropertyKeyActorRole: "owner",
})
t.Run("resource", func(t *testing.T) {
cond := Condition{
Op: CondStringEquals,
Kind: KindResource,
Key: native.PropertyKeyObjectID,
Value: "79xGoKYwhyJQhrDNb7bHhY1WCvN6trHJPTjKkw24c6W9",
}
found := cond.Match(request)
require.True(t, found)
})
t.Run("request", func(t *testing.T) {
cond := Condition{
Op: CondStringEquals,
Kind: KindRequest,
Key: native.PropertyKeyActorRole,
Value: "owner",
}
found := cond.Match(request)
require.True(t, found)
})
t.Run("unknown", func(t *testing.T) {
cond := Condition{
Op: CondStringEquals,
Kind: ConditionKindType(255),
Key: native.PropertyKeyActorRole,
Value: "owner",
}
require.PanicsWithValue(t, "unknown condition type: 255", func() {
cond.Match(request)
})
})
})
}
func testCondSliceContainsMatch(t *testing.T) {
func TestCondSliceContainsMatch(t *testing.T) {
propKey := common.PropertyKeyFrostFSIDGroupID
groupID := "1"
@ -287,7 +99,7 @@ func testCondSliceContainsMatch(t *testing.T) {
Resources: Resources{Names: []string{native.ResourceFormatRootContainers}},
Condition: []Condition{{
Op: CondSliceContains,
Kind: KindRequest,
Object: ObjectRequest,
Key: propKey,
Value: groupID,
}},
@ -339,7 +151,7 @@ func testCondSliceContainsMatch(t *testing.T) {
}
}
func testNumericConditionsMatch(t *testing.T) {
func TestNumericConditionsMatch(t *testing.T) {
propKey := s3.PropertyKeyMaxKeys
for _, tc := range []struct {
@ -353,19 +165,19 @@ func testNumericConditionsMatch(t *testing.T) {
conditions: []Condition{
{
Op: CondNumericLessThan,
Kind: KindRequest,
Object: ObjectRequest,
Key: propKey,
Value: "100",
},
{
Op: CondNumericGreaterThan,
Kind: KindRequest,
Object: ObjectRequest,
Key: propKey,
Value: "80",
},
{
Op: CondNumericNotEquals,
Kind: KindRequest,
Object: ObjectRequest,
Key: propKey,
Value: "91",
},
@ -378,19 +190,19 @@ func testNumericConditionsMatch(t *testing.T) {
conditions: []Condition{
{
Op: CondNumericEquals,
Kind: KindRequest,
Object: ObjectRequest,
Key: propKey,
Value: "50",
},
{
Op: CondNumericLessThanEquals,
Kind: KindRequest,
Object: ObjectRequest,
Key: propKey,
Value: "50",
},
{
Op: CondNumericGreaterThanEquals,
Kind: KindRequest,
Object: ObjectRequest,
Key: propKey,
Value: "50",
},
@ -415,428 +227,7 @@ func testNumericConditionsMatch(t *testing.T) {
}
}
func testStringConiditionsMatch(t *testing.T) {
propKey := fmt.Sprintf(common.PropertyKeyFormatFrostFSIDUserClaim, "some-tag")
val := "tag-value"
t.Run(CondStringEquals.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: CondStringEquals,
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)
})
t.Run(CondStringNotEquals.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: CondStringNotEquals,
Kind: KindRequest,
Key: propKey,
Value: val,
}},
}}}
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
propKey: "distort_tag_value" + 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: val,
})
st, found = ch.Match(request)
require.False(t, found)
require.Equal(t, NoRuleFound, st)
})
t.Run(CondStringEqualsIgnoreCase.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: CondStringEqualsIgnoreCase,
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)
})
t.Run(CondStringNotEqualsIgnoreCase.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: CondStringNotEqualsIgnoreCase,
Kind: KindRequest,
Key: propKey,
Value: val,
}},
}}}
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
propKey: strings.ToUpper("distort_tag_value" + 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(val),
})
st, found = ch.Match(request)
require.False(t, found)
require.Equal(t, NoRuleFound, st)
})
t.Run(CondStringLike.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: CondStringLike,
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)
})
t.Run(CondStringNotLike.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: CondStringNotLike,
Kind: KindRequest,
Key: propKey,
Value: "prefix" + 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: "prefix" + val,
})
st, found = ch.Match(request)
require.False(t, found)
require.Equal(t, NoRuleFound, st)
})
t.Run(CondStringLessThan.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: CondStringLessThan,
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)
})
t.Run(CondStringLessThanEquals.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: CondStringLessThanEquals,
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)
})
t.Run(CondStringGreaterThan.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: CondStringGreaterThan,
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)
})
t.Run(CondStringGreaterThanEquals.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: CondStringGreaterThanEquals,
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)
})
}
func testIPConditionMatch(t *testing.T) {
t.Run(CondIPAddress.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: CondIPAddress,
Kind: KindRequest,
Key: common.PropertyKeyFrostFSSourceIP,
Value: "192.92.1.1/20",
}},
}}}
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.92.1.91",
})
st, found := ch.Match(request)
require.True(t, found)
require.Equal(t, Allow, st)
request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.93.1.91",
})
st, found = ch.Match(request)
require.False(t, found)
require.Equal(t, NoRuleFound, st)
})
t.Run(CondNotIPAddress.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: CondNotIPAddress,
Kind: KindRequest,
Key: common.PropertyKeyFrostFSSourceIP,
Value: "192.92.1.1/20",
}},
}}}
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.93.1.91",
})
st, found := ch.Match(request)
require.True(t, found)
require.Equal(t, Allow, st)
request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.92.1.91",
})
st, found = ch.Match(request)
require.False(t, found)
require.Equal(t, NoRuleFound, st)
})
t.Run("invalid ip address condition value", 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: CondIPAddress,
Kind: KindRequest,
Key: common.PropertyKeyFrostFSSourceIP,
Value: "192.92.1.1:33333",
}},
}}}
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.92.1.91",
})
st, found := ch.Match(request)
require.False(t, found)
require.Equal(t, NoRuleFound, st)
})
t.Run("match ip", func(t *testing.T) {
cond := Condition{
Op: CondIPAddress,
Kind: KindRequest,
Key: common.PropertyKeyFrostFSSourceIP,
Value: "192.92.1.1/10",
}
require.NotPanics(t, func() {
cond.matchIP("192.92.1.91")
})
require.PanicsWithValue(t, "unimplemented: 10", func() {
cond.Op = CondNumericEquals
cond.matchIP("192.92.1.91")
})
})
}
func TestInvalidNumericValues(t *testing.T) {
t.Run("invalid request property", func(t *testing.T) {
propKey := s3.PropertyKeyMaxKeys
propValues := []string{"", "invalid"}
@ -880,7 +271,7 @@ func TestInvalidNumericValues(t *testing.T) {
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
condition := Condition{
Op: tc.conditionType,
Kind: KindRequest,
Object: ObjectRequest,
Key: propKey,
Value: "50",
}
@ -893,136 +284,4 @@ func TestInvalidNumericValues(t *testing.T) {
}
})
}
})
t.Run("invalid condition numeric value", func(t *testing.T) {
propKey := s3.PropertyKeyMaxKeys
condition := Condition{
Kind: KindRequest,
Key: propKey,
Value: "invalid",
}
t.Run("match on CondNumericNotEquals", func(t *testing.T) {
condition.Op = CondNumericNotEquals
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{propKey: "50"})
match := condition.Match(request)
require.Equal(t, true, match)
})
t.Run("non-match on non-CondNumericNotEquals", func(t *testing.T) {
condition.Op = CondNumericEquals
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{propKey: "50"})
match := condition.Match(request)
require.Equal(t, false, match)
})
t.Run("match numeric", func(t *testing.T) {
cond := Condition{
Op: CondNumericLessThan,
Kind: KindRequest,
Key: s3.PropertyKeyMaxKeys,
Value: "5",
}
require.NotPanics(t, func() {
cond.matchNumeric("6")
})
require.PanicsWithValue(t, "unimplemented: 0", func() {
cond.Op = CondStringEquals
cond.matchNumeric("10")
})
})
})
}
func TestCondTypeStringification(t *testing.T) {
for _, pair := range []struct {
cond ConditionType
expectedString string
}{
{CondStringEquals, "StringEquals"},
{CondStringNotEquals, "StringNotEquals"},
{CondStringEqualsIgnoreCase, "StringEqualsIgnoreCase"},
{CondStringNotEqualsIgnoreCase, "StringNotEqualsIgnoreCase"},
{CondStringLike, "StringLike"},
{CondStringNotLike, "StringNotLike"},
{CondStringLessThan, "StringLessThan"},
{CondStringLessThanEquals, "StringLessThanEquals"},
{CondStringGreaterThan, "StringGreaterThan"},
{CondStringGreaterThanEquals, "StringGreaterThanEquals"},
{CondNumericEquals, "NumericEquals"},
{CondNumericNotEquals, "NumericNotEquals"},
{CondNumericLessThan, "NumericLessThan"},
{CondNumericLessThanEquals, "NumericLessThanEquals"},
{CondNumericGreaterThan, "NumericGreaterThan"},
{CondNumericGreaterThanEquals, "NumericGreaterThanEquals"},
{CondSliceContains, "SliceContains"},
{CondIPAddress, "IPAddress"},
{CondNotIPAddress, "NotIPAddress"},
{ConditionType(255), "unknown condition type"},
} {
require.Equal(t, pair.expectedString, pair.cond.String())
}
}
func TestRuleMatch(t *testing.T) {
rule := Rule{
Status: Allow,
Actions: Actions{Names: []string{native.MethodPutObject}},
Resources: Resources{Names: []string{native.ResourceFormatRootContainers}},
Condition: []Condition{{
Op: CondIPAddress,
Kind: KindRequest,
Key: common.PropertyKeyFrostFSSourceIP,
Value: "192.92.1.1/20",
}},
}
t.Run("match", func(t *testing.T) {
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.92.1.91",
})
st, found := rule.Match(request)
require.True(t, found)
require.Equal(t, Allow, st)
})
t.Run("not matching resource name", func(t *testing.T) {
resource := testutil.NewResource(fmt.Sprintf(native.ResourceFormatNamespaceContainers, "namespicy"), nil)
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.92.1.91",
})
st, found := rule.Match(request)
require.False(t, found)
require.Equal(t, NoRuleFound, st)
})
t.Run("not matching action", func(t *testing.T) {
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
request := testutil.NewRequest(native.MethodGetObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.92.1.91",
})
st, found := rule.Match(request)
require.False(t, found)
require.Equal(t, NoRuleFound, st)
})
t.Run("not matching condition", func(t *testing.T) {
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
request := testutil.NewRequest(native.MethodGetObject, resource, map[string]string{
common.PropertyKeyFrostFSSourceIP: "192.93.1.91",
})
st, found := rule.Match(request)
require.False(t, found)
require.Equal(t, NoRuleFound, st)
})
}

View file

@ -218,7 +218,7 @@ func marshalCondition(buf []byte, offset int, c Condition) (int, error) {
if err != nil {
return 0, err
}
offset, err = marshal.ByteMarshal(buf, offset, byte(c.Kind))
offset, err = marshal.ByteMarshal(buf, offset, byte(c.Object))
if err != nil {
return 0, err
}
@ -241,7 +241,7 @@ func unmarshalCondition(buf []byte, offset int) (Condition, int, error) {
if err != nil {
return Condition{}, 0, err
}
c.Kind = ConditionKindType(obV)
c.Object = ObjectType(obV)
c.Key, offset, err = marshal.StringUnmarshal(buf, offset)
if err != nil {

View file

@ -28,6 +28,31 @@ func TestInvalidChainData(t *testing.T) {
require.Error(t, ch.UnmarshalBinary([]byte("\x00\x00:aws:iam::namespace:group/so\x82\x82\x82\x82\x82\x82u\x82")))
}
func FuzzUnmarshal(f *testing.F) {
for _, id := range generateTestIDs() {
for _, rules := range generateTestRules() {
for _, matchType := range generateTestMatchTypes() {
chain := Chain{
ID: id,
Rules: rules,
MatchType: matchType,
}
data, err := chain.MarshalBinary()
require.NoError(f, err)
f.Add(data)
}
}
}
f.Fuzz(func(t *testing.T, data []byte) {
var ch Chain
require.NotPanics(t, func() {
_ = ch.UnmarshalBinary(data)
})
})
}
func performMarshalTest(t *testing.T, id ID, r []Rule, mt MatchType) {
chain := Chain{
ID: id,
@ -179,28 +204,28 @@ func generateTestConditions() [][]Condition {
for _, ot := range generateObjectTypes() {
result[2] = append(result[2], Condition{
Op: ct,
Kind: ot,
Object: ot,
Key: "",
Value: "",
})
result[2] = append(result[2], Condition{
Op: ct,
Kind: ot,
Object: ot,
Key: "key",
Value: "",
})
result[2] = append(result[2], Condition{
Op: ct,
Kind: ot,
Object: ot,
Key: "",
Value: "value",
})
result[2] = append(result[2], Condition{
Op: ct,
Kind: ot,
Object: ot,
Key: "key",
Value: "value",
})
@ -232,10 +257,10 @@ func generateTestConditionTypes() []ConditionType {
}
}
func generateObjectTypes() []ConditionKindType {
return []ConditionKindType{
KindResource,
KindRequest,
func generateObjectTypes() []ObjectType {
return []ObjectType{
ObjectResource,
ObjectRequest,
}
}

View file

@ -1,13 +0,0 @@
//go:build gofuzz
// +build gofuzz
package chain
func DoFuzzChainUnmarshalBinary(data []byte) int {
var ch Chain
err := ch.UnmarshalBinary(data)
if err != nil {
return 0
}
return 1
}

View file

@ -1,34 +0,0 @@
//go:build gofuzz
// +build gofuzz
package chain
import (
"testing"
"github.com/stretchr/testify/require"
)
func FuzzUnmarshal(f *testing.F) {
for _, id := range generateTestIDs() {
for _, rules := range generateTestRules() {
for _, matchType := range generateTestMatchTypes() {
chain := Chain{
ID: id,
Rules: rules,
MatchType: matchType,
}
data, err := chain.MarshalBinary()
require.NoError(f, err)
f.Add(data)
}
}
}
f.Fuzz(func(t *testing.T, data []byte) {
require.NotPanics(t, func() {
DoFuzzChainUnmarshalBinary(data)
})
})
}

View file

@ -29,11 +29,11 @@ var statusToJSONValue = []struct {
}
var objectTypeToJSONValue = []struct {
t ConditionKindType
t ObjectType
str string
}{
{KindRequest, "Request"},
{KindResource, "Resource"},
{ObjectRequest, "Request"},
{ObjectResource, "Resource"},
}
func (mt MatchType) MarshalEasyJSON(w *jwriter.Writer) {
@ -90,7 +90,7 @@ func (st *Status) UnmarshalEasyJSON(l *jlexer.Lexer) {
*st = Status(v)
}
func (ot ConditionKindType) MarshalEasyJSON(w *jwriter.Writer) {
func (ot ObjectType) MarshalEasyJSON(w *jwriter.Writer) {
for _, p := range objectTypeToJSONValue {
if p.t == ot {
w.String(p.str)
@ -100,7 +100,7 @@ func (ot ConditionKindType) MarshalEasyJSON(w *jwriter.Writer) {
w.String(strconv.FormatUint(uint64(ot), 10))
}
func (ot *ConditionKindType) UnmarshalEasyJSON(l *jlexer.Lexer) {
func (ot *ObjectType) UnmarshalEasyJSON(l *jlexer.Lexer) {
str := l.String()
for _, p := range objectTypeToJSONValue {
if p.str == str {
@ -114,7 +114,7 @@ func (ot *ConditionKindType) UnmarshalEasyJSON(l *jlexer.Lexer) {
l.AddError(fmt.Errorf("failed to parse object type: %w", err))
return
}
*ot = ConditionKindType(v)
*ot = ObjectType(v)
}
func (ct ConditionType) MarshalEasyJSON(w *jwriter.Writer) {

View file

@ -69,7 +69,7 @@ func TestJsonEnums(t *testing.T) {
Condition: []Condition{
{
Op: CondStringEquals,
Kind: KindRequest,
Object: ObjectRequest,
Key: native.PropertyKeyActorRole,
Value: native.PropertyValueContainerRoleOthers,
},
@ -88,7 +88,7 @@ func TestJsonEnums(t *testing.T) {
Condition: []Condition{
{
Op: CondStringNotLike,
Kind: KindResource,
Object: ObjectResource,
Key: native.PropertyKeyObjectType,
Value: "regular",
},
@ -99,7 +99,7 @@ func TestJsonEnums(t *testing.T) {
Condition: []Condition{
{
Op: ConditionType(255),
Kind: ConditionKindType(128),
Object: ObjectType(128),
},
},
},

View file

@ -20,7 +20,7 @@
"Condition": [
{
"Op": "StringEquals",
"Kind": "Request",
"Object": "Request",
"Key": "$Actor:role",
"Value": "others"
}
@ -44,7 +44,7 @@
"Condition": [
{
"Op": "StringNotLike",
"Kind": "Resource",
"Object": "Resource",
"Key": "$Object:objectType",
"Value": "regular"
}
@ -64,7 +64,7 @@
"Condition": [
{
"Op": "255",
"Kind": "128",
"Object": "128",
"Key": "",
"Value": ""
}

View file

@ -42,36 +42,43 @@ func (dr *defaultChainRouter) checkLocal(name chain.Name, rt RequestTarget, r re
if dr.local == nil {
return
}
var hasAllow bool
var ruleFounds []bool
for _, target := range rt.Targets() {
status, ruleFound, err = dr.matchLocalOverrides(name, target, r)
if err != nil || ruleFound && status != chain.Allow {
return
}
hasAllow = hasAllow || ruleFound
ruleFounds = append(ruleFounds, ruleFound)
}
if hasAllow {
return chain.Allow, true, nil
status = chain.NoRuleFound
for _, ruleFound = range ruleFounds {
if ruleFound {
status = chain.Allow
break
}
return chain.NoRuleFound, false, nil
}
return
}
func (dr *defaultChainRouter) checkMorph(name chain.Name, rt RequestTarget, r resource.Request) (status chain.Status, ruleFound bool, err error) {
var hasAllow bool
var ruleFounds []bool
for _, target := range rt.Targets() {
status, ruleFound, err = dr.matchMorphRuleChains(name, target, r)
if err != nil || ruleFound && status != chain.Allow {
return
}
hasAllow = hasAllow || ruleFound
ruleFounds = append(ruleFounds, ruleFound)
}
if hasAllow {
return chain.Allow, true, nil
status = chain.NoRuleFound
for _, ruleFound = range ruleFounds {
if ruleFound {
status = chain.Allow
break
}
return chain.NoRuleFound, false, nil
}
return
}
func (dr *defaultChainRouter) matchLocalOverrides(name chain.Name, target Target, r resource.Request) (status chain.Status, ruleFound bool, err error) {

View file

@ -31,49 +31,6 @@ func TestAddRootOverrides(t *testing.T) {
require.Equal(t, string(id), string(res[0].ID))
}
func TestInmemory_MultipleTargets(t *testing.T) {
const op = "ape::test::op"
targets := []engine.Target{
engine.NamespaceTarget("ns1"),
engine.ContainerTarget("cnr1"),
engine.GroupTarget("group1"),
engine.UserTarget("user1"),
}
req := resourcetest.NewRequest(op, resourcetest.NewResource("r", nil), nil)
target := engine.NewRequestTargetExtended("ns1", "cnr1", "user1", []string{"group1"})
for _, tt := range targets {
t.Run("morph", func(t *testing.T) {
s := NewInMemoryLocalOverrides()
s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, tt, &chain.Chain{
Rules: []chain.Rule{{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{op}},
}},
})
status, found, err := s.IsAllowed(chain.Ingress, target, req)
require.NoError(t, err)
require.True(t, found)
require.Equal(t, chain.Allow, status)
})
t.Run("override", func(t *testing.T) {
s := NewInMemoryLocalOverrides()
s.LocalStorage().AddOverride(chain.Ingress, tt, &chain.Chain{
Rules: []chain.Rule{{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{op}},
}},
})
status, found, err := s.IsAllowed(chain.Ingress, target, req)
require.NoError(t, err)
require.True(t, found)
require.Equal(t, chain.Allow, status)
})
}
}
func TestInmemory(t *testing.T) {
const (
object = "native::object::abc/xyz"
@ -113,13 +70,13 @@ func TestInmemory(t *testing.T) {
Condition: []chain.Condition{
{
Op: chain.CondStringNotLike,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "SourceIP",
Value: "10.1.1.*",
},
{
Op: chain.CondStringNotEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "Actor",
Value: actor1,
},
@ -155,13 +112,13 @@ func TestInmemory(t *testing.T) {
Condition: []chain.Condition{
{
Op: chain.CondStringEquals,
Kind: chain.KindResource,
Object: chain.ObjectResource,
Key: "Department",
Value: "HR",
},
{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Object: chain.ObjectRequest,
Key: "Actor",
Value: actor2,
},

View file

@ -6,7 +6,6 @@ import (
"math/big"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient"
"git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/policy"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
@ -14,7 +13,6 @@ import (
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
neoinvoker "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
@ -28,10 +26,6 @@ var (
// ContractStorage is the interface to manage chain rules within Policy contract.
type ContractStorage struct {
hash util.Uint160
actor ContractStorageActor
contractInterface *client.Contract
}
@ -39,49 +33,23 @@ var _ engine.MorphRuleChainStorage = (*ContractStorage)(nil)
// ContractStorageReader is the interface to read data from Policy contract.
type ContractStorageReader struct {
hash util.Uint160
invoker ContractStorageInvoker
contractReaderInterface *client.ContractReader
}
type ContractStorageActor interface {
client.Actor
GetRPCInvoker() neoinvoker.RPCInvoke
}
var _ engine.MorphRuleChainStorageReader = (*ContractStorageReader)(nil)
func NewContractStorage(actor ContractStorageActor, contract util.Uint160) *ContractStorage {
func NewContractStorage(actor client.Actor, contract util.Uint160) *ContractStorage {
return &ContractStorage{
hash: contract,
actor: actor,
contractInterface: client.New(actor, contract),
}
}
type contractStorageActorImpl struct {
client.Actor
rpcActor actor.RPCActor
}
var _ ContractStorageActor = &contractStorageActorImpl{}
func (c *contractStorageActorImpl) GetRPCInvoker() neoinvoker.RPCInvoke {
return c.rpcActor
}
// NewContractStorageWithSimpleActor constructs core actor from `rpcActor`.
//
// Note: NewContractStorageWithSimpleActor is appropriate only for call-only-once cases (for example, in CLIs). Otherwise, it is unsafe,
// because core actor may use invalidated `rpcActor` if some connection errors occurred.
func NewContractStorageWithSimpleActor(rpcActor actor.RPCActor, acc *wallet.Account, contract util.Uint160) (*ContractStorage, error) {
act, err := actor.NewSimple(rpcActor, acc)
if err != nil {
return nil, fmt.Errorf("failed to create simple actor: %w", err)
}
return NewContractStorage(&contractStorageActorImpl{Actor: act, rpcActor: rpcActor}, contract), nil
return NewContractStorage(act, contract), nil
}
func (s *ContractStorage) AddMorphRuleChain(name chain.Name, target engine.Target, c *chain.Chain) (txHash util.Uint256, vub uint32, err error) {
@ -130,26 +98,17 @@ func (s *ContractStorage) RemoveMorphRuleChainsByTarget(name chain.Name, target
return
}
func listChains(name chain.Name, target engine.Target, rpcInvoker neoinvoker.RPCInvoke, hash util.Uint160) ([]*chain.Chain, error) {
func (s *ContractStorage) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
kind, err := policyKind(target.Type)
if err != nil {
return nil, err
}
const (
method = "iteratorChainsByPrefix"
batchSize = neoinvoker.DefaultIteratorResultItems
)
inv := neoinvoker.New(rpcInvoker, nil)
params := []any{
big.NewInt(int64(kind)), target.Name, []byte(name),
}
items, err := commonclient.ReadIteratorItems(inv, batchSize, hash, method, params...)
items, err := s.contractInterface.ListChainsByPrefix(big.NewInt(int64(kind)), target.Name, []byte(name))
if err != nil {
return nil, fmt.Errorf("read items error: %w", err)
return nil, err
}
var chains []*chain.Chain
for _, item := range items {
serialized, err := bytesFromStackItem(item)
@ -162,11 +121,8 @@ func listChains(name chain.Name, target engine.Target, rpcInvoker neoinvoker.RPC
}
chains = append(chains, c)
}
return chains, nil
}
func (s *ContractStorage) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
return listChains(name, target, s.actor.GetRPCInvoker(), s.hash)
return chains, nil
}
func (s *ContractStorage) ListTargetsIterator(targetType engine.TargetType) (uuid.UUID, result.Iterator, error) {
@ -185,21 +141,37 @@ func (s *ContractStorage) SetAdmin(addr util.Uint160) (util.Uint256, uint32, err
return s.contractInterface.SetAdmin(addr)
}
type ContractStorageInvoker interface {
client.Invoker
GetRPCInvoker() neoinvoker.RPCInvoke
}
func NewContractStorageReader(inv ContractStorageInvoker, contract util.Uint160) *ContractStorageReader {
func NewContractStorageReader(inv client.Invoker, contract util.Uint160) *ContractStorageReader {
return &ContractStorageReader{
hash: contract,
invoker: inv,
contractReaderInterface: client.NewReader(inv, contract),
}
}
func (s *ContractStorageReader) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
return listChains(name, target, s.invoker.GetRPCInvoker(), s.hash)
kind, err := policyKind(target.Type)
if err != nil {
return nil, err
}
items, err := s.contractReaderInterface.ListChainsByPrefix(big.NewInt(int64(kind)), target.Name, []byte(name))
if err != nil {
return nil, err
}
var chains []*chain.Chain
for _, item := range items {
serialized, err := bytesFromStackItem(item)
if err != nil {
return nil, err
}
c := new(chain.Chain)
if err := c.DecodeBytes(serialized); err != nil {
return nil, err
}
chains = append(chains, c)
}
return chains, nil
}
func (s *ContractStorageReader) GetAdmin() (util.Uint160, error) {

View file

@ -3,9 +3,5 @@ package common
const (
PropertyKeyFrostFSIDGroupID = "frostfsid:groupID"
PropertyKeyFrostFSSourceIP = "frostfs:sourceIP"
PropertyKeyFormatFrostFSIDUserClaim = "frostfsid:userClaim/%s"
PropertyKeyFrostFSXHeader = "frostfs:xheader/%s"
)

View file

@ -8,7 +8,6 @@ const (
MethodSearchObject = "SearchObject"
MethodRangeObject = "RangeObject"
MethodHashObject = "HashObject"
MethodPatchObject = "PatchObject"
MethodPutContainer = "PutContainer"
MethodDeleteContainer = "DeleteContainer"

View file

@ -11,9 +11,6 @@ const (
PropertyKeyFormatResourceTag = "aws:ResourceTag/%s"
PropertyKeyFormatRequestTag = "aws:RequestTag/%s"
PropertyKeyAccessBoxAttrMFA = "AccessBox-Attribute/IAM-MFA"
PropertyKeyFormatAccessBoxAttr = "AccessBox-Attribute/%s"
ResourceFormatS3All = "arn:aws:s3:::*"
ResourceFormatS3Bucket = "arn:aws:s3:::%s"
ResourceFormatS3BucketObjects = "arn:aws:s3:::%s/*"