Compare commits

..

1 commit

Author SHA1 Message Date
aarifullin
5f7b8458ba [#3] policy: Introduce policy contract interface wrapper
Signed-off-by: Airat Arifullin <aarifullin@yadro.com>
2023-12-06 12:25:57 +03:00
35 changed files with 374 additions and 3100 deletions

View file

@ -16,6 +16,6 @@ jobs:
go-version: '1.21' go-version: '1.21'
- name: Run commit format checker - name: Run commit format checker
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v3 uses: https://git.frostfs.info/TrueCloudLab/dco-go@v2
with: with:
from: 'origin/${{ github.event.pull_request.base.ref }}' from: 'origin/${{ github.event.pull_request.base.ref }}'

2
.gitattributes vendored
View file

@ -1,4 +1,2 @@
/**/*.pb.go -diff -merge /**/*.pb.go -diff -merge
/**/*.pb.go linguist-generated=true /**/*.pb.go linguist-generated=true
/**/*_easyjson.go -diff -merge
/**/*_easyjson.go linguist-generated=true

View file

@ -5,8 +5,6 @@ TMP_DIR := .cache
OUTPUT_LINT_DIR ?= $(shell pwd)/bin OUTPUT_LINT_DIR ?= $(shell pwd)/bin
LINT_VERSION ?= 1.55.1 LINT_VERSION ?= 1.55.1
LINT_DIR = $(OUTPUT_LINT_DIR)/golangci-lint-$(LINT_VERSION)-v$(TRUECLOUDLAB_LINT_VERSION) 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)
# Run all code formatters # Run all code formatters
fmts: fmt imports fmts: fmt imports
@ -62,15 +60,3 @@ staticcheck-install:
# Run staticcheck # Run staticcheck
staticcheck-run: staticcheck-run:
@staticcheck ./... @staticcheck ./...
easyjson-install:
@rm -rf $(EASYJSON_DIR)
@mkdir -p $(EASYJSON_DIR)
@GOBIN=$(EASYJSON_DIR) go install github.com/mailru/easyjson/...@$(EASYJSON_VERSION)
generate:
@if [ ! -d "$(EASYJSON_DIR)" ]; then \
make easyjson-install; \
fi
find ./ -name "_easyjson.go" -exec rm -rf {} \;
$(EASYJSON_DIR)/easyjson ./pkg/chain/chain.go

View file

@ -1,20 +0,0 @@
# Resource
From the point of the access policy engine, a resource is an object to which a request is being performed.
This can be an object in a container within a namespace, or all objects in a container,
or all containers within the root namespace etc.
A resource can be viewed from two sides:
- As part of a [request](../pkg/resource/resource.go). In this case a resource has a name and properties.
- As part of rule [chain](../pkg/chain/chain.go): a resource has just a name.
## Resource name
A resource name must have a such format that can be processed by a chain router that matches a request
either with local overrides or with rules within policy contract to get if this request is allowed to be performed.
The main idea of this format is for the chain router to match by full name (`native:object//cnrID/objID`) or
wildcard (`native:object//cnrID/*`).
Check out formats that are defined in the schema: [native formats](../schema/native/consts.go), [s3 formats](../schema/s3/consts.go).
You should validate a resource name using [util](../schema/native/util/validation.go) before instantiating a request or
before putting it to either to local override storage or the policy contract storage.

37
go.mod
View file

@ -3,33 +3,26 @@ module git.frostfs.info/TrueCloudLab/policy-engine
go 1.20 go 1.20
require ( require (
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0 git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231129062201-a1b61d394958
github.com/google/uuid v1.3.1 github.com/mr-tron/base58 v1.2.0
github.com/mailru/easyjson v0.7.7 github.com/nspcc-dev/neo-go v0.103.0
github.com/nspcc-dev/neo-go v0.105.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 )
require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/golang-lru v0.6.0 // indirect
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 // indirect
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20231020160724-c3955f87d1b5 // indirect
github.com/nspcc-dev/rfc6979 v0.2.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/text v0.13.0 // indirect
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/hashicorp/golang-lru v0.6.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/nspcc-dev/go-ordered-json v0.0.0-20231123160306-3374ff1e7a3c // indirect
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20231127165613-b35f351f0ba0 // indirect
github.com/nspcc-dev/rfc6979 v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 // indirect
github.com/twmb/murmur3 v1.1.5 // indirect
go.etcd.io/bbolt v1.3.8 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

130
go.sum
View file

@ -1,141 +1,43 @@
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0 h1:FzurjElUwC7InY9v5rzXReKbfBL5yRJKSWJPq6BKhH0= git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231129062201-a1b61d394958 h1:X9yPizADIhD3K/gdKVCthlAnf9aQ3UJJGnZgIwwixRQ=
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0/go.mod h1:F/fe1OoIDKr5Bz99q4sriuHDuf3aZefZy9ZsCqEtgxc= git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231129062201-a1b61d394958/go.mod h1:rQWdsG18NaiFvkJpMguJev913KD/yleHaniRBkUyt0o=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb h1:f0BMgIjhZy4lSRHCXFbQst85f5agZAjtDMixQqBWNpc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/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= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
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/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4= 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/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
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/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 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/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= github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 h1:n4ZaFCKt1pQJd7PXoMJabZWK9ejjbLOVrkl/lOUmshg=
github.com/nspcc-dev/go-ordered-json v0.0.0-20231123160306-3374ff1e7a3c/go.mod h1:79bEUDEviBHJMFV6Iq6in57FEOCMcRhfQnfaf0ETA5U= github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22/go.mod h1:79bEUDEviBHJMFV6Iq6in57FEOCMcRhfQnfaf0ETA5U=
github.com/nspcc-dev/neo-go v0.105.0 h1:vtNZYFEFySK8zRDhLzQYha849VzWrcKezlnq/oNQg/w= github.com/nspcc-dev/neo-go v0.103.0 h1:UVyWPhzZdfYFG35ORP3FRDLh8J/raRQ6m8SptDdlgfM=
github.com/nspcc-dev/neo-go v0.105.0/go.mod h1:6pchIHg5okeZO955RxpTh5q0sUI0vtpgPM6Q+no1rlI= github.com/nspcc-dev/neo-go v0.103.0/go.mod h1:x+wmcYqpZYJwLp1l/pHZrqNp3RSWlkMymWGDij3/OPo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20231127165613-b35f351f0ba0 h1:N+dMIBmteXjJpkH6UZ7HmNftuFxkqszfGLbhsEctnv0= github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20231020160724-c3955f87d1b5 h1:09CpI5uwsxb1EeFPIKQRwwWlfCmDD/Dwwh01lPiQScM=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20231127165613-b35f351f0ba0/go.mod h1:J/Mk6+nKeKSW4wygkZQFLQ6SkLOSGX5Ga0RuuuktEag= github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20231020160724-c3955f87d1b5/go.mod h1:J/Mk6+nKeKSW4wygkZQFLQ6SkLOSGX5Ga0RuuuktEag=
github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE= github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE=
github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/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_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 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/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs= github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs=
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
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/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 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= 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.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
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=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
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/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/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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/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=
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/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=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=

View file

@ -50,16 +50,12 @@ const (
CondArnLike string = "ArnLike" CondArnLike string = "ArnLike"
CondArnNotEquals string = "ArnNotEquals" CondArnNotEquals string = "ArnNotEquals"
CondArnNotLike string = "ArnNotLike" CondArnNotLike string = "ArnNotLike"
// Custom condition operators.
CondSliceContains string = "SliceContains"
) )
const ( const (
arnIAMPrefix = "arn:aws:iam::" arnIAMPrefix = "arn:aws:iam::"
s3ResourcePrefix = "arn:aws:s3:::" s3ResourcePrefix = "arn:aws:s3:::"
s3ActionPrefix = "s3:" s3ActionPrefix = "s3:"
iamActionPrefix = "iam:"
) )
var ( var (
@ -71,9 +67,6 @@ var (
// ErrInvalidActionFormat occurs when action has unknown/unsupported format. // ErrInvalidActionFormat occurs when action has unknown/unsupported format.
ErrInvalidActionFormat = errors.New("invalid action format") ErrInvalidActionFormat = errors.New("invalid action format")
// ErrActionsNotApplicable occurs when failed to convert any actions.
ErrActionsNotApplicable = errors.New("actions not applicable")
) )
type formPrincipalConditionFunc func(string) chain.Condition type formPrincipalConditionFunc func(string) chain.Condition
@ -197,8 +190,6 @@ func getConditionTypeAndConverter(op string) (chain.ConditionType, convertFuncti
return chain.CondStringLike, noConvertFunction, nil return chain.CondStringLike, noConvertFunction, nil
case op == CondNotIPAddress: case op == CondNotIPAddress:
return chain.CondStringNotLike, noConvertFunction, nil return chain.CondStringNotLike, noConvertFunction, nil
case op == CondSliceContains:
return chain.CondSliceContains, noConvertFunction, nil
default: default:
return 0, nil, fmt.Errorf("unsupported condition operator: '%s'", op) return 0, nil, fmt.Errorf("unsupported condition operator: '%s'", op)
} }
@ -252,38 +243,53 @@ func parsePrincipalAsIAMUser(principal string) (account string, user string, err
return account, user, nil return account, user, nil
} }
func validateResource(resource string) error { func parseResourceAsS3ARN(resource string) (bucket string, object string, err error) {
if resource == Wildcard { if resource == Wildcard {
return nil return Wildcard, Wildcard, nil
} }
if !strings.HasPrefix(resource, s3ResourcePrefix) && !strings.HasPrefix(resource, arnIAMPrefix) { if !strings.HasPrefix(resource, s3ResourcePrefix) {
return ErrInvalidResourceFormat return "", "", ErrInvalidResourceFormat
} }
index := strings.IndexByte(resource, Wildcard[0]) // iam arn format arn:aws:s3:::<bucket-name>/<object-name>
if index != -1 && index != utf8.RuneCountInString(resource)-1 { s3Resource := strings.TrimPrefix(resource, s3ResourcePrefix)
return ErrInvalidResourceFormat sepIndex := strings.Index(s3Resource, "/")
if sepIndex < 0 {
return s3Resource, Wildcard, nil
} }
return nil bucket = s3Resource[:sepIndex]
object = s3Resource[sepIndex+1:]
if len(object) == 0 {
return bucket, Wildcard, nil
}
if bucket == Wildcard && object != Wildcard {
return "", "", ErrInvalidResourceFormat
}
return bucket, object, nil
} }
func validateAction(action string) error { func parseActionAsS3Action(action string) (string, error) {
if action == Wildcard { if action == Wildcard {
return nil return Wildcard, nil
} }
if !strings.HasPrefix(action, s3ActionPrefix) && !strings.HasPrefix(action, iamActionPrefix) { if !strings.HasPrefix(action, s3ActionPrefix) {
return ErrInvalidActionFormat return "", ErrInvalidActionFormat
} }
index := strings.IndexByte(action, Wildcard[0]) // iam arn format :s3:<action-name>
if index != -1 && index != utf8.RuneCountInString(action)-1 { s3Action := strings.TrimPrefix(action, s3ActionPrefix)
return ErrInvalidActionFormat
index := strings.IndexByte(s3Action, Wildcard[0])
if index != -1 && index != utf8.RuneCountInString(s3Action)-1 {
return "", ErrInvalidActionFormat
} }
return nil return s3Action, nil
} }
func splitGroupedConditions(groupedConditions []GroupedConditions) [][]chain.Condition { func splitGroupedConditions(groupedConditions []GroupedConditions) [][]chain.Condition {

View file

@ -1,8 +1,8 @@
package iam package iam
import ( import (
"errors"
"fmt" "fmt"
"strings"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native" "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
@ -10,61 +10,28 @@ import (
const PropertyKeyFilePath = "FilePath" const PropertyKeyFilePath = "FilePath"
var supportedActionToNativeOpMap = map[string][]string{ // ErrActionsNotApplicable occurs when failed to convert any actions.
supportedS3NativeActionDeleteObject: {native.MethodDeleteObject, native.MethodHeadObject}, var ErrActionsNotApplicable = errors.New("actions not applicable")
supportedS3NativeActionHeadObject: {native.MethodHeadObject},
supportedS3NativeActionGetObject: {native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
supportedS3NativeActionPutObject: {native.MethodPutObject},
supportedS3NativeActionListBucket: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
supportedS3NativeActionCreateBucket: {native.MethodPutContainer}, var actionToOpMap = map[string][]string{
supportedS3NativeActionDeleteBucket: {native.MethodDeleteContainer}, supportedS3ActionDeleteObject: {native.MethodDeleteObject},
supportedS3NativeActionListAllMyBucket: {native.MethodListContainers}, supportedS3ActionGetObject: {native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
supportedS3NativeActionPutBucketACL: {native.MethodSetContainerEACL}, supportedS3ActionHeadObject: {native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
supportedS3NativeActionGetBucketACL: {native.MethodGetContainerEACL}, supportedS3ActionPutObject: {native.MethodPutObject},
} supportedS3ActionListBucket: {native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
var containerNativeOperations = map[string]struct{}{
native.MethodPutContainer: {},
native.MethodDeleteContainer: {},
native.MethodGetContainer: {},
native.MethodListContainers: {},
native.MethodSetContainerEACL: {},
native.MethodGetContainerEACL: {},
}
var objectNativeOperations = map[string]struct{}{
native.MethodGetObject: {},
native.MethodPutObject: {},
native.MethodHeadObject: {},
native.MethodDeleteObject: {},
native.MethodSearchObject: {},
native.MethodRangeObject: {},
native.MethodHashObject: {},
} }
const ( const (
supportedS3NativeActionDeleteObject = "s3:DeleteObject" supportedS3ActionDeleteObject = "DeleteObject"
supportedS3NativeActionGetObject = "s3:GetObject" supportedS3ActionGetObject = "GetObject"
supportedS3NativeActionHeadObject = "s3:HeadObject" supportedS3ActionHeadObject = "HeadObject"
supportedS3NativeActionPutObject = "s3:PutObject" supportedS3ActionPutObject = "PutObject"
supportedS3NativeActionListBucket = "s3:ListBucket" supportedS3ActionListBucket = "ListBucket"
supportedS3NativeActionCreateBucket = "s3:CreateBucket"
supportedS3NativeActionDeleteBucket = "s3:DeleteBucket"
supportedS3NativeActionListAllMyBucket = "s3:ListAllMyBuckets"
supportedS3NativeActionPutBucketACL = "s3:PutBucketAcl"
supportedS3NativeActionGetBucketACL = "s3:GetBucketAcl"
) )
type NativeResolver interface { type NativeResolver interface {
GetUserKey(account, name string) (string, error) GetUserKey(account, name string) (string, error)
GetBucketInfo(bucket string) (*BucketInfo, error) GetBucketCID(bucket string) (string, error)
}
type BucketInfo struct {
Namespace string
Container string
} }
func ConvertToNativeChain(p Policy, resolver NativeResolver) (*chain.Chain, error) { func ConvertToNativeChain(p Policy, resolver NativeResolver) (*chain.Chain, error) {
@ -88,7 +55,7 @@ func ConvertToNativeChain(p Policy, resolver NativeResolver) (*chain.Chain, erro
} }
resource, resourceInverted := statement.resource() resource, resourceInverted := statement.resource()
groupedResources, err := formNativeResourceNamesAndConditions(resource, resolver, getActionTypes(nativeActions)) groupedResources, err := formNativeResourceNamesAndConditions(resource, resolver)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -107,12 +74,7 @@ func ConvertToNativeChain(p Policy, resolver NativeResolver) (*chain.Chain, erro
for _, groupedResource := range groupedResources { for _, groupedResource := range groupedResources {
for _, principal := range principals { for _, principal := range principals {
for _, conditions := range splitConditions { for _, conditions := range splitConditions {
var principalCondition []chain.Condition ruleConditions := append([]chain.Condition{principalCondFn(principal)}, groupedResource.Conditions...)
if principal != Wildcard {
principalCondition = []chain.Condition{principalCondFn(principal)}
}
ruleConditions := append(principalCondition, groupedResource.Conditions...)
r := chain.Rule{ r := chain.Rule{
Status: status, Status: status,
@ -136,23 +98,6 @@ func ConvertToNativeChain(p Policy, resolver NativeResolver) (*chain.Chain, erro
return &engineChain, nil return &engineChain, nil
} }
func getActionTypes(nativeActions []string) ActionTypes {
var res ActionTypes
for _, action := range nativeActions {
if res.Object && res.Container {
break
}
_, isObj := objectNativeOperations[action]
_, isCnr := containerNativeOperations[action]
res.Object = isObj || action == Wildcard
res.Container = isCnr || action == Wildcard
}
return res
}
func getNativePrincipalsAndConditionFunc(statement Statement, resolver NativeResolver) ([]string, formPrincipalConditionFunc, error) { func getNativePrincipalsAndConditionFunc(statement Statement, resolver NativeResolver) ([]string, formPrincipalConditionFunc, error) {
var principals []string var principals []string
var op chain.ConditionType var op chain.ConditionType
@ -210,67 +155,35 @@ type GroupedResources struct {
Conditions []chain.Condition Conditions []chain.Condition
} }
type ActionTypes struct { func formNativeResourceNamesAndConditions(names []string, resolver NativeResolver) ([]GroupedResources, error) {
Object bool
Container bool
}
func formNativeResourceNamesAndConditions(names []string, resolver NativeResolver, actionTypes ActionTypes) ([]GroupedResources, error) {
if !actionTypes.Object && !actionTypes.Container {
return nil, ErrActionsNotApplicable
}
res := make([]GroupedResources, 0, len(names)) res := make([]GroupedResources, 0, len(names))
var combined []string var combined []string
for _, resource := range names { for i := range names {
if err := validateResource(resource); err != nil { bkt, obj, err := parseResourceAsS3ARN(names[i])
return nil, err
}
if resource == Wildcard {
res = res[:0]
return append(res, formWildcardNativeResource(actionTypes)), nil
}
if !strings.HasPrefix(resource, s3ResourcePrefix) {
continue
}
var bkt, obj string
s3Resource := strings.TrimPrefix(resource, s3ResourcePrefix)
if s3Resource == Wildcard {
res = res[:0]
return append(res, formWildcardNativeResource(actionTypes)), nil
}
if sepIndex := strings.Index(s3Resource, "/"); sepIndex < 0 {
bkt = s3Resource
} else {
bkt = s3Resource[:sepIndex]
obj = s3Resource[sepIndex+1:]
if len(obj) == 0 {
obj = Wildcard
}
}
bktInfo, err := resolver.GetBucketInfo(bkt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if obj == Wildcard && actionTypes.Object { // this corresponds to arn:aws:s3:::BUCKET/ or arn:aws:s3:::BUCKET/* if bkt == Wildcard {
combined = append(combined, fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, bktInfo.Namespace, bktInfo.Container)) res = res[:0]
continue return append(res, GroupedResources{Names: []string{native.ResourceFormatAllObjects}}), nil
} }
if obj == "" && actionTypes.Container { // this corresponds to arn:aws:s3:::BUCKET
combined = append(combined, fmt.Sprintf(native.ResourceFormatNamespaceContainer, bktInfo.Namespace, bktInfo.Container)) cnrID, err := resolver.GetBucketCID(bkt)
if err != nil {
return nil, err
}
resource := fmt.Sprintf(native.ResourceFormatRootContainerObjects, cnrID)
if obj == Wildcard {
combined = append(combined, resource)
continue continue
} }
res = append(res, GroupedResources{ res = append(res, GroupedResources{
Names: []string{fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, bktInfo.Namespace, bktInfo.Container)}, Names: []string{resource},
Conditions: []chain.Condition{ Conditions: []chain.Condition{
{ {
Op: chain.CondStringLike, Op: chain.CondStringLike,
@ -289,18 +202,6 @@ func formNativeResourceNamesAndConditions(names []string, resolver NativeResolve
return res, nil return res, nil
} }
func formWildcardNativeResource(actionTypes ActionTypes) GroupedResources {
groupedNames := make([]string, 0, 2)
if actionTypes.Object {
groupedNames = append(groupedNames, native.ResourceFormatAllObjects)
}
if actionTypes.Container {
groupedNames = append(groupedNames, native.ResourceFormatAllContainers)
}
return GroupedResources{Names: groupedNames}
}
func formNativePrincipal(principal []string, resolver NativeResolver) ([]string, error) { func formNativePrincipal(principal []string, resolver NativeResolver) ([]string, error) {
res := make([]string, len(principal)) res := make([]string, len(principal))
@ -331,24 +232,15 @@ func formPrincipalKey(principal string, resolver NativeResolver) (string, error)
func formNativeActionNames(names []string) ([]string, error) { func formNativeActionNames(names []string) ([]string, error) {
res := make([]string, 0, len(names)) res := make([]string, 0, len(names))
for _, action := range names { for i := range names {
if err := validateAction(action); err != nil { action, err := parseActionAsS3Action(names[i])
if err != nil {
return nil, err return nil, err
} }
if action == Wildcard { if action == Wildcard {
return []string{Wildcard}, nil return []string{Wildcard}, nil
} }
res = append(res, actionToOpMap[action]...)
if !strings.HasPrefix(action, s3ActionPrefix) {
continue
}
if strings.TrimPrefix(action, s3ActionPrefix) == Wildcard {
return []string{Wildcard}, nil
}
res = append(res, supportedActionToNativeOpMap[action]...)
} }
return res, nil return res, nil

View file

@ -7,76 +7,6 @@ import (
"git.frostfs.info/TrueCloudLab/policy-engine/schema/s3" "git.frostfs.info/TrueCloudLab/policy-engine/schema/s3"
) )
var specialActionToS3OpMap = map[string][]string{
specialS3ActionsListAllMyBuckets: {"s3:ListBuckets"},
specialS3ActionsListBucket: {"s3:HeadBucket", "s3:GetBucketLocation", "s3:ListObjectsV1", "s3:ListObjectsV2"},
specialS3ActionsListBucketVersions: {"s3:ListBucketObjectVersions"},
specialS3ActionsListBucketMultipartUploads: {"s3:ListMultipartUploads"},
specialS3ActionsGetBucketObjectLockConfiguration: {"s3:GetBucketObjectLockConfig"},
specialS3ActionsGetEncryptionConfiguration: {"s3:GetBucketEncryption"},
specialS3ActionsGetLifecycleConfiguration: {"s3:GetBucketLifecycle"},
specialS3ActionsGetBucketACL: {"s3:GetBucketACL"},
specialS3ActionsGetBucketCORS: {"s3:GetBucketCors"},
specialS3ActionsPutBucketTagging: {"s3:PutBucketTagging", "s3:DeleteBucketTagging"},
specialS3ActionsPutBucketObjectLockConfiguration: {"s3:PutBucketObjectLockConfig"},
specialS3ActionsPutEncryptionConfiguration: {"s3:PutBucketEncryption", "s3:DeleteBucketEncryption"},
specialS3ActionsPutLifecycleConfiguration: {"s3:PutBucketLifecycle", "s3:DeleteBucketLifecycle"},
specialS3ActionsPutBucketACL: {"s3:PutBucketACL"},
specialS3ActionsPutBucketCORS: {"s3:PutBucketCors", "s3:DeleteBucketCors"},
specialS3ActionsDeleteBucketCORS: {"s3:DeleteBucketCors"},
specialS3ActionsListMultipartUploadParts: {"s3:ListParts"},
specialS3ActionsGetObjectACL: {"s3:GetObjectACL"},
specialS3ActionsGetObject: {"s3:GetObject", "s3:HeadObject"},
specialS3ActionsGetObjectVersion: {"s3:GetObject", "s3:HeadObject"},
specialS3ActionsGetObjectVersionACL: {"s3:GetObjectACL"},
specialS3ActionsGetObjectVersionAttributes: {"s3:GetObjectAttributes"},
specialS3ActionsGetObjectVersionTagging: {"s3:GetObjectTagging"},
specialS3ActionsPutObjectACL: {"s3:PutObjectACL"},
specialS3ActionsPutObjectVersionACL: {"s3:PutObjectACL"},
specialS3ActionsPutObjectVersionTagging: {"s3:PutObjectTagging"},
specialS3ActionsPutObject: {
"s3:PutObject", "s3:PostObject", "s3:CopyObject",
"s3:UploadPart", "s3:UploadPartCopy", "s3:CreateMultipartUpload", "s3:CompleteMultipartUpload",
},
specialS3ActionsDeleteObjectVersionTagging: {"s3:DeleteObjectTagging"},
specialS3ActionsDeleteObject: {"s3:DeleteObject", "s3:DeleteMultipleObjects"},
specialS3ActionsDeleteObjectVersion: {"s3:DeleteObject", "s3:DeleteMultipleObjects"},
}
const (
specialS3ActionsListAllMyBuckets = "s3:ListAllMyBuckets"
specialS3ActionsListBucket = "s3:ListBucket"
specialS3ActionsListBucketVersions = "s3:ListBucketVersions"
specialS3ActionsListBucketMultipartUploads = "s3:ListBucketMultipartUploads"
specialS3ActionsGetBucketObjectLockConfiguration = "s3:GetBucketObjectLockConfiguration"
specialS3ActionsGetEncryptionConfiguration = "s3:GetEncryptionConfiguration"
specialS3ActionsGetLifecycleConfiguration = "s3:GetLifecycleConfiguration"
specialS3ActionsGetBucketACL = "s3:GetBucketAcl"
specialS3ActionsGetBucketCORS = "s3:GetBucketCORS"
specialS3ActionsPutBucketTagging = "s3:PutBucketTagging"
specialS3ActionsPutBucketObjectLockConfiguration = "s3:PutBucketObjectLockConfiguration"
specialS3ActionsPutEncryptionConfiguration = "s3:PutEncryptionConfiguration"
specialS3ActionsPutLifecycleConfiguration = "s3:PutLifecycleConfiguration"
specialS3ActionsPutBucketACL = "s3:PutBucketAcl"
specialS3ActionsPutBucketCORS = "s3:PutBucketCORS"
specialS3ActionsDeleteBucketCORS = "s3:DeleteBucketCORS"
specialS3ActionsListMultipartUploadParts = "s3:ListMultipartUploadParts"
specialS3ActionsGetObjectACL = "s3:GetObjectAcl"
specialS3ActionsGetObject = "s3:GetObject"
specialS3ActionsGetObjectVersion = "s3:GetObjectVersion"
specialS3ActionsGetObjectVersionACL = "s3:GetObjectVersionAcl"
specialS3ActionsGetObjectVersionAttributes = "s3:GetObjectVersionAttributes"
specialS3ActionsGetObjectVersionTagging = "s3:GetObjectVersionTagging"
specialS3ActionsPutObjectACL = "s3:PutObjectAcl"
specialS3ActionsPutObjectVersionACL = "s3:PutObjectVersionAcl"
specialS3ActionsPutObjectVersionTagging = "s3:PutObjectVersionTagging"
specialS3ActionsPutObject = "s3:PutObject"
specialS3ActionsDeleteObjectVersionTagging = "s3:DeleteObjectVersionTagging"
specialS3ActionsDeleteObject = "s3:DeleteObject"
specialS3ActionsDeleteObjectVersion = "s3:DeleteObjectVersion"
)
type S3Resolver interface { type S3Resolver interface {
GetUserAddress(account, user string) (string, error) GetUserAddress(account, user string) (string, error)
} }
@ -91,21 +21,19 @@ func ConvertToS3Chain(p Policy, resolver S3Resolver) (*chain.Chain, error) {
for _, statement := range p.Statement { for _, statement := range p.Statement {
status := formStatus(statement) status := formStatus(statement)
actions, actionInverted := statement.action() action, actionInverted := statement.action()
s3Actions, err := formS3ActionNames(actions) s3Actions, err := formS3ActionNames(action)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ruleAction := chain.Actions{Inverted: actionInverted, Names: s3Actions} ruleAction := chain.Actions{Inverted: actionInverted, Names: s3Actions}
if len(ruleAction.Names) == 0 {
continue
}
resources, resourceInverted := statement.resource() resource, resourceInverted := statement.resource()
if err := validateS3ResourceNames(resources); err != nil { s3Resources, err := formS3ResourceNames(resource)
if err != nil {
return nil, err return nil, err
} }
ruleResource := chain.Resources{Inverted: resourceInverted, Names: resources} ruleResource := chain.Resources{Inverted: resourceInverted, Names: s3Resources}
groupedConditions, err := convertToS3ChainCondition(statement.Conditions, resolver) groupedConditions, err := convertToS3ChainCondition(statement.Conditions, resolver)
if err != nil { if err != nil {
@ -120,26 +48,17 @@ func ConvertToS3Chain(p Policy, resolver S3Resolver) (*chain.Chain, error) {
for _, principal := range principals { for _, principal := range principals {
for _, conditions := range splitConditions { for _, conditions := range splitConditions {
var principalCondition []chain.Condition
if principal != Wildcard {
principalCondition = []chain.Condition{principalCondFn(principal)}
}
r := chain.Rule{ r := chain.Rule{
Status: status, Status: status,
Actions: ruleAction, Actions: ruleAction,
Resources: ruleResource, Resources: ruleResource,
Condition: append(principalCondition, conditions...), Condition: append([]chain.Condition{principalCondFn(principal)}, conditions...),
} }
engineChain.Rules = append(engineChain.Rules, r) engineChain.Rules = append(engineChain.Rules, r)
} }
} }
} }
if len(engineChain.Rules) == 0 {
return nil, ErrActionsNotApplicable
}
return &engineChain, nil return &engineChain, nil
} }
@ -222,32 +141,31 @@ func formPrincipalOwner(principal string, resolver S3Resolver) (string, error) {
return address, nil return address, nil
} }
func validateS3ResourceNames(names []string) error { func formS3ResourceNames(names []string) ([]string, error) {
res := make([]string, len(names))
for i := range names { for i := range names {
if err := validateResource(names[i]); err != nil { bkt, obj, err := parseResourceAsS3ARN(names[i])
return err if err != nil {
}
}
return nil
}
func formS3ActionNames(names []string) ([]string, error) {
res := make([]string, 0, len(names))
for _, action := range names {
if err := validateAction(action); err != nil {
return nil, err return nil, err
} }
if action == Wildcard { if bkt == Wildcard {
return []string{Wildcard}, nil res[i] = bkt
continue
} }
if actions, ok := specialActionToS3OpMap[action]; ok { res[i] = bkt + "/" + obj
res = append(res, actions...) }
} else {
res = append(res, action) return res, nil
}
func formS3ActionNames(names []string) ([]string, error) {
var err error
res := make([]string, len(names))
for i := range names {
if res[i], err = parseActionAsS3Action(names[i]); err != nil {
return nil, err
} }
} }

View file

@ -18,22 +18,21 @@ import (
type mockUserResolver struct { type mockUserResolver struct {
users map[string]string users map[string]string
containers map[string]string buckets map[string]string
namespace string
} }
func newMockUserResolver(accountUsers []string, buckets []string, namespace string) *mockUserResolver { func newMockUserResolver(accountUsers []string, buckets []string) *mockUserResolver {
userMap := make(map[string]string, len(accountUsers)) userMap := make(map[string]string, len(accountUsers))
for _, user := range accountUsers { for _, user := range accountUsers {
userMap[user] = user + "/resolvedValue" userMap[user] = user + "/resolvedValue"
} }
containerMap := make(map[string]string, len(buckets)) bucketMap := make(map[string]string, len(buckets))
for _, bkt := range buckets { for _, bkt := range buckets {
containerMap[bkt] = bkt + "/resolvedValues" bucketMap[bkt] = bkt + "/resolvedValues"
} }
return &mockUserResolver{users: userMap, containers: containerMap, namespace: namespace} return &mockUserResolver{users: userMap, buckets: bucketMap}
} }
func (m *mockUserResolver) GetUserAddress(account, user string) (string, error) { func (m *mockUserResolver) GetUserAddress(account, user string) (string, error) {
@ -54,13 +53,13 @@ func (m *mockUserResolver) GetUserKey(account, user string) (string, error) {
return key, nil return key, nil
} }
func (m *mockUserResolver) GetBucketInfo(bkt string) (*BucketInfo, error) { func (m *mockUserResolver) GetBucketCID(bkt string) (string, error) {
cnr, ok := m.containers[bkt] cnrID, ok := m.buckets[bkt]
if !ok { if !ok {
return nil, errors.New("not found") return "", errors.New("not found")
} }
return &BucketInfo{Container: cnr, Namespace: m.namespace}, nil return cnrID, nil
} }
func TestConverters(t *testing.T) { func TestConverters(t *testing.T) {
@ -70,11 +69,10 @@ func TestConverters(t *testing.T) {
principal := "arn:aws:iam::" + namespace + ":user/" + userName principal := "arn:aws:iam::" + namespace + ":user/" + userName
bktName := "DOC-EXAMPLE-BUCKET" bktName := "DOC-EXAMPLE-BUCKET"
objName := "object-name" objName := "object-name"
resource := fmt.Sprintf(s3.ResourceFormatS3BucketObjects, bktName) resource := bktName + "/*"
s3GetObjectAction := "s3:GetObject" action := "PutObject"
s3HeadObjectAction := "s3:HeadObject"
mockResolver := newMockUserResolver([]string{user}, []string{bktName}, namespace) mockResolver := newMockUserResolver([]string{user}, []string{bktName})
t.Run("valid policy", func(t *testing.T) { t.Run("valid policy", func(t *testing.T) {
p := Policy{ p := Policy{
@ -84,8 +82,8 @@ func TestConverters(t *testing.T) {
AWSPrincipalType: {principal}, AWSPrincipalType: {principal},
}, },
Effect: AllowEffect, Effect: AllowEffect,
Action: []string{s3GetObjectAction}, Action: []string{"s3:PutObject"},
Resource: []string{resource}, Resource: []string{"arn:aws:s3:::" + resource},
Conditions: map[string]Condition{ Conditions: map[string]Condition{
CondStringEquals: { CondStringEquals: {
"s3:RequestObjectTag/Department": {"Finance"}, "s3:RequestObjectTag/Department": {"Finance"},
@ -97,7 +95,7 @@ func TestConverters(t *testing.T) {
expected := &chain.Chain{Rules: []chain.Rule{ expected := &chain.Chain{Rules: []chain.Rule{
{ {
Status: chain.Allow, Status: chain.Allow,
Actions: chain.Actions{Names: []string{s3GetObjectAction, s3HeadObjectAction}}, Actions: chain.Actions{Names: []string{action}},
Resources: chain.Resources{Names: []string{resource}}, Resources: chain.Resources{Names: []string{resource}},
Condition: []chain.Condition{ Condition: []chain.Condition{
{ {
@ -130,15 +128,15 @@ func TestConverters(t *testing.T) {
}, },
Effect: AllowEffect, Effect: AllowEffect,
Action: []string{"s3:PutObject"}, Action: []string{"s3:PutObject"},
Resource: []string{resource}, Resource: []string{"arn:aws:s3:::" + resource},
}}, }},
} }
expected := &chain.Chain{Rules: []chain.Rule{ expected := &chain.Chain{Rules: []chain.Rule{
{ {
Status: chain.Allow, Status: chain.Allow,
Actions: chain.Actions{Names: []string{native.MethodPutObject}}, Actions: chain.Actions{Names: []string{action}},
Resources: chain.Resources{Names: []string{fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, namespace, mockResolver.containers[bktName])}}, Resources: chain.Resources{Names: []string{fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.buckets[bktName])}},
Condition: []chain.Condition{ Condition: []chain.Condition{
{ {
Op: chain.CondStringEquals, Op: chain.CondStringEquals,
@ -163,15 +161,15 @@ func TestConverters(t *testing.T) {
AWSPrincipalType: {principal}, AWSPrincipalType: {principal},
}, },
Effect: DenyEffect, Effect: DenyEffect,
NotAction: []string{s3GetObjectAction}, NotAction: []string{"s3:PutObject"},
NotResource: []string{resource}, NotResource: []string{"arn:aws:s3:::" + resource},
}}, }},
} }
expected := &chain.Chain{Rules: []chain.Rule{ expected := &chain.Chain{Rules: []chain.Rule{
{ {
Status: chain.AccessDenied, Status: chain.AccessDenied,
Actions: chain.Actions{Inverted: true, Names: []string{s3GetObjectAction, s3HeadObjectAction}}, Actions: chain.Actions{Inverted: true, Names: []string{action}},
Resources: chain.Resources{Inverted: true, Names: []string{resource}}, Resources: chain.Resources{Inverted: true, Names: []string{resource}},
Condition: []chain.Condition{ Condition: []chain.Condition{
{ {
@ -189,7 +187,7 @@ func TestConverters(t *testing.T) {
require.Equal(t, expected, s3Chain) require.Equal(t, expected, s3Chain)
}) })
t.Run("valid native policy map action", func(t *testing.T) { t.Run("valid policy map get action", func(t *testing.T) {
p := Policy{ p := Policy{
Version: "2012-10-17", Version: "2012-10-17",
Statement: []Statement{{ Statement: []Statement{{
@ -197,20 +195,17 @@ func TestConverters(t *testing.T) {
AWSPrincipalType: {principal}, AWSPrincipalType: {principal},
}, },
Effect: DenyEffect, Effect: DenyEffect,
Action: []string{"s3:DeleteObject", "s3:DeleteBucket"}, NotAction: []string{"s3:GetObject"},
Resource: []string{ NotResource: []string{"arn:aws:s3:::" + bktName + "/" + objName},
fmt.Sprintf(s3.ResourceFormatS3BucketObject, bktName, objName),
fmt.Sprintf(s3.ResourceFormatS3Bucket, bktName),
},
}}, }},
} }
expected := &chain.Chain{Rules: []chain.Rule{ expected := &chain.Chain{Rules: []chain.Rule{
{ {
Status: chain.AccessDenied, Status: chain.AccessDenied,
Actions: chain.Actions{Names: []string{native.MethodDeleteObject, native.MethodHeadObject, native.MethodDeleteContainer}}, Actions: chain.Actions{Inverted: true, Names: actionToOpMap["GetObject"]},
Resources: chain.Resources{Names: []string{ Resources: chain.Resources{Inverted: true, Names: []string{
fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, namespace, mockResolver.containers[bktName]), fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.buckets[bktName]),
}}, }},
Condition: []chain.Condition{ Condition: []chain.Condition{
{ {
@ -227,19 +222,6 @@ func TestConverters(t *testing.T) {
}, },
}, },
}, },
{
Status: chain.AccessDenied,
Actions: chain.Actions{Names: []string{native.MethodDeleteObject, native.MethodHeadObject, native.MethodDeleteContainer}},
Resources: chain.Resources{Names: []string{
fmt.Sprintf(native.ResourceFormatNamespaceContainer, namespace, mockResolver.containers[bktName]),
}},
Condition: []chain.Condition{{
Op: chain.CondStringEquals,
Object: chain.ObjectRequest,
Key: native.PropertyKeyActorPublicKey,
Value: mockResolver.users[user],
}},
},
}} }}
nativeChain, err := ConvertToNativeChain(p, mockResolver) nativeChain, err := ConvertToNativeChain(p, mockResolver)
@ -296,66 +278,6 @@ func TestConverters(t *testing.T) {
_, err := ConvertToNativeChain(p, mockResolver) _, err := ConvertToNativeChain(p, mockResolver)
require.Error(t, err) require.Error(t, err)
}) })
t.Run("invalid policy (missing s3 actions)", func(t *testing.T) {
p := Policy{
Version: "2012-10-17",
Statement: []Statement{{
Principal: map[PrincipalType][]string{
AWSPrincipalType: {principal},
},
Effect: AllowEffect,
Resource: []string{"arn:aws:s3:::" + resource},
}},
}
_, err := ConvertToS3Chain(p, mockResolver)
require.Error(t, err)
})
t.Run("valid mixed iam/s3 actions", func(t *testing.T) {
p := Policy{
Version: "2012-10-17",
Statement: []Statement{{
Principal: map[PrincipalType][]string{AWSPrincipalType: {principal}},
Effect: AllowEffect,
Action: []string{"s3:DeleteObject", "iam:*"},
Resource: []string{"*"},
}},
}
s3Expected := &chain.Chain{Rules: []chain.Rule{{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{"s3:DeleteObject", "s3:DeleteMultipleObjects", "iam:*"}},
Resources: chain.Resources{Names: []string{"*"}},
Condition: []chain.Condition{{
Op: chain.CondStringEquals,
Object: chain.ObjectRequest,
Key: s3.PropertyKeyOwner,
Value: mockResolver.users[user],
}},
}}}
s3Chain, err := ConvertToS3Chain(p, mockResolver)
require.NoError(t, err)
require.Equal(t, s3Expected, s3Chain)
nativeExpected := &chain.Chain{Rules: []chain.Rule{{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{native.MethodDeleteObject, native.MethodHeadObject}},
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects}},
Condition: []chain.Condition{{
Op: chain.CondStringEquals,
Object: chain.ObjectRequest,
Key: native.PropertyKeyActorPublicKey,
Value: mockResolver.users[user],
}},
}}}
nativeChain, err := ConvertToNativeChain(p, mockResolver)
require.NoError(t, err)
require.Equal(t, nativeExpected, nativeChain)
})
} }
func TestConvertToChainCondition(t *testing.T) { func TestConvertToChainCondition(t *testing.T) {
@ -622,10 +544,10 @@ func TestComplexNativeConditions(t *testing.T) {
key1, key2 := "key1", "key2" key1, key2 := "key1", "key2"
val0, val1, val2 := "val0", "val1", "val2" val0, val1, val2 := "val0", "val1", "val2"
mockResolver := newMockUserResolver([]string{user1, user2}, []string{bktName1, bktName2, bktName3}, "") mockResolver := newMockUserResolver([]string{user1, user2}, []string{bktName1, bktName2, bktName3})
nativeResource1 := fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.containers[bktName1]) nativeResource1 := fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.buckets[bktName1])
nativeResource2 := fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.containers[bktName2]) nativeResource2 := fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.buckets[bktName2])
nativeResource3 := fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.containers[bktName3]) nativeResource3 := fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.buckets[bktName3])
p := Policy{ p := Policy{
Version: "2012-10-17", Version: "2012-10-17",
@ -644,7 +566,7 @@ func TestComplexNativeConditions(t *testing.T) {
} }
expectedStatus := chain.AccessDenied expectedStatus := chain.AccessDenied
expectedActions := chain.Actions{Names: supportedActionToNativeOpMap["s3:"+action]} expectedActions := chain.Actions{Names: actionToOpMap[action]}
expectedResource1 := chain.Resources{Names: []string{nativeResource1}} expectedResource1 := chain.Resources{Names: []string{nativeResource1}}
expectedResource23 := chain.Resources{Names: []string{nativeResource2, nativeResource3}} expectedResource23 := chain.Resources{Names: []string{nativeResource2, nativeResource3}}
@ -761,7 +683,7 @@ func TestComplexNativeConditions(t *testing.T) {
{ {
name: "bucket resource1, all conditions matched", name: "bucket resource1, all conditions matched",
action: action, action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName2], "some-oid"), resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName2], "some-oid"),
resourceMap: map[string]string{ resourceMap: map[string]string{
PropertyKeyFilePath: "any-object-name", PropertyKeyFilePath: "any-object-name",
}, },
@ -775,7 +697,7 @@ func TestComplexNativeConditions(t *testing.T) {
{ {
name: "bucket resource3, all conditions matched", name: "bucket resource3, all conditions matched",
action: action, action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName3], "some-oid"), resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName3], "some-oid"),
resourceMap: map[string]string{ resourceMap: map[string]string{
PropertyKeyFilePath: "any-object-name", PropertyKeyFilePath: "any-object-name",
}, },
@ -789,7 +711,7 @@ func TestComplexNativeConditions(t *testing.T) {
{ {
name: "bucket resource, user condition mismatched", name: "bucket resource, user condition mismatched",
action: action, action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName2], "some-oid"), resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName2], "some-oid"),
resourceMap: map[string]string{ resourceMap: map[string]string{
PropertyKeyFilePath: "any-object-name", PropertyKeyFilePath: "any-object-name",
}, },
@ -802,7 +724,7 @@ func TestComplexNativeConditions(t *testing.T) {
{ {
name: "bucket resource, key2 condition mismatched", name: "bucket resource, key2 condition mismatched",
action: action, action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName3], "some-oid"), resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName3], "some-oid"),
resourceMap: map[string]string{ resourceMap: map[string]string{
PropertyKeyFilePath: "any-object-name", PropertyKeyFilePath: "any-object-name",
}, },
@ -816,7 +738,7 @@ func TestComplexNativeConditions(t *testing.T) {
{ {
name: "bucket resource, key1 condition mismatched", name: "bucket resource, key1 condition mismatched",
action: action, action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName2], "some-oid"), resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName2], "some-oid"),
resourceMap: map[string]string{ resourceMap: map[string]string{
PropertyKeyFilePath: "any-object-name", PropertyKeyFilePath: "any-object-name",
}, },
@ -829,7 +751,7 @@ func TestComplexNativeConditions(t *testing.T) {
{ {
name: "bucket/object resource, all conditions matched", name: "bucket/object resource, all conditions matched",
action: action, action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName1], "some-oid"), resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName1], "some-oid"),
resourceMap: map[string]string{ resourceMap: map[string]string{
PropertyKeyFilePath: objName1, PropertyKeyFilePath: objName1,
}, },
@ -843,7 +765,7 @@ func TestComplexNativeConditions(t *testing.T) {
{ {
name: "bucket/object resource, user condition mismatched", name: "bucket/object resource, user condition mismatched",
action: action, action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName1], "some-oid"), resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName1], "some-oid"),
resourceMap: map[string]string{ resourceMap: map[string]string{
PropertyKeyFilePath: objName1, PropertyKeyFilePath: objName1,
}, },
@ -857,7 +779,7 @@ func TestComplexNativeConditions(t *testing.T) {
{ {
name: "bucket/object resource, key1 condition mismatched", name: "bucket/object resource, key1 condition mismatched",
action: action, action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName1], "some-oid"), resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName1], "some-oid"),
resourceMap: map[string]string{ resourceMap: map[string]string{
PropertyKeyFilePath: objName1, PropertyKeyFilePath: objName1,
}, },
@ -870,7 +792,7 @@ func TestComplexNativeConditions(t *testing.T) {
{ {
name: "bucket/object resource, key2 condition mismatched", name: "bucket/object resource, key2 condition mismatched",
action: action, action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName1], "some-oid"), resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName1], "some-oid"),
resourceMap: map[string]string{ resourceMap: map[string]string{
PropertyKeyFilePath: objName1, PropertyKeyFilePath: objName1,
}, },
@ -884,7 +806,7 @@ func TestComplexNativeConditions(t *testing.T) {
{ {
name: "bucket/object resource, object filepath condition mismatched", name: "bucket/object resource, object filepath condition mismatched",
action: action, action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName1], "some-oid"), resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName1], "some-oid"),
resourceMap: map[string]string{ resourceMap: map[string]string{
PropertyKeyFilePath: "any-object-name", PropertyKeyFilePath: "any-object-name",
}, },
@ -927,16 +849,15 @@ func TestComplexS3Conditions(t *testing.T) {
principal2 := "arn:aws:iam::" + namespace + ":user/" + userName2 principal2 := "arn:aws:iam::" + namespace + ":user/" + userName2
bktName1, bktName2, bktName3 := "bktName", "bktName2", "bktName3" bktName1, bktName2, bktName3 := "bktName", "bktName2", "bktName3"
objName1 := "objName1" objName1 := "objName1"
resource1 := fmt.Sprintf(s3.ResourceFormatS3BucketObject, bktName1, objName1) resource1 := bktName1 + "/" + objName1
resource2 := fmt.Sprintf(s3.ResourceFormatS3BucketObjects, bktName2) resource2 := bktName2 + "/*"
resource3 := fmt.Sprintf(s3.ResourceFormatS3BucketObjects, bktName3) resource3 := bktName3 + "/*"
action := "s3:DeleteObject" action := "PutObject"
action2 := "s3:DeleteMultipleObjects"
key1, key2 := "key1", "key2" key1, key2 := "key1", "key2"
val0, val1, val2 := "val0", "val1", "val2" val0, val1, val2 := "val0", "val1", "val2"
mockResolver := newMockUserResolver([]string{user1, user2}, []string{bktName1, bktName2, bktName3}, "") mockResolver := newMockUserResolver([]string{user1, user2}, []string{bktName1, bktName2, bktName3})
p := Policy{ p := Policy{
Version: "2012-10-17", Version: "2012-10-17",
@ -945,8 +866,8 @@ func TestComplexS3Conditions(t *testing.T) {
AWSPrincipalType: {principal1, principal2}, AWSPrincipalType: {principal1, principal2},
}, },
Effect: DenyEffect, Effect: DenyEffect,
Action: []string{action}, Action: []string{"s3:" + action},
Resource: []string{resource1, resource2, resource3}, Resource: []string{"arn:aws:s3:::" + resource1, "arn:aws:s3:::" + resource2, "arn:aws:s3:::" + resource3},
Conditions: map[string]Condition{ Conditions: map[string]Condition{
CondStringEquals: {key1: {val0, val1}}, CondStringEquals: {key1: {val0, val1}},
CondStringLike: {key2: {val2}}, CondStringLike: {key2: {val2}},
@ -955,7 +876,7 @@ func TestComplexS3Conditions(t *testing.T) {
} }
expectedStatus := chain.AccessDenied expectedStatus := chain.AccessDenied
expectedActions := chain.Actions{Names: []string{action, action2}} expectedActions := chain.Actions{Names: actionToOpMap[action]}
expectedResources := chain.Resources{Names: []string{resource1, resource2, resource3}} expectedResources := chain.Resources{Names: []string{resource1, resource2, resource3}}
user1Condition := chain.Condition{Op: chain.CondStringEquals, Object: chain.ObjectRequest, Key: s3.PropertyKeyOwner, Value: mockResolver.users[user1]} user1Condition := chain.Condition{Op: chain.CondStringEquals, Object: chain.ObjectRequest, Key: s3.PropertyKeyOwner, Value: mockResolver.users[user1]}
@ -1037,7 +958,7 @@ func TestComplexS3Conditions(t *testing.T) {
{ {
name: "bucket resource3, all conditions matched", name: "bucket resource3, all conditions matched",
action: action, action: action,
resource: fmt.Sprintf(s3.ResourceFormatS3BucketObject, bktName3, "some-obj"), resource: bktName3 + "/some-obj",
requestMap: map[string]string{ requestMap: map[string]string{
s3.PropertyKeyOwner: mockResolver.users[user1], s3.PropertyKeyOwner: mockResolver.users[user1],
key1: val0, key1: val0,
@ -1048,7 +969,7 @@ func TestComplexS3Conditions(t *testing.T) {
{ {
name: "bucket resource, user condition mismatched", name: "bucket resource, user condition mismatched",
action: action, action: action,
resource: fmt.Sprintf(s3.ResourceFormatS3BucketObject, bktName2, "some-obj"), resource: bktName2 + "/some-obj",
requestMap: map[string]string{ requestMap: map[string]string{
key1: val0, key1: val0,
key2: val2, key2: val2,
@ -1058,7 +979,7 @@ func TestComplexS3Conditions(t *testing.T) {
{ {
name: "bucket resource, key2 condition mismatched", name: "bucket resource, key2 condition mismatched",
action: action, action: action,
resource: fmt.Sprintf(s3.ResourceFormatS3BucketObject, bktName3, "some-obj"), resource: bktName3 + "/some-obj",
requestMap: map[string]string{ requestMap: map[string]string{
s3.PropertyKeyOwner: mockResolver.users[user1], s3.PropertyKeyOwner: mockResolver.users[user1],
key1: val0, key1: val0,
@ -1069,7 +990,7 @@ func TestComplexS3Conditions(t *testing.T) {
{ {
name: "bucket resource, key1 condition mismatched", name: "bucket resource, key1 condition mismatched",
action: action, action: action,
resource: fmt.Sprintf(s3.ResourceFormatS3BucketObject, bktName2, "some-obj"), resource: bktName2 + "/some-obj",
requestMap: map[string]string{ requestMap: map[string]string{
s3.PropertyKeyOwner: mockResolver.users[user1], s3.PropertyKeyOwner: mockResolver.users[user1],
key2: val2, key2: val2,
@ -1090,7 +1011,7 @@ func TestComplexS3Conditions(t *testing.T) {
{ {
name: "bucket/object resource, resource mismatched", name: "bucket/object resource, resource mismatched",
action: action, action: action,
resource: fmt.Sprintf(s3.ResourceFormatS3BucketObject, bktName1, "some-obj"), resource: bktName1 + "/some-obj",
requestMap: map[string]string{ requestMap: map[string]string{
s3.PropertyKeyOwner: mockResolver.users[user1], s3.PropertyKeyOwner: mockResolver.users[user1],
key1: val0, key1: val0,
@ -1133,7 +1054,7 @@ func TestComplexS3Conditions(t *testing.T) {
{ {
name: "resource mismatched", name: "resource mismatched",
action: action, action: action,
resource: fmt.Sprintf(s3.ResourceFormatS3BucketObject, "some-bkt", "some-obj"), resource: "some-bkt/some-obj",
requestMap: map[string]string{ requestMap: map[string]string{
s3.PropertyKeyOwner: mockResolver.users[user1], s3.PropertyKeyOwner: mockResolver.users[user1],
key1: val0, key1: val0,
@ -1151,57 +1072,6 @@ func TestComplexS3Conditions(t *testing.T) {
} }
} }
func TestS3BucketResource(t *testing.T) {
namespace := "ns"
bktName1, bktName2 := "bucket1", "bucket2"
chainName := chain.Name("name")
mockResolver := newMockUserResolver([]string{}, []string{}, "")
p := Policy{
Version: "2012-10-17",
Statement: []Statement{
{
Principal: map[PrincipalType][]string{Wildcard: nil},
Effect: DenyEffect,
Action: []string{"s3:HeadBucket"},
Resource: []string{fmt.Sprintf(s3.ResourceFormatS3Bucket, bktName1)},
},
{
Principal: map[PrincipalType][]string{Wildcard: nil},
Effect: AllowEffect,
Action: []string{"*"},
Resource: []string{s3.ResourceFormatS3All},
},
},
}
s3Chain, err := ConvertToS3Chain(p, mockResolver)
require.NoError(t, err)
s := inmemory.NewInMemory()
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chainName, engine.NamespaceTarget(namespace), s3Chain)
require.NoError(t, err)
// check we match just "bucket1" resource
req := testutil.NewRequest("s3:HeadBucket", testutil.NewResource(fmt.Sprintf(s3.ResourceFormatS3Bucket, bktName1), nil), nil)
status, _, err := s.IsAllowed(chainName, engine.NewRequestTargetWithNamespace(namespace), req)
require.NoError(t, err)
require.Equal(t, chain.AccessDenied.String(), status.String())
// check we match just "bucket2" resource
req = testutil.NewRequest("s3:HeadBucket", testutil.NewResource(fmt.Sprintf(s3.ResourceFormatS3Bucket, bktName2), nil), nil)
status, _, err = s.IsAllowed(chainName, engine.NewRequestTargetWithNamespace(namespace), req)
require.NoError(t, err)
require.Equal(t, chain.Allow.String(), status.String())
// check we also match "bucket2/object" resource
req = testutil.NewRequest("s3:PutObject", testutil.NewResource(fmt.Sprintf(s3.ResourceFormatS3BucketObject, bktName2, "object"), nil), nil)
status, _, err = s.IsAllowed(chainName, engine.NewRequestTargetWithNamespace(namespace), req)
require.NoError(t, err)
require.Equal(t, chain.Allow.String(), status.String())
}
func TestWildcardConverters(t *testing.T) { func TestWildcardConverters(t *testing.T) {
policy := `{"Version":"2012-10-17","Statement":{"Effect":"Allow", "Principal": "*", "Action":"*","Resource":"*"}}` policy := `{"Version":"2012-10-17","Statement":{"Effect":"Allow", "Principal": "*", "Action":"*","Resource":"*"}}`
@ -1209,73 +1079,60 @@ func TestWildcardConverters(t *testing.T) {
err := json.Unmarshal([]byte(policy), &p) err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err) require.NoError(t, err)
s3Expected := &chain.Chain{ _, err = ConvertToS3Chain(p, newMockUserResolver(nil, nil))
Rules: []chain.Rule{{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{Wildcard}},
Resources: chain.Resources{Names: []string{Wildcard}},
}},
}
s3Chain, err := ConvertToS3Chain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, s3Expected, s3Chain)
nativeExpected := &chain.Chain{ _, err = ConvertToNativeChain(p, newMockUserResolver(nil, nil))
Rules: []chain.Rule{{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{Wildcard}},
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}},
}},
}
nativeChain, err := ConvertToNativeChain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, nativeExpected, nativeChain)
} }
func TestActionParsing(t *testing.T) { func TestActionParsing(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
action string action string
expected string
err bool err bool
}{ }{
{ {
action: "withoutPrefix", action: "withoutPrefix",
expected: "",
err: true, err: true,
}, },
{ {
action: "s3:*Object", action: "s3:*Object",
expected: "",
err: true, err: true,
}, },
{ {
action: "*", action: "*",
expected: "*",
}, },
{ {
action: "s3:PutObject", action: "s3:PutObject",
expected: "PutObject",
}, },
{ {
action: "s3:Put*", action: "s3:Put*",
expected: "Put*",
}, },
{ {
action: "s3:*", action: "s3:*",
expected: "*",
}, },
{ {
action: "s3:", action: "s3:",
},
{ expected: "",
action: "iam:ListAccessKeys",
},
{
action: "iam:*",
}, },
} { } {
t.Run("", func(t *testing.T) { t.Run("", func(t *testing.T) {
err := validateAction(tc.action) actual, err := parseActionAsS3Action(tc.action)
if tc.err { if tc.err {
require.Error(t, err) require.Error(t, err)
} else { return
require.NoError(t, err)
} }
require.NoError(t, err)
require.Equal(t, tc.expected, actual)
}) })
} }
} }
@ -1346,23 +1203,54 @@ func TestPrincipalParsing(t *testing.T) {
func TestResourceParsing(t *testing.T) { func TestResourceParsing(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
resource string resource string
expectedBucket string
expectedObject string
err bool err bool
}{ }{
{resource: "withoutPrefixAnd", err: true}, {
{resource: "arn:aws:s3:::*/obj", err: true}, resource: "withoutPrefixAnd",
{resource: "arn:aws:s3:::bkt/*"}, err: true,
{resource: "arn:aws:s3:::bkt"}, },
{resource: "arn:aws:s3:::bkt/"}, {
{resource: "arn:aws:s3:::*"}, resource: "arn:aws:s3:::*/obj",
{resource: "*"}, err: true,
},
{
resource: "arn:aws:s3:::bkt/*",
expectedBucket: "bkt",
expectedObject: "*",
},
{
resource: "arn:aws:s3:::bkt",
expectedBucket: "bkt",
expectedObject: "*",
},
{
resource: "arn:aws:s3:::bkt/",
expectedBucket: "bkt",
expectedObject: "*",
},
{
resource: "arn:aws:s3:::*",
expectedBucket: "*",
expectedObject: "*",
},
{
resource: "*",
expectedBucket: "*",
expectedObject: "*",
},
} { } {
t.Run("", func(t *testing.T) { t.Run("", func(t *testing.T) {
err := validateResource(tc.resource) bkt, obj, err := parseResourceAsS3ARN(tc.resource)
if tc.err { if tc.err {
require.Error(t, err) require.Error(t, err)
} else { return
require.NoError(t, err)
} }
require.NoError(t, err)
require.Equal(t, tc.expectedBucket, bkt)
require.Equal(t, tc.expectedObject, obj)
}) })
} }
} }

View file

@ -46,8 +46,6 @@ type (
PrincipalType string PrincipalType string
) )
const policyVersion = "2012-10-17"
const ( const (
GeneralPolicyType PolicyType = iota GeneralPolicyType PolicyType = iota
IdentityBasedPolicyType IdentityBasedPolicyType
@ -224,20 +222,11 @@ func (p Policy) Validate(typ PolicyType) error {
} }
func (p Policy) validate() error { func (p Policy) validate() error {
if p.Version != policyVersion {
return fmt.Errorf("invalid policy version, expected '%s', actual: '%s'", policyVersion, p.Version)
}
if len(p.Statement) == 0 { if len(p.Statement) == 0 {
return errors.New("'Statement' is missing") return errors.New("'Statement' is missing")
} }
sids := make(map[string]struct{}, len(p.Statement))
for _, statement := range p.Statement { for _, statement := range p.Statement {
if _, ok := sids[statement.SID]; ok && statement.SID != "" {
return fmt.Errorf("duplicate 'SID': %s", statement.SID)
}
sids[statement.SID] = struct{}{}
if !statement.Effect.IsValid() { if !statement.Effect.IsValid() {
return fmt.Errorf("unknown effect: '%s'", statement.Effect) return fmt.Errorf("unknown effect: '%s'", statement.Effect)
} }

View file

@ -2,14 +2,8 @@ package iam
import ( import (
"encoding/json" "encoding/json"
"fmt"
"testing" "testing"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource/testutil"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -218,7 +212,6 @@ func TestValidatePolicies(t *testing.T) {
{ {
name: "valid permission boundaries", name: "valid permission boundaries",
policy: Policy{ policy: Policy{
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: AllowEffect, Effect: AllowEffect,
Action: []string{"s3:*", "cloudwatch:*", "ec2:*"}, Action: []string{"s3:*", "cloudwatch:*", "ec2:*"},
@ -231,7 +224,6 @@ func TestValidatePolicies(t *testing.T) {
{ {
name: "general invalid effect", name: "general invalid effect",
policy: Policy{ policy: Policy{
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: "dummy", Effect: "dummy",
Action: []string{"s3:*", "cloudwatch:*", "ec2:*"}, Action: []string{"s3:*", "cloudwatch:*", "ec2:*"},
@ -244,7 +236,6 @@ func TestValidatePolicies(t *testing.T) {
{ {
name: "general invalid principal block", name: "general invalid principal block",
policy: Policy{ policy: Policy{
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: AllowEffect, Effect: AllowEffect,
Action: []string{"s3:*", "cloudwatch:*", "ec2:*"}, Action: []string{"s3:*", "cloudwatch:*", "ec2:*"},
@ -259,7 +250,6 @@ func TestValidatePolicies(t *testing.T) {
{ {
name: "general invalid not principal", name: "general invalid not principal",
policy: Policy{ policy: Policy{
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: AllowEffect, Effect: AllowEffect,
Action: []string{"s3:*", "cloudwatch:*", "ec2:*"}, Action: []string{"s3:*", "cloudwatch:*", "ec2:*"},
@ -273,7 +263,6 @@ func TestValidatePolicies(t *testing.T) {
{ {
name: "general invalid principal type", name: "general invalid principal type",
policy: Policy{ policy: Policy{
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: AllowEffect, Effect: AllowEffect,
Action: []string{"s3:*", "cloudwatch:*", "ec2:*"}, Action: []string{"s3:*", "cloudwatch:*", "ec2:*"},
@ -287,7 +276,6 @@ func TestValidatePolicies(t *testing.T) {
{ {
name: "general invalid action block", name: "general invalid action block",
policy: Policy{ policy: Policy{
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: AllowEffect, Effect: AllowEffect,
Action: []string{"s3:*", "cloudwatch:*", "ec2:*"}, Action: []string{"s3:*", "cloudwatch:*", "ec2:*"},
@ -301,7 +289,6 @@ func TestValidatePolicies(t *testing.T) {
{ {
name: "general invalid resource block", name: "general invalid resource block",
policy: Policy{ policy: Policy{
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: AllowEffect, Effect: AllowEffect,
Resource: []string{Wildcard}, Resource: []string{Wildcard},
@ -314,7 +301,6 @@ func TestValidatePolicies(t *testing.T) {
{ {
name: "invalid resource block", name: "invalid resource block",
policy: Policy{ policy: Policy{
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: AllowEffect, Effect: AllowEffect,
Resource: []string{}, Resource: []string{},
@ -327,7 +313,6 @@ func TestValidatePolicies(t *testing.T) {
{ {
name: "missing resource block", name: "missing resource block",
policy: Policy{ policy: Policy{
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: AllowEffect, Effect: AllowEffect,
}}, }},
@ -341,43 +326,9 @@ func TestValidatePolicies(t *testing.T) {
typ: GeneralPolicyType, typ: GeneralPolicyType,
isValid: false, isValid: false,
}, },
{
name: "duplicate sid",
policy: Policy{
Version: policyVersion,
Statement: []Statement{
{
SID: "sid",
Effect: AllowEffect,
Action: []string{"s3:*"},
Resource: []string{Wildcard},
},
{
SID: "sid",
Effect: AllowEffect,
Action: []string{"cloudwatch:*"},
Resource: []string{Wildcard},
}},
},
typ: GeneralPolicyType,
isValid: false,
},
{
name: "missing version",
policy: Policy{
Statement: []Statement{{
Effect: AllowEffect,
Action: []string{"s3:*"},
Resource: []string{Wildcard},
}},
},
typ: GeneralPolicyType,
isValid: false,
},
{ {
name: "identity based valid", name: "identity based valid",
policy: Policy{ policy: Policy{
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: AllowEffect, Effect: AllowEffect,
Action: []string{"s3:PutObject"}, Action: []string{"s3:PutObject"},
@ -391,7 +342,6 @@ func TestValidatePolicies(t *testing.T) {
name: "identity based invalid because of id presence", name: "identity based invalid because of id presence",
policy: Policy{ policy: Policy{
ID: "some-id", ID: "some-id",
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: AllowEffect, Effect: AllowEffect,
Action: []string{"s3:PutObject"}, Action: []string{"s3:PutObject"},
@ -404,7 +354,6 @@ func TestValidatePolicies(t *testing.T) {
{ {
name: "identity based invalid because of principal presence", name: "identity based invalid because of principal presence",
policy: Policy{ policy: Policy{
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: AllowEffect, Effect: AllowEffect,
Action: []string{"s3:PutObject"}, Action: []string{"s3:PutObject"},
@ -418,7 +367,6 @@ func TestValidatePolicies(t *testing.T) {
{ {
name: "identity based invalid because of not principal presence", name: "identity based invalid because of not principal presence",
policy: Policy{ policy: Policy{
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: AllowEffect, Effect: AllowEffect,
Action: []string{"s3:PutObject"}, Action: []string{"s3:PutObject"},
@ -432,7 +380,6 @@ func TestValidatePolicies(t *testing.T) {
{ {
name: "resource based valid principal", name: "resource based valid principal",
policy: Policy{ policy: Policy{
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: DenyEffect, Effect: DenyEffect,
Action: []string{"s3:PutObject"}, Action: []string{"s3:PutObject"},
@ -447,7 +394,6 @@ func TestValidatePolicies(t *testing.T) {
name: "resource based valid not principal", name: "resource based valid not principal",
policy: Policy{ policy: Policy{
ID: "some-id", ID: "some-id",
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: DenyEffect, Effect: DenyEffect,
Action: []string{"s3:PutObject"}, Action: []string{"s3:PutObject"},
@ -462,7 +408,6 @@ func TestValidatePolicies(t *testing.T) {
name: "resource based invalid missing principal", name: "resource based invalid missing principal",
policy: Policy{ policy: Policy{
ID: "some-id", ID: "some-id",
Version: policyVersion,
Statement: []Statement{{ Statement: []Statement{{
Effect: AllowEffect, Effect: AllowEffect,
Action: []string{"s3:PutObject"}, Action: []string{"s3:PutObject"},
@ -483,70 +428,3 @@ func TestValidatePolicies(t *testing.T) {
}) })
} }
} }
func TestProcessDenyFirst(t *testing.T) {
identityBasedPolicyStr := `
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [ "arn:aws:iam::root:user/user-name" ]
},
"Action": ["s3:PutObject" ],
"Resource": "arn:aws:s3:::*"
}
]
}
`
resourceBasedPolicyStr := `
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [ "arn:aws:s3:::test-bucket/*" ]
}
]
}
`
var identityPolicy Policy
err := json.Unmarshal([]byte(identityBasedPolicyStr), &identityPolicy)
require.NoError(t, err)
var resourcePolicy Policy
err = json.Unmarshal([]byte(resourceBasedPolicyStr), &resourcePolicy)
require.NoError(t, err)
mockResolver := newMockUserResolver([]string{"root/user-name"}, []string{"test-bucket"}, "")
identityNativePolicy, err := ConvertToNativeChain(identityPolicy, mockResolver)
require.NoError(t, err)
identityNativePolicy.MatchType = chain.MatchTypeFirstMatch
resourceNativePolicy, err := ConvertToNativeChain(resourcePolicy, mockResolver)
require.NoError(t, err)
s := inmemory.NewInMemory()
target := engine.NamespaceTarget("ns")
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, target, identityNativePolicy)
require.NoError(t, err)
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, target, resourceNativePolicy)
require.NoError(t, err)
resource := testutil.NewResource(fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.containers["test-bucket"]), nil)
request := testutil.NewRequest("PutObject", resource, map[string]string{native.PropertyKeyActorPublicKey: mockResolver.users["root/user-name"]})
status, found, err := s.IsAllowed(chain.Ingress, engine.NewRequestTarget("ns", ""), request)
require.NoError(t, err)
require.True(t, found)
require.Equal(t, chain.AccessDenied, status)
}

View file

@ -1,38 +1,25 @@
package chain package chain
import ( import (
"encoding/json"
"fmt" "fmt"
"strings" "strings"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
"git.frostfs.info/TrueCloudLab/policy-engine/util" "git.frostfs.info/TrueCloudLab/policy-engine/util"
"golang.org/x/exp/slices"
) )
// ID is the ID of rule chain. // ID is the ID of rule chain.
type ID []byte type ID string
// MatchType is the match type for chain rules.
type MatchType uint8
const (
// MatchTypeDenyPriority rejects the request if any `Deny` is specified.
MatchTypeDenyPriority MatchType = 0
// MatchTypeFirstMatch returns the first rule action matched to the request.
MatchTypeFirstMatch MatchType = 1
)
//easyjson:json
type Chain struct { type Chain struct {
ID ID ID ID
Rules []Rule Rules []Rule
MatchType MatchType
} }
func (c *Chain) Bytes() []byte { func (c *Chain) Bytes() []byte {
data, err := c.MarshalBinary() data, err := json.Marshal(c)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -40,7 +27,7 @@ func (c *Chain) Bytes() []byte {
} }
func (c *Chain) DecodeBytes(b []byte) error { func (c *Chain) DecodeBytes(b []byte) error {
return c.UnmarshalBinary(b) return json.Unmarshal(b, c)
} }
type Rule struct { type Rule struct {
@ -77,8 +64,6 @@ type ObjectType byte
const ( const (
ObjectResource ObjectType = iota ObjectResource ObjectType = iota
ObjectRequest ObjectRequest
ContainerResource
ContainerRequest
) )
type ConditionType byte type ConditionType byte
@ -106,48 +91,45 @@ const (
CondNumericLessThanEquals CondNumericLessThanEquals
CondNumericGreaterThan CondNumericGreaterThan
CondNumericGreaterThanEquals CondNumericGreaterThanEquals
CondSliceContains
) )
var condToStr = []struct {
ct ConditionType
str 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"},
}
func (c ConditionType) String() string { func (c ConditionType) String() string {
for _, v := range condToStr { switch c {
if v.ct == c { case CondStringEquals:
return v.str return "StringEquals"
} case CondStringNotEquals:
} return "StringNotEquals"
case CondStringEqualsIgnoreCase:
return "StringEqualsIgnoreCase"
case CondStringNotEqualsIgnoreCase:
return "StringNotEqualsIgnoreCase"
case CondStringLike:
return "StringLike"
case CondStringNotLike:
return "StringNotLike"
case CondStringLessThan:
return "StringLessThan"
case CondStringLessThanEquals:
return "StringLessThanEquals"
case CondStringGreaterThan:
return "StringGreaterThan"
case CondStringGreaterThanEquals:
return "StringGreaterThanEquals"
case CondNumericEquals:
return "NumericEquals"
case CondNumericNotEquals:
return "NumericNotEquals"
case CondNumericLessThan:
return "NumericLessThan"
case CondNumericLessThanEquals:
return "NumericLessThanEquals"
case CondNumericGreaterThan:
return "NumericGreaterThan"
case CondNumericGreaterThanEquals:
return "NumericGreaterThanEquals"
default:
return "unknown condition type" return "unknown condition type"
} }
const condSliceContainsDelimiter = "\x00"
// FormCondSliceContainsValue builds value for ObjectResource or ObjectRequest property
// that can be matched by CondSliceContains condition.
func FormCondSliceContainsValue(values []string) string {
return strings.Join(values, condSliceContainsDelimiter)
} }
func (c *Condition) Match(req resource.Request) bool { func (c *Condition) Match(req resource.Request) bool {
@ -184,8 +166,6 @@ func (c *Condition) Match(req resource.Request) bool {
return val > c.Value return val > c.Value
case CondStringGreaterThanEquals: case CondStringGreaterThanEquals:
return val >= c.Value return val >= c.Value
case CondSliceContains:
return slices.Contains(strings.Split(val, condSliceContainsDelimiter), c.Value)
} }
} }
@ -234,17 +214,6 @@ func (r *Rule) matchAll(obj resource.Request) (status Status, matched bool) {
} }
func (c *Chain) Match(req resource.Request) (status Status, matched bool) { func (c *Chain) Match(req resource.Request) (status Status, matched bool) {
switch c.MatchType {
case MatchTypeDenyPriority:
return c.denyPriority(req)
case MatchTypeFirstMatch:
return c.firstMatch(req)
default:
panic(fmt.Sprintf("unknown MatchType %d", c.MatchType))
}
}
func (c *Chain) firstMatch(req resource.Request) (status Status, matched bool) {
for i := range c.Rules { for i := range c.Rules {
status, matched := c.Rules[i].Match(req) status, matched := c.Rules[i].Match(req)
if matched { if matched {
@ -253,21 +222,3 @@ func (c *Chain) firstMatch(req resource.Request) (status Status, matched bool) {
} }
return NoRuleFound, false return NoRuleFound, false
} }
func (c *Chain) denyPriority(req resource.Request) (status Status, matched bool) {
var allowFound bool
for i := range c.Rules {
status, matched := c.Rules[i].Match(req)
if !matched {
continue
}
if status != Allow {
return status, true
}
allowFound = true
}
if allowFound {
return Allow, true
}
return NoRuleFound, false
}

Binary file not shown.

View file

@ -7,7 +7,4 @@ const (
// Ingress represents chains applied when crossing user/storage network boundary. // Ingress represents chains applied when crossing user/storage network boundary.
// It is not applied when talking between nodes. // It is not applied when talking between nodes.
Ingress Name = "ingress" Ingress Name = "ingress"
// S3 represents chains applied when crossing user/s3 network boundary.
S3 Name = "s3"
) )

View file

@ -3,28 +3,11 @@ package chain
import ( import (
"testing" "testing"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource/testutil"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestChainIDSerialization(t *testing.T) {
chainIDBytes := []byte{93, 236, 80, 138, 168, 3, 144, 92, 173, 141, 16, 42, 249, 90, 97, 109, 211, 169, 54, 163}
chain1 := &Chain{ID: ID(chainIDBytes)}
data := chain1.Bytes()
var chain2 Chain
err := chain2.DecodeBytes(data)
require.NoError(t, err)
require.Equal(t, chain1.ID, chain2.ID)
}
func TestEncodeDecode(t *testing.T) { func TestEncodeDecode(t *testing.T) {
expected := Chain{ expected := Chain{
MatchType: MatchTypeFirstMatch,
Rules: []Rule{ Rules: []Rule{
{ {
Status: Allow, Status: Allow,
@ -48,104 +31,3 @@ func TestEncodeDecode(t *testing.T) {
require.NoError(t, actual.DecodeBytes(data)) require.NoError(t, actual.DecodeBytes(data))
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
} }
func TestReturnFirstMatch(t *testing.T) {
ch := Chain{
Rules: []Rule{
{
Status: Allow,
Actions: Actions{Names: []string{
native.MethodPutObject,
}},
Resources: Resources{Names: []string{native.ResourceFormatRootContainers}},
Condition: []Condition{},
},
{
Status: AccessDenied,
Actions: Actions{Names: []string{
native.MethodPutObject,
}},
Resources: Resources{Names: []string{native.ResourceFormatRootContainers}},
Condition: []Condition{},
},
},
}
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
request := testutil.NewRequest(native.MethodPutObject, resource, nil)
t.Run("default match", func(t *testing.T) {
st, found := ch.Match(request)
require.True(t, found)
require.Equal(t, AccessDenied, st)
})
t.Run("return first match", func(t *testing.T) {
ch.MatchType = MatchTypeFirstMatch
st, found := ch.Match(request)
require.True(t, found)
require.Equal(t, Allow, st)
})
}
func TestCondSliceContainsMatch(t *testing.T) {
propKey := common.PropertyKeyFrostFSIDGroupID
groupID := "1"
ch := Chain{Rules: []Rule{{
Status: Allow,
Actions: Actions{Names: []string{native.MethodPutObject}},
Resources: Resources{Names: []string{native.ResourceFormatRootContainers}},
Condition: []Condition{{
Op: CondSliceContains,
Object: ObjectRequest,
Key: propKey,
Value: groupID,
}},
}}}
for _, tc := range []struct {
name string
value string
status Status
}{
{
name: "simple value",
value: groupID,
status: Allow,
},
{
name: "simple value by func",
value: FormCondSliceContainsValue([]string{groupID}),
status: Allow,
},
{
name: "multiple values by func",
value: FormCondSliceContainsValue([]string{groupID, "2", "3"}),
status: Allow,
},
{
name: "simple mismatched",
value: "3",
status: NoRuleFound,
},
{
name: "multiple mismatched",
value: FormCondSliceContainsValue([]string{"11", "12"}),
status: NoRuleFound,
},
{
name: "comma correct handling mismatched",
value: "1,11",
status: NoRuleFound,
},
} {
t.Run(tc.name, func(t *testing.T) {
resource := testutil.NewResource(native.ResourceFormatRootContainers, nil)
request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{propKey: tc.value})
st, _ := ch.Match(request)
require.Equal(t, tc.status.String(), st.String())
})
}
}

View file

@ -1,257 +0,0 @@
package chain
import (
"encoding"
"fmt"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/marshal"
)
const (
ChainMarshalVersion uint8 = 0 // increase if breaking change
)
var (
_ encoding.BinaryMarshaler = (*Chain)(nil)
_ encoding.BinaryUnmarshaler = (*Chain)(nil)
)
func (c *Chain) MarshalBinary() ([]byte, error) {
s := marshal.UInt8Size // Marshaller version
s += marshal.UInt8Size // Chain version
s += marshal.SliceSize(c.ID, func(byte) int { return marshal.ByteSize })
s += marshal.SliceSize(c.Rules, ruleSize)
s += marshal.UInt8Size // MatchType
buf := make([]byte, s)
var offset int
var err error
offset, err = marshal.UInt8Marshal(buf, offset, marshal.Version)
if err != nil {
return nil, err
}
offset, err = marshal.UInt8Marshal(buf, offset, ChainMarshalVersion)
if err != nil {
return nil, err
}
offset, err = marshal.SliceMarshal(buf, offset, c.ID, marshal.ByteMarshal)
if err != nil {
return nil, err
}
offset, err = marshal.SliceMarshal(buf, offset, c.Rules, marshalRule)
if err != nil {
return nil, err
}
offset, err = marshal.UInt8Marshal(buf, offset, uint8(c.MatchType))
if err != nil {
return nil, err
}
if err := marshal.VerifyMarshal(buf, offset); err != nil {
return nil, err
}
return buf, nil
}
func (c *Chain) UnmarshalBinary(data []byte) error {
var offset int
marshallerVersion, offset, err := marshal.UInt8Unmarshal(data, offset)
if err != nil {
return err
}
if marshallerVersion != marshal.Version {
return fmt.Errorf("unsupported marshaller version %d", marshallerVersion)
}
chainVersion, offset, err := marshal.UInt8Unmarshal(data, offset)
if err != nil {
return err
}
if chainVersion != ChainMarshalVersion {
return fmt.Errorf("unsupported chain version %d", chainVersion)
}
idBytes, offset, err := marshal.SliceUnmarshal(data, offset, marshal.ByteUnmarshal)
if err != nil {
return err
}
c.ID = ID(idBytes)
c.Rules, offset, err = marshal.SliceUnmarshal(data, offset, unmarshalRule)
if err != nil {
return err
}
matchTypeV, offset, err := marshal.UInt8Unmarshal(data, offset)
if err != nil {
return err
}
c.MatchType = MatchType(matchTypeV)
return marshal.VerifyUnmarshal(data, offset)
}
func ruleSize(r Rule) int {
s := marshal.ByteSize // Status
s += actionsSize(r.Actions)
s += resourcesSize(r.Resources)
s += marshal.BoolSize // Any
s += marshal.SliceSize(r.Condition, conditionSize)
return s
}
func marshalRule(buf []byte, offset int, r Rule) (int, error) {
offset, err := marshal.ByteMarshal(buf, offset, byte(r.Status))
if err != nil {
return 0, err
}
offset, err = marshalActions(buf, offset, r.Actions)
if err != nil {
return 0, err
}
offset, err = marshalResources(buf, offset, r.Resources)
if err != nil {
return 0, err
}
offset, err = marshal.BoolMarshal(buf, offset, r.Any)
if err != nil {
return 0, err
}
return marshal.SliceMarshal(buf, offset, r.Condition, marshalCondition)
}
func unmarshalRule(buf []byte, offset int) (Rule, int, error) {
var r Rule
statusV, offset, err := marshal.ByteUnmarshal(buf, offset)
if err != nil {
return Rule{}, 0, err
}
r.Status = Status(statusV)
r.Actions, offset, err = unmarshalActions(buf, offset)
if err != nil {
return Rule{}, 0, err
}
r.Resources, offset, err = unmarshalResources(buf, offset)
if err != nil {
return Rule{}, 0, err
}
r.Any, offset, err = marshal.BoolUnmarshal(buf, offset)
if err != nil {
return Rule{}, 0, err
}
r.Condition, offset, err = marshal.SliceUnmarshal(buf, offset, unmarshalCondition)
if err != nil {
return Rule{}, 0, err
}
return r, offset, nil
}
func actionsSize(a Actions) int {
return marshal.BoolSize + // Inverted
marshal.SliceSize(a.Names, marshal.StringSize)
}
func marshalActions(buf []byte, offset int, a Actions) (int, error) {
offset, err := marshal.BoolMarshal(buf, offset, a.Inverted)
if err != nil {
return 0, err
}
return marshal.SliceMarshal(buf, offset, a.Names, marshal.StringMarshal)
}
func unmarshalActions(buf []byte, offset int) (Actions, int, error) {
var a Actions
var err error
a.Inverted, offset, err = marshal.BoolUnmarshal(buf, offset)
if err != nil {
return Actions{}, 0, err
}
a.Names, offset, err = marshal.SliceUnmarshal(buf, offset, marshal.StringUnmarshal)
if err != nil {
return Actions{}, 0, err
}
return a, offset, nil
}
func resourcesSize(r Resources) int {
return marshal.BoolSize + // Inverted
marshal.SliceSize(r.Names, marshal.StringSize)
}
func marshalResources(buf []byte, offset int, r Resources) (int, error) {
offset, err := marshal.BoolMarshal(buf, offset, r.Inverted)
if err != nil {
return 0, err
}
return marshal.SliceMarshal(buf, offset, r.Names, marshal.StringMarshal)
}
func unmarshalResources(buf []byte, offset int) (Resources, int, error) {
var r Resources
var err error
r.Inverted, offset, err = marshal.BoolUnmarshal(buf, offset)
if err != nil {
return Resources{}, 0, err
}
r.Names, offset, err = marshal.SliceUnmarshal(buf, offset, marshal.StringUnmarshal)
if err != nil {
return Resources{}, 0, err
}
return r, offset, nil
}
func conditionSize(c Condition) int {
return marshal.ByteSize + // Op
marshal.ByteSize + // Object
marshal.StringSize(c.Key) +
marshal.StringSize(c.Value)
}
func marshalCondition(buf []byte, offset int, c Condition) (int, error) {
offset, err := marshal.ByteMarshal(buf, offset, byte(c.Op))
if err != nil {
return 0, err
}
offset, err = marshal.ByteMarshal(buf, offset, byte(c.Object))
if err != nil {
return 0, err
}
offset, err = marshal.StringMarshal(buf, offset, c.Key)
if err != nil {
return 0, err
}
return marshal.StringMarshal(buf, offset, c.Value)
}
func unmarshalCondition(buf []byte, offset int) (Condition, int, error) {
var c Condition
opV, offset, err := marshal.ByteUnmarshal(buf, offset)
if err != nil {
return Condition{}, 0, err
}
c.Op = ConditionType(opV)
obV, offset, err := marshal.ByteUnmarshal(buf, offset)
if err != nil {
return Condition{}, 0, err
}
c.Object = ObjectType(obV)
c.Key, offset, err = marshal.StringUnmarshal(buf, offset)
if err != nil {
return Condition{}, 0, err
}
c.Value, offset, err = marshal.StringUnmarshal(buf, offset)
if err != nil {
return Condition{}, 0, err
}
return c, offset, nil
}

View file

@ -1,272 +0,0 @@
package chain
import (
"fmt"
"testing"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
func TestChainMarshalling(t *testing.T) {
t.Parallel()
for _, id := range generateTestIDs() {
for _, rules := range generateTestRules() {
for _, matchType := range generateTestMatchTypes() {
performMarshalTest(t, id, rules, matchType)
}
}
}
}
func TestInvalidChainData(t *testing.T) {
var ch Chain
require.Error(t, ch.UnmarshalBinary(nil))
require.Error(t, ch.UnmarshalBinary([]byte{}))
require.Error(t, ch.UnmarshalBinary([]byte{1, 2, 3}))
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,
Rules: r,
MatchType: mt,
}
data, err := chain.MarshalBinary()
require.NoError(t, err)
var unmarshalledChain Chain
require.NoError(t, unmarshalledChain.UnmarshalBinary(data))
require.Equal(t, chain, unmarshalledChain)
}
func generateTestIDs() []ID {
return []ID{
ID(""),
ID(uuid.New().String()),
ID("*::/"),
ID("avada kedavra"),
ID("arn:aws:iam::namespace:group/some_group"),
ID("$Object:homomorphicHash"),
ID("native:container/ns/9LPLUFZpEmfidG4n44vi2cjXKXSqWT492tCvLJiJ8W1J"),
}
}
func generateTestRules() [][]Rule {
result := [][]Rule{
nil,
{},
{},
}
for _, st := range generateTestStatuses() {
for _, act := range generateTestActions() {
for _, res := range generateTestResources() {
for _, cond := range generateTestConditions() {
result[2] = append(result[2], Rule{
Status: st,
Actions: act,
Resources: res,
Condition: cond,
Any: true,
})
result[2] = append(result[2], Rule{
Status: st,
Actions: act,
Resources: res,
Condition: cond,
})
}
}
}
}
return result
}
func generateTestStatuses() []Status {
return []Status{
Allow,
NoRuleFound,
AccessDenied,
QuotaLimitReached,
}
}
func generateTestActions() []Actions {
return []Actions{
{
Inverted: true,
Names: nil,
},
{
Names: nil,
},
{
Inverted: true,
Names: []string{},
},
{
Names: []string{},
},
{
Inverted: true,
Names: []string{native.MethodPutObject},
},
{
Names: []string{native.MethodPutObject},
},
{
Inverted: true,
Names: []string{native.MethodPutObject, native.MethodDeleteContainer, native.MethodDeleteObject},
},
{
Names: []string{native.MethodPutObject, native.MethodDeleteContainer, native.MethodDeleteObject},
},
}
}
func generateTestResources() []Resources {
return []Resources{
{
Inverted: true,
Names: nil,
},
{
Names: nil,
},
{
Inverted: true,
Names: []string{},
},
{
Names: []string{},
},
{
Inverted: true,
Names: []string{native.ResourceFormatAllObjects},
},
{
Names: []string{native.ResourceFormatAllObjects},
},
{
Inverted: true,
Names: []string{
native.ResourceFormatAllObjects,
fmt.Sprintf(native.ResourceFormatRootContainer, "9LPLUFZpEmfidG4n44vi2cjXKXSqWT492tCvLJiJ8W1J"),
},
},
{
Names: []string{
native.ResourceFormatAllObjects,
fmt.Sprintf(native.ResourceFormatRootContainer, "9LPLUFZpEmfidG4n44vi2cjXKXSqWT492tCvLJiJ8W1J"),
},
},
}
}
func generateTestConditions() [][]Condition {
result := [][]Condition{
nil,
{},
{},
}
for _, ct := range generateTestConditionTypes() {
for _, ot := range generateObjectTypes() {
result[2] = append(result[2], Condition{
Op: ct,
Object: ot,
Key: "",
Value: "",
})
result[2] = append(result[2], Condition{
Op: ct,
Object: ot,
Key: "key",
Value: "",
})
result[2] = append(result[2], Condition{
Op: ct,
Object: ot,
Key: "",
Value: "value",
})
result[2] = append(result[2], Condition{
Op: ct,
Object: ot,
Key: "key",
Value: "value",
})
}
}
return result
}
func generateTestConditionTypes() []ConditionType {
return []ConditionType{
CondStringEquals,
CondStringNotEquals,
CondStringEqualsIgnoreCase,
CondStringNotEqualsIgnoreCase,
CondStringLike,
CondStringNotLike,
CondStringLessThan,
CondStringLessThanEquals,
CondStringGreaterThan,
CondStringGreaterThanEquals,
CondNumericEquals,
CondNumericNotEquals,
CondNumericLessThan,
CondNumericLessThanEquals,
CondNumericGreaterThan,
CondNumericGreaterThanEquals,
CondSliceContains,
}
}
func generateObjectTypes() []ObjectType {
return []ObjectType{
ObjectResource,
ObjectRequest,
}
}
func generateTestMatchTypes() []MatchType {
return []MatchType{
MatchTypeDenyPriority,
MatchTypeFirstMatch,
}
}

View file

@ -1,145 +0,0 @@
package chain
import (
"fmt"
"strconv"
jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter"
)
// Run `make generate`` if types added or changed
var matchTypeToJSONValue = []struct {
mt MatchType
str string
}{
{MatchTypeDenyPriority, "DenyPriority"},
{MatchTypeFirstMatch, "FirstMatch"},
}
var statusToJSONValue = []struct {
s Status
str string
}{
{Allow, "Allow"},
{NoRuleFound, "NoRuleFound"},
{AccessDenied, "AccessDenied"},
{QuotaLimitReached, "QuotaLimitReached"},
}
var objectTypeToJSONValue = []struct {
t ObjectType
str string
}{
{ObjectRequest, "Request"},
{ObjectResource, "Resource"},
}
func (mt MatchType) MarshalEasyJSON(w *jwriter.Writer) {
for _, p := range matchTypeToJSONValue {
if p.mt == mt {
w.String(p.str)
return
}
}
w.String(strconv.FormatUint(uint64(mt), 10))
}
func (mt *MatchType) UnmarshalEasyJSON(l *jlexer.Lexer) {
str := l.String()
for _, p := range matchTypeToJSONValue {
if p.str == str {
*mt = p.mt
return
}
}
v, err := strconv.ParseUint(str, 10, 8)
if err != nil {
l.AddError(fmt.Errorf("failed to parse match type: %w", err))
return
}
*mt = MatchType(v)
}
func (st Status) MarshalEasyJSON(w *jwriter.Writer) {
for _, p := range statusToJSONValue {
if p.s == st {
w.String(p.str)
return
}
}
w.String(strconv.FormatUint(uint64(st), 10))
}
func (st *Status) UnmarshalEasyJSON(l *jlexer.Lexer) {
str := l.String()
for _, p := range statusToJSONValue {
if p.str == str {
*st = p.s
return
}
}
v, err := strconv.ParseUint(str, 10, 8)
if err != nil {
l.AddError(fmt.Errorf("failed to parse status: %w", err))
return
}
*st = Status(v)
}
func (ot ObjectType) MarshalEasyJSON(w *jwriter.Writer) {
for _, p := range objectTypeToJSONValue {
if p.t == ot {
w.String(p.str)
return
}
}
w.String(strconv.FormatUint(uint64(ot), 10))
}
func (ot *ObjectType) UnmarshalEasyJSON(l *jlexer.Lexer) {
str := l.String()
for _, p := range objectTypeToJSONValue {
if p.str == str {
*ot = p.t
return
}
}
v, err := strconv.ParseUint(str, 10, 8)
if err != nil {
l.AddError(fmt.Errorf("failed to parse object type: %w", err))
return
}
*ot = ObjectType(v)
}
func (ct ConditionType) MarshalEasyJSON(w *jwriter.Writer) {
for _, p := range condToStr {
if p.ct == ct {
w.String(p.str)
return
}
}
w.String(strconv.FormatUint(uint64(ct), 10))
}
func (ct *ConditionType) UnmarshalEasyJSON(l *jlexer.Lexer) {
str := l.String()
for _, p := range condToStr {
if p.str == str {
*ct = p.ct
return
}
}
v, err := strconv.ParseUint(str, 10, 8)
if err != nil {
l.AddError(fmt.Errorf("failed to parse condition type: %w", err))
return
}
*ct = ConditionType(v)
}

View file

@ -1,121 +0,0 @@
package chain
import (
"fmt"
"os"
"testing"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/stretchr/testify/require"
)
func TestID(t *testing.T) {
key, err := keys.NewPrivateKeyFromWIF("L5eVx6HcHaFpQpvjQ3fy29uKDZ8rQ34bfMVx4XfZMm52EqafpNMg") // s3-gw key
require.NoError(t, err)
chain1 := &Chain{ID: ID(key.PublicKey().GetScriptHash().BytesBE())}
data := chain1.Bytes()
var chain2 Chain
require.NoError(t, chain2.DecodeBytes(data))
require.Equal(t, chain1.ID, chain2.ID)
data, err = chain1.MarshalJSON()
require.NoError(t, err)
require.NoError(t, chain2.UnmarshalJSON(data))
require.Equal(t, chain1.ID, chain2.ID)
}
func TestMatchTypeJson(t *testing.T) {
for _, mt := range []MatchType{MatchTypeDenyPriority, MatchTypeFirstMatch, MatchType(100)} {
var chain Chain
chain.MatchType = mt
data, err := chain.MarshalJSON()
require.NoError(t, err)
if mt == MatchTypeDenyPriority {
require.Equal(t, []byte("{\"ID\":null,\"Rules\":null,\"MatchType\":\"DenyPriority\"}"), data)
} else if mt == MatchTypeFirstMatch {
require.Equal(t, []byte("{\"ID\":null,\"Rules\":null,\"MatchType\":\"FirstMatch\"}"), data)
} else {
require.Equal(t, []byte(fmt.Sprintf("{\"ID\":null,\"Rules\":null,\"MatchType\":\"%d\"}", mt)), data)
}
var parsed Chain
require.NoError(t, parsed.UnmarshalJSON(data))
require.Equal(t, chain, parsed)
require.Error(t, parsed.UnmarshalJSON([]byte("{\"ID\":\"\",\"Rules\":null,\"MatchType\":\"NotValid\"}")))
}
}
func TestJsonEnums(t *testing.T) {
chain := Chain{
ID: []byte("2cca5ae7-cee8-428d-b45f-567fb1d03f01"), // will be encoded to base64
MatchType: MatchTypeFirstMatch,
Rules: []Rule{
{
Status: AccessDenied,
Actions: Actions{
Names: []string{native.MethodDeleteObject, native.MethodGetContainer},
},
Resources: Resources{
Names: []string{native.ResourceFormatAllObjects},
},
Condition: []Condition{
{
Op: CondStringEquals,
Object: ObjectRequest,
Key: native.PropertyKeyActorRole,
Value: native.PropertyValueContainerRoleOthers,
},
},
},
{
Status: QuotaLimitReached,
Actions: Actions{
Inverted: true,
Names: []string{native.MethodPutObject},
},
Resources: Resources{
Names: []string{fmt.Sprintf(native.ResourceFormatRootContainerObjects, "9LPLUFZpEmfidG4n44vi2cjXKXSqWT492tCvLJiJ8W1J")},
},
Any: true,
Condition: []Condition{
{
Op: CondStringNotLike,
Object: ObjectResource,
Key: native.PropertyKeyObjectType,
Value: "regular",
},
},
},
{
Status: Status(100),
Condition: []Condition{
{
Op: ConditionType(255),
Object: ObjectType(128),
},
},
},
},
}
data, err := chain.MarshalJSON()
require.NoError(t, err)
var parsed Chain
require.NoError(t, parsed.UnmarshalJSON(data))
require.Equal(t, chain, parsed)
expected, err := os.ReadFile("./testdata/test_status_json.json")
require.NoError(t, err)
require.NoError(t, parsed.UnmarshalJSON(expected))
require.Equal(t, chain, parsed)
}

View file

@ -1,75 +0,0 @@
{
"ID": "MmNjYTVhZTctY2VlOC00MjhkLWI0NWYtNTY3ZmIxZDAzZjAx",
"Rules": [
{
"Status": "AccessDenied",
"Actions": {
"Inverted": false,
"Names": [
"DeleteObject",
"GetContainer"
]
},
"Resources": {
"Inverted": false,
"Names": [
"native:object/*"
]
},
"Any": false,
"Condition": [
{
"Op": "StringEquals",
"Object": "Request",
"Key": "$Actor:role",
"Value": "others"
}
]
},
{
"Status": "QuotaLimitReached",
"Actions": {
"Inverted": true,
"Names": [
"PutObject"
]
},
"Resources": {
"Inverted": false,
"Names": [
"native:object//9LPLUFZpEmfidG4n44vi2cjXKXSqWT492tCvLJiJ8W1J/*"
]
},
"Any": true,
"Condition": [
{
"Op": "StringNotLike",
"Object": "Resource",
"Key": "$Object:objectType",
"Value": "regular"
}
]
},
{
"Status": "100",
"Actions": {
"Inverted": false,
"Names": null
},
"Resources": {
"Inverted": false,
"Names": null
},
"Any": false,
"Condition": [
{
"Op": "255",
"Object": "128",
"Key": "",
"Value": ""
}
]
}
],
"MatchType": "FirstMatch"
}

View file

@ -6,18 +6,18 @@ import (
) )
type defaultChainRouter struct { type defaultChainRouter struct {
morph MorphRuleChainStorageReader morph MorphRuleChainStorage
local LocalOverrideStorage local LocalOverrideStorage
} }
func NewDefaultChainRouter(morph MorphRuleChainStorageReader) ChainRouter { func NewDefaultChainRouter(morph MorphRuleChainStorage) ChainRouter {
return &defaultChainRouter{ return &defaultChainRouter{
morph: morph, morph: morph,
} }
} }
func NewDefaultChainRouterWithLocalOverrides(morph MorphRuleChainStorageReader, local LocalOverrideStorage) ChainRouter { func NewDefaultChainRouterWithLocalOverrides(morph MorphRuleChainStorage, local LocalOverrideStorage) ChainRouter {
return &defaultChainRouter{ return &defaultChainRouter{
morph: morph, morph: morph,
local: local, local: local,
@ -86,31 +86,23 @@ func (dr *defaultChainRouter) matchLocalOverrides(name chain.Name, target Target
if err != nil { if err != nil {
return return
} }
status, ruleFound = dr.getStatusFromChains(localOverrides, r) for _, c := range localOverrides {
if status, ruleFound = c.Match(r); ruleFound && status != chain.Allow {
return
}
}
return return
} }
func (dr *defaultChainRouter) matchMorphRuleChains(name chain.Name, target Target, r resource.Request) (status chain.Status, ruleFound bool, err error) { func (dr *defaultChainRouter) matchMorphRuleChains(name chain.Name, target Target, r resource.Request) (status chain.Status, ruleFound bool, err error) {
namespaceChains, err := dr.morph.ListMorphRuleChains(name, target) namespaceChains, err := dr.morph.ListMorphRuleChains(name, target)
if err != nil { if err != nil {
return chain.NoRuleFound, false, err return
}
for _, c := range namespaceChains {
if status, ruleFound = c.Match(r); ruleFound {
return
}
} }
status, ruleFound = dr.getStatusFromChains(namespaceChains, r)
return return
} }
func (dr *defaultChainRouter) getStatusFromChains(chains []*chain.Chain, r resource.Request) (chain.Status, bool) {
var allow bool
for _, c := range chains {
if status, found := c.Match(r); found {
if status != chain.Allow {
return status, true
}
allow = true
}
}
if allow {
return chain.Allow, true
}
return chain.NoRuleFound, false
}

View file

@ -1,13 +1,11 @@
package inmemory package inmemory
import ( import (
"bytes"
"testing" "testing"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
resourcetest "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource/testutil" resourcetest "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource/testutil"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -65,10 +63,6 @@ func TestInmemory(t *testing.T) {
}, },
}) })
_, it, err := s.MorphRuleChainStorage().ListTargetsIterator(engine.Namespace)
require.NoError(t, err)
itemStacksEqual(t, it.Values, toStackItems(namespace))
s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(namespace2), &chain.Chain{ s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(namespace2), &chain.Chain{
Rules: []chain.Rule{ Rules: []chain.Rule{
{ // Deny all expect "native::object::get" for all objects expect "native::object::abc/xyz". { // Deny all expect "native::object::get" for all objects expect "native::object::abc/xyz".
@ -79,10 +73,6 @@ func TestInmemory(t *testing.T) {
}, },
}) })
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Namespace)
require.NoError(t, err)
itemStacksEqual(t, it.Values, toStackItems(namespace, namespace2))
s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(container), &chain.Chain{ s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(container), &chain.Chain{
Rules: []chain.Rule{ Rules: []chain.Rule{
{ // Allow to actor2 to get objects from the specific container only if they have `Department=HR` attribute. { // Allow to actor2 to get objects from the specific container only if they have `Department=HR` attribute.
@ -107,14 +97,6 @@ func TestInmemory(t *testing.T) {
}, },
}) })
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Namespace)
require.NoError(t, err)
itemStacksEqual(t, it.Values, toStackItems(namespace, namespace2))
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Container)
require.NoError(t, err)
itemStacksEqual(t, it.Values, toStackItems(container))
t.Run("bad subnet, namespace deny", func(t *testing.T) { t.Run("bad subnet, namespace deny", func(t *testing.T) {
// Request initiating from the untrusted subnet. // Request initiating from the untrusted subnet.
reqBadIP := resourcetest.NewRequest("native::object::put", res, map[string]string{ reqBadIP := resourcetest.NewRequest("native::object::put", res, map[string]string{
@ -193,14 +175,6 @@ func TestInmemory(t *testing.T) {
}}, }},
}) })
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Namespace)
require.NoError(t, err)
itemStacksEqual(t, it.Values, toStackItems(namespace, namespace2))
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Container)
require.NoError(t, err)
itemStacksEqual(t, it.Values, toStackItems(container))
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqGood) status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqGood)
require.Equal(t, chain.NoRuleFound, status) require.Equal(t, chain.NoRuleFound, status)
require.False(t, ok) require.False(t, ok)
@ -216,14 +190,6 @@ func TestInmemory(t *testing.T) {
}}, }},
}) })
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Namespace)
require.NoError(t, err)
itemStacksEqual(t, it.Values, toStackItems(namespace, namespace2))
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Container)
require.NoError(t, err)
itemStacksEqual(t, it.Values, toStackItems(container))
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqGood) status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqGood)
require.Equal(t, chain.QuotaLimitReached, status) require.Equal(t, chain.QuotaLimitReached, status)
require.True(t, ok) require.True(t, ok)
@ -232,83 +198,9 @@ func TestInmemory(t *testing.T) {
err := s.LocalStorage().RemoveOverride(chain.Ingress, engine.ContainerTarget(container), quotaRuleChainID) err := s.LocalStorage().RemoveOverride(chain.Ingress, engine.ContainerTarget(container), quotaRuleChainID)
require.NoError(t, err) require.NoError(t, err)
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Namespace)
require.NoError(t, err)
itemStacksEqual(t, it.Values, toStackItems(namespace, namespace2))
_, it, err = s.MorphRuleChainStorage().ListTargetsIterator(engine.Container)
require.NoError(t, err)
itemStacksEqual(t, it.Values, toStackItems(container))
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqGood) status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqGood)
require.Equal(t, chain.NoRuleFound, status) require.Equal(t, chain.NoRuleFound, status)
require.False(t, ok) require.False(t, ok)
}) })
}) })
t.Run("remove all", func(t *testing.T) {
s := NewInMemoryLocalOverrides()
_, _, err := s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(namespace), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{Inverted: true, Names: []string{"native::object::get"}},
Resources: chain.Resources{Inverted: true, Names: []string{object}},
},
},
})
require.NoError(t, err)
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(namespace2), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.Allow,
Actions: chain.Actions{Inverted: true, Names: []string{"native::object::get"}},
Resources: chain.Resources{Inverted: true, Names: []string{object}},
},
},
})
require.NoError(t, err)
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(namespace2), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{Inverted: true, Names: []string{"native::object::get"}},
Resources: chain.Resources{Inverted: true, Names: []string{object}},
},
},
})
require.NoError(t, err)
_, _, err = s.MorphRuleChainStorage().RemoveMorphRuleChainsByTarget(chain.Ingress, engine.NamespaceTarget(namespace2))
require.NoError(t, err)
chains, err := s.MorphRuleChainStorage().ListMorphRuleChains(chain.Ingress, engine.NamespaceTarget(namespace2))
require.NoError(t, err)
require.Equal(t, 0, len(chains))
chains, err = s.MorphRuleChainStorage().ListMorphRuleChains(chain.Ingress, engine.NamespaceTarget(namespace))
require.NoError(t, err)
require.Equal(t, 1, len(chains))
})
}
func itemStacksEqual(t *testing.T, got []stackitem.Item, expected []stackitem.Item) {
next:
for _, exp := range expected {
expBytes, err := exp.TryBytes()
require.NoError(t, err)
for _, v := range got {
vBytes, err := v.TryBytes()
require.NoError(t, err)
if bytes.Equal(vBytes, expBytes) {
continue next
}
}
t.Fatalf("not found %s", exp)
}
}
func toStackItems(names ...string) []stackitem.Item {
var items []stackitem.Item
for _, name := range names {
items = append(items, stackitem.NewByteArray([]byte(name)))
}
return items
} }

View file

@ -1,11 +1,9 @@
package inmemory package inmemory
import ( import (
"bytes"
"fmt" "fmt"
"math/rand" "math/rand"
"strings" "strings"
"sync"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
@ -15,16 +13,14 @@ import (
type targetToChain map[engine.Target][]*chain.Chain type targetToChain map[engine.Target][]*chain.Chain
type inmemoryLocalStorage struct { type inmemoryLocalStorage struct {
usedChainID map[string]struct{} usedChainID map[chain.ID]struct{}
nameToResourceChains map[chain.Name]targetToChain nameToResourceChains map[chain.Name]targetToChain
guard *sync.RWMutex
} }
func NewInmemoryLocalStorage() engine.LocalOverrideStorage { func NewInmemoryLocalStorage() engine.LocalOverrideStorage {
return &inmemoryLocalStorage{ return &inmemoryLocalStorage{
usedChainID: map[string]struct{}{}, usedChainID: map[chain.ID]struct{}{},
nameToResourceChains: make(map[chain.Name]targetToChain), nameToResourceChains: make(map[chain.Name]targetToChain),
guard: &sync.RWMutex{},
} }
} }
@ -36,24 +32,20 @@ func (s *inmemoryLocalStorage) generateChainID(name chain.Name, target engine.Ta
sid = strings.ReplaceAll(sid, "*", "") sid = strings.ReplaceAll(sid, "*", "")
sid = strings.ReplaceAll(sid, "/", ":") sid = strings.ReplaceAll(sid, "/", ":")
sid = strings.ReplaceAll(sid, "::", ":") sid = strings.ReplaceAll(sid, "::", ":")
_, ok := s.usedChainID[sid] id = chain.ID(sid)
_, ok := s.usedChainID[id]
if ok { if ok {
continue continue
} }
s.usedChainID[sid] = struct{}{} s.usedChainID[id] = struct{}{}
id = chain.ID(sid)
break break
} }
return id return id
} }
func (s *inmemoryLocalStorage) AddOverride(name chain.Name, target engine.Target, c *chain.Chain) (chain.ID, error) { func (s *inmemoryLocalStorage) AddOverride(name chain.Name, target engine.Target, c *chain.Chain) (chain.ID, error) {
s.guard.Lock()
defer s.guard.Unlock()
// AddOverride assigns generated chain ID if it has not been assigned. // AddOverride assigns generated chain ID if it has not been assigned.
if len(c.ID) == 0 { if c.ID == "" {
c.ID = s.generateChainID(name, target) c.ID = s.generateChainID(name, target)
} }
if s.nameToResourceChains[name] == nil { if s.nameToResourceChains[name] == nil {
@ -61,7 +53,7 @@ func (s *inmemoryLocalStorage) AddOverride(name chain.Name, target engine.Target
} }
rc := s.nameToResourceChains[name] rc := s.nameToResourceChains[name]
for i := range rc[target] { for i := range rc[target] {
if bytes.Equal(rc[target][i].ID, c.ID) { if rc[target][i].ID == c.ID {
rc[target][i] = c rc[target][i] = c
return c.ID, nil return c.ID, nil
} }
@ -71,21 +63,15 @@ func (s *inmemoryLocalStorage) AddOverride(name chain.Name, target engine.Target
} }
func (s *inmemoryLocalStorage) GetOverride(name chain.Name, target engine.Target, chainID chain.ID) (*chain.Chain, error) { func (s *inmemoryLocalStorage) GetOverride(name chain.Name, target engine.Target, chainID chain.ID) (*chain.Chain, error) {
s.guard.RLock()
defer s.guard.RUnlock()
if _, ok := s.nameToResourceChains[name]; !ok { if _, ok := s.nameToResourceChains[name]; !ok {
return nil, engine.ErrChainNameNotFound return nil, engine.ErrChainNameNotFound
} }
if target.Name == "" {
target.Name = "root"
}
chains, ok := s.nameToResourceChains[name][target] chains, ok := s.nameToResourceChains[name][target]
if !ok { if !ok {
return nil, engine.ErrResourceNotFound return nil, engine.ErrResourceNotFound
} }
for _, c := range chains { for _, c := range chains {
if bytes.Equal(c.ID, chainID) { if c.ID == chainID {
return c, nil return c, nil
} }
} }
@ -93,21 +79,15 @@ func (s *inmemoryLocalStorage) GetOverride(name chain.Name, target engine.Target
} }
func (s *inmemoryLocalStorage) RemoveOverride(name chain.Name, target engine.Target, chainID chain.ID) error { func (s *inmemoryLocalStorage) RemoveOverride(name chain.Name, target engine.Target, chainID chain.ID) error {
s.guard.Lock()
defer s.guard.Unlock()
if _, ok := s.nameToResourceChains[name]; !ok { if _, ok := s.nameToResourceChains[name]; !ok {
return engine.ErrChainNameNotFound return engine.ErrChainNameNotFound
} }
if target.Name == "" {
target.Name = "root"
}
chains, ok := s.nameToResourceChains[name][target] chains, ok := s.nameToResourceChains[name][target]
if !ok { if !ok {
return engine.ErrResourceNotFound return engine.ErrResourceNotFound
} }
for i, c := range chains { for i, c := range chains {
if bytes.Equal(c.ID, chainID) { if c.ID == chainID {
s.nameToResourceChains[name][target] = append(chains[:i], chains[i+1:]...) s.nameToResourceChains[name][target] = append(chains[:i], chains[i+1:]...)
return nil return nil
} }
@ -115,35 +95,11 @@ func (s *inmemoryLocalStorage) RemoveOverride(name chain.Name, target engine.Tar
return engine.ErrChainNotFound return engine.ErrChainNotFound
} }
func (s *inmemoryLocalStorage) RemoveOverridesByTarget(name chain.Name, target engine.Target) error {
s.guard.Lock()
defer s.guard.Unlock()
if _, ok := s.nameToResourceChains[name]; !ok {
return engine.ErrChainNameNotFound
}
if target.Name == "" {
target.Name = "root"
}
_, ok := s.nameToResourceChains[name][target]
if ok {
delete(s.nameToResourceChains[name], target)
return nil
}
return engine.ErrResourceNotFound
}
func (s *inmemoryLocalStorage) ListOverrides(name chain.Name, target engine.Target) ([]*chain.Chain, error) { func (s *inmemoryLocalStorage) ListOverrides(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
s.guard.RLock()
defer s.guard.RUnlock()
rcs, ok := s.nameToResourceChains[name] rcs, ok := s.nameToResourceChains[name]
if !ok { if !ok {
return []*chain.Chain{}, nil return []*chain.Chain{}, nil
} }
if target.Name == "" {
target.Name = "root"
}
for t, chains := range rcs { for t, chains := range rcs {
if t.Type != target.Type { if t.Type != target.Type {
continue continue
@ -157,20 +113,6 @@ func (s *inmemoryLocalStorage) ListOverrides(name chain.Name, target engine.Targ
} }
func (s *inmemoryLocalStorage) DropAllOverrides(name chain.Name) error { func (s *inmemoryLocalStorage) DropAllOverrides(name chain.Name) error {
s.guard.Lock()
defer s.guard.Unlock()
s.nameToResourceChains[name] = make(targetToChain) s.nameToResourceChains[name] = make(targetToChain)
return nil return nil
} }
func (s *inmemoryLocalStorage) ListOverrideDefinedTargets(name chain.Name) ([]engine.Target, error) {
s.guard.RLock()
defer s.guard.RUnlock()
ttc := s.nameToResourceChains[name]
var keys []engine.Target
for k := range ttc {
keys = append(keys, k)
}
return keys, nil
}

View file

@ -14,7 +14,9 @@ const (
nonExistChainId = "ingress:LxGyWyL" nonExistChainId = "ingress:LxGyWyL"
) )
var resrc = engine.ContainerTarget(container) var (
resrc = engine.ContainerTarget(container)
)
func testInmemLocalStorage() *inmemoryLocalStorage { func testInmemLocalStorage() *inmemoryLocalStorage {
return NewInmemoryLocalStorage().(*inmemoryLocalStorage) return NewInmemoryLocalStorage().(*inmemoryLocalStorage)
@ -110,52 +112,6 @@ func TestRemoveOverride(t *testing.T) {
require.True(t, ok) require.True(t, ok)
require.Len(t, resourceChains, 0) require.Len(t, resourceChains, 0)
}) })
t.Run("remove by target", func(t *testing.T) {
inmem := testInmemLocalStorage()
t0 := engine.ContainerTarget("name0")
t1 := engine.ContainerTarget("name1")
inmem.AddOverride(chain.Ingress, t0, &chain.Chain{
ID: chain.ID(chainID),
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{Names: []string{"native::object::delete"}},
Resources: chain.Resources{Names: []string{"native::object::*"}},
},
},
})
inmem.AddOverride(chain.Ingress, t0, &chain.Chain{
ID: chain.ID(chainID),
Rules: []chain.Rule{
{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{"native::object::delete"}},
Resources: chain.Resources{Names: []string{"native::object::*"}},
},
},
})
inmem.AddOverride(chain.Ingress, t1, &chain.Chain{
ID: chain.ID(chainID),
Rules: []chain.Rule{
{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{"native::object::delete"}},
Resources: chain.Resources{Names: []string{"native::object::*"}},
},
},
})
err := inmem.RemoveOverridesByTarget(chain.Ingress, t0)
require.NoError(t, err)
ingressChains, ok := inmem.nameToResourceChains[chain.Ingress]
require.True(t, ok)
require.Len(t, ingressChains, 1)
resourceChains, ok := ingressChains[t1]
require.True(t, ok)
require.Len(t, resourceChains, 1)
})
} }
func TestGetOverride(t *testing.T) { func TestGetOverride(t *testing.T) {
@ -230,9 +186,6 @@ func TestListOverrides(t *testing.T) {
inmem.AddOverride(chain.Ingress, resrc, addChain) inmem.AddOverride(chain.Ingress, resrc, addChain)
l, _ := inmem.ListOverrides(chain.Ingress, resrc) l, _ := inmem.ListOverrides(chain.Ingress, resrc)
require.Len(t, l, 1) require.Len(t, l, 1)
targets, err := inmem.ListOverrideDefinedTargets(chain.Ingress)
require.NoError(t, err)
require.Equal(t, []engine.Target{resrc}, targets)
}) })
t.Run("list after drop", func(t *testing.T) { t.Run("list after drop", func(t *testing.T) {
@ -257,12 +210,12 @@ func TestGenerateID(t *testing.T) {
} }
func hasDuplicates(ids []chain.ID) bool { func hasDuplicates(ids []chain.ID) bool {
seen := make(map[string]bool) seen := make(map[chain.ID]bool)
for _, id := range ids { for _, id := range ids {
if seen[string(id)] { if seen[id] {
return true return true
} }
seen[string(id)] = true seen[id] = true
} }
return false return false
} }

View file

@ -3,26 +3,27 @@ package inmemory
import ( import (
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
type inmemoryMorphRuleChainStorage struct { type inmemoryMorphRuleChainStorage struct {
storage engine.LocalOverrideStorage nameToNamespaceChains engine.LocalOverrideStorage
nameToContainerChains engine.LocalOverrideStorage
} }
func NewInmemoryMorphRuleChainStorage() engine.MorphRuleChainStorage { func NewInmemoryMorphRuleChainStorage() engine.MorphRuleChainStorage {
return &inmemoryMorphRuleChainStorage{ return &inmemoryMorphRuleChainStorage{
storage: NewInmemoryLocalStorage(), nameToNamespaceChains: NewInmemoryLocalStorage(),
nameToContainerChains: NewInmemoryLocalStorage(),
} }
} }
func (s *inmemoryMorphRuleChainStorage) AddMorphRuleChain(name chain.Name, target engine.Target, c *chain.Chain) (_ util.Uint256, _ uint32, err error) { func (s *inmemoryMorphRuleChainStorage) AddMorphRuleChain(name chain.Name, target engine.Target, c *chain.Chain) (_ util.Uint256, _ uint32, err error) {
switch target.Type { switch target.Type {
case engine.Namespace, engine.Container: case engine.Namespace:
_, err = s.storage.AddOverride(name, target, c) _, err = s.nameToNamespaceChains.AddOverride(name, target, c)
case engine.Container:
_, err = s.nameToContainerChains.AddOverride(name, target, c)
default: default:
err = engine.ErrUnknownTarget err = engine.ErrUnknownTarget
} }
@ -31,18 +32,10 @@ func (s *inmemoryMorphRuleChainStorage) AddMorphRuleChain(name chain.Name, targe
func (s *inmemoryMorphRuleChainStorage) RemoveMorphRuleChain(name chain.Name, target engine.Target, chainID chain.ID) (_ util.Uint256, _ uint32, err error) { func (s *inmemoryMorphRuleChainStorage) RemoveMorphRuleChain(name chain.Name, target engine.Target, chainID chain.ID) (_ util.Uint256, _ uint32, err error) {
switch target.Type { switch target.Type {
case engine.Namespace, engine.Container: case engine.Namespace:
err = s.storage.RemoveOverride(name, target, chainID) err = s.nameToNamespaceChains.RemoveOverride(name, target, chainID)
default: case engine.Container:
err = engine.ErrUnknownTarget err = s.nameToContainerChains.RemoveOverride(name, target, chainID)
}
return
}
func (s *inmemoryMorphRuleChainStorage) RemoveMorphRuleChainsByTarget(name chain.Name, target engine.Target) (_ util.Uint256, _ uint32, err error) {
switch target.Type {
case engine.Namespace, engine.Container:
err = s.storage.RemoveOverridesByTarget(name, target)
default: default:
err = engine.ErrUnknownTarget err = engine.ErrUnknownTarget
} }
@ -51,62 +44,11 @@ func (s *inmemoryMorphRuleChainStorage) RemoveMorphRuleChainsByTarget(name chain
func (s *inmemoryMorphRuleChainStorage) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) { func (s *inmemoryMorphRuleChainStorage) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
switch target.Type { switch target.Type {
case engine.Namespace, engine.Container: case engine.Namespace:
return s.storage.ListOverrides(name, target) return s.nameToNamespaceChains.ListOverrides(name, target)
case engine.Container:
return s.nameToContainerChains.ListOverrides(name, target)
default: default:
} }
return nil, engine.ErrUnknownTarget return nil, engine.ErrUnknownTarget
} }
func (s *inmemoryMorphRuleChainStorage) GetAdmin() (util.Uint160, error) {
panic("not implemented")
}
func (s *inmemoryMorphRuleChainStorage) SetAdmin(_ util.Uint160) (util.Uint256, uint32, error) {
panic("not implemented")
}
func (s *inmemoryMorphRuleChainStorage) ListTargetsIterator(targetType engine.TargetType) (uuid uuid.UUID, it result.Iterator, err error) {
it.Values = make([]stackitem.Item, 0)
switch targetType {
case engine.Namespace:
// Listing targets may look bizarre, because inmemory rule chain storage use inmemory local overrides where
// targets are listed by chain names.
var targets []engine.Target
targets, err = s.storage.ListOverrideDefinedTargets(chain.Ingress)
if err != nil {
return
}
for _, t := range targets {
it.Values = append(it.Values, stackitem.NewByteArray([]byte(t.Name)))
}
targets, err = s.storage.ListOverrideDefinedTargets(chain.S3)
if err != nil {
return
}
for _, t := range targets {
it.Values = append(it.Values, stackitem.NewByteArray([]byte(t.Name)))
}
case engine.Container:
var targets []engine.Target
targets, err = s.storage.ListOverrideDefinedTargets(chain.Ingress)
if err != nil {
return
}
for _, t := range targets {
it.Values = append(it.Values, stackitem.NewByteArray([]byte(t.Name)))
}
targets, err = s.storage.ListOverrideDefinedTargets(chain.S3)
if err != nil {
return
}
for _, t := range targets {
it.Values = append(it.Values, stackitem.NewByteArray([]byte(t.Name)))
}
default:
}
return
}

View file

@ -3,8 +3,6 @@ package engine
import ( import (
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
) )
@ -23,13 +21,9 @@ type LocalOverrideStorage interface {
RemoveOverride(name chain.Name, target Target, chainID chain.ID) error RemoveOverride(name chain.Name, target Target, chainID chain.ID) error
RemoveOverridesByTarget(name chain.Name, target Target) error
ListOverrides(name chain.Name, target Target) ([]*chain.Chain, error) ListOverrides(name chain.Name, target Target) ([]*chain.Chain, error)
DropAllOverrides(name chain.Name) error DropAllOverrides(name chain.Name) error
ListOverrideDefinedTargets(name chain.Name) ([]Target, error)
} }
type TargetType rune type TargetType rune
@ -74,12 +68,12 @@ func NewRequestTarget(namespace, container string) RequestTarget {
} }
func (rt *RequestTarget) Targets() (targets []Target) { func (rt *RequestTarget) Targets() (targets []Target) {
if rt.Namespace != nil {
targets = append(targets, *rt.Namespace)
}
if rt.Container != nil { if rt.Container != nil {
targets = append(targets, *rt.Container) targets = append(targets, *rt.Container)
} }
if rt.Namespace != nil {
targets = append(targets, *rt.Namespace)
}
return return
} }
@ -97,33 +91,17 @@ func ContainerTarget(container string) Target {
} }
} }
// MorphRuleChainStorageReader is the interface that provides read-only methods to receive // MorphRuleChainStorage is the interface to manage chains from the chain storage.
// data like chains, target or admin from a chain storage.
type MorphRuleChainStorageReader interface {
// ListMorphRuleChains just lists deserialized chains.
ListMorphRuleChains(name chain.Name, target Target) ([]*chain.Chain, error)
GetAdmin() (util.Uint160, error)
// ListTargetsIterator provides an iterator to list targets for which rules are defined.
ListTargetsIterator(targetType TargetType) (uuid.UUID, result.Iterator, error)
}
// MorphRuleChainStorage is the interface to read and manage data within a chain storage.
// Basically, this implies that the storage manages rules stored in policy contract. // Basically, this implies that the storage manages rules stored in policy contract.
type MorphRuleChainStorage interface { type MorphRuleChainStorage interface {
MorphRuleChainStorageReader
// AddMorphRuleChain adds a chain rule to the policy contract and returns transaction hash, VUB and error. // AddMorphRuleChain adds a chain rule to the policy contract and returns transaction hash, VUB and error.
AddMorphRuleChain(name chain.Name, target Target, c *chain.Chain) (util.Uint256, uint32, error) AddMorphRuleChain(name chain.Name, target Target, c *chain.Chain) (util.Uint256, uint32, error)
// RemoveMorphRuleChain removes a chain rule to the policy contract and returns transaction hash, VUB and error. // RemoveMorphRuleChain removes a chain rule to the policy contract and returns transaction hash, VUB and error.
RemoveMorphRuleChain(name chain.Name, target Target, chainID chain.ID) (util.Uint256, uint32, error) RemoveMorphRuleChain(name chain.Name, target Target, chainID chain.ID) (util.Uint256, uint32, error)
// RemoveMorphRuleChainsByTarget removes all chains by target and returns transaction hash, VUB and error. // ListMorphRuleChains just lists deserialized chains.
RemoveMorphRuleChainsByTarget(name chain.Name, target Target) (util.Uint256, uint32, error) ListMorphRuleChains(name chain.Name, target Target) ([]*chain.Chain, error)
SetAdmin(addr util.Uint160) (util.Uint256, uint32, error)
} }
// Engine is the interface that provides methods to check request permissions checking // Engine is the interface that provides methods to check request permissions checking

View file

@ -1,267 +0,0 @@
package marshal
import (
"encoding/binary"
"fmt"
)
const (
Version byte = 0 // increase if breaking change
ByteSize int = 1
UInt8Size int = ByteSize
BoolSize int = ByteSize
nilSlice int64 = -1
nilSliceSize int = 1
byteTrue uint8 = 1
byteFalse uint8 = 0
// maxSliceLen taken from https://github.com/neo-project/neo/blob/38218bbee5bbe8b33cd8f9453465a19381c9a547/src/Neo/IO/Helper.cs#L77
maxSliceLen = 0x1000000
)
type MarshallerError struct {
errMsg string
offset int
}
func (e *MarshallerError) Error() string {
if e == nil {
return ""
}
if e.offset < 0 {
return e.errMsg
}
return fmt.Sprintf("%s (offset: %d)", e.errMsg, e.offset)
}
func errBufTooSmall(t string, marshal bool, offset int) error {
action := "unmarshal"
if marshal {
action = "marshal"
}
return &MarshallerError{
errMsg: fmt.Sprintf("not enough bytes left to %s value of type '%s'", action, t),
offset: offset,
}
}
func VerifyMarshal(buf []byte, lastOffset int) error {
if len(buf) != lastOffset {
return &MarshallerError{
errMsg: "actual data size differs from expected",
offset: -1,
}
}
return nil
}
func VerifyUnmarshal(buf []byte, lastOffset int) error {
if len(buf) != lastOffset {
return &MarshallerError{
errMsg: "unmarshalled bytes left",
}
}
return nil
}
func SliceSize[T any](slice []T, sizeOf func(T) int) int {
if slice == nil {
return nilSliceSize
}
s := Int64Size(int64(len(slice)))
for _, v := range slice {
s += sizeOf(v)
}
return s
}
func SliceMarshal[T any](buf []byte, offset int, slice []T, marshalT func([]byte, int, T) (int, error)) (int, error) {
if slice == nil {
return Int64Marshal(buf, offset, nilSlice)
}
if len(slice) > maxSliceLen {
return 0, &MarshallerError{
errMsg: fmt.Sprintf("slice size if too big: '%d'", len(slice)),
offset: offset,
}
}
offset, err := Int64Marshal(buf, offset, int64(len(slice)))
if err != nil {
return 0, err
}
for _, v := range slice {
offset, err = marshalT(buf, offset, v)
if err != nil {
return 0, err
}
}
return offset, nil
}
func SliceUnmarshal[T any](buf []byte, offset int, unmarshalT func(buf []byte, offset int) (T, int, error)) ([]T, int, error) {
size, offset, err := Int64Unmarshal(buf, offset)
if err != nil {
return nil, 0, err
}
if size == nilSlice {
return nil, offset, nil
}
if size > maxSliceLen {
return nil, 0, &MarshallerError{
errMsg: fmt.Sprintf("slice size if too big: '%d'", size),
offset: offset,
}
}
if size < 0 {
return nil, 0, &MarshallerError{
errMsg: fmt.Sprintf("invalid slice size: '%d'", size),
offset: offset,
}
}
result := make([]T, size)
for idx := 0; idx < len(result); idx++ {
result[idx], offset, err = unmarshalT(buf, offset)
if err != nil {
return nil, 0, err
}
}
return result, offset, nil
}
func Int64Size(v int64) int {
// https://cs.opensource.google/go/go/+/master:src/encoding/binary/varint.go;l=92;drc=dac9b9ddbd5160c5f4552410f5f8281bd5eed38c
// and
// https://cs.opensource.google/go/go/+/master:src/encoding/binary/varint.go;l=41;drc=dac9b9ddbd5160c5f4552410f5f8281bd5eed38c
ux := uint64(v) << 1
if v < 0 {
ux = ^ux
}
s := 0
for ux >= 0x80 {
s++
ux >>= 7
}
return s + 1
}
func Int64Marshal(buf []byte, offset int, v int64) (int, error) {
if len(buf)-offset < Int64Size(v) {
return 0, errBufTooSmall("int64", true, offset)
}
return offset + binary.PutVarint(buf[offset:], v), nil
}
func Int64Unmarshal(buf []byte, offset int) (int64, int, error) {
v, read := binary.Varint(buf[offset:])
if read == 0 {
return 0, 0, errBufTooSmall("int64", false, offset)
}
if read < 0 {
return 0, 0, &MarshallerError{
errMsg: "int64 unmarshal overflow",
offset: offset,
}
}
return v, offset + read, nil
}
func StringSize(s string) int {
return Int64Size(int64(len(s))) + len(s)
}
func StringMarshal(buf []byte, offset int, s string) (int, error) {
if len(s) > maxSliceLen {
return 0, &MarshallerError{
errMsg: fmt.Sprintf("string is too long: '%d'", len(s)),
offset: offset,
}
}
if len(buf)-offset < Int64Size(int64(len(s)))+len(s) {
return 0, errBufTooSmall("string", true, offset)
}
offset, err := Int64Marshal(buf, offset, int64(len(s)))
if err != nil {
return 0, err
}
if s == "" {
return offset, nil
}
return offset + copy(buf[offset:], s), nil
}
func StringUnmarshal(buf []byte, offset int) (string, int, error) {
size, offset, err := Int64Unmarshal(buf, offset)
if err != nil {
return "", 0, err
}
if size == 0 {
return "", offset, nil
}
if size > maxSliceLen {
return "", 0, &MarshallerError{
errMsg: fmt.Sprintf("string is too long: '%d'", size),
offset: offset,
}
}
if size < 0 {
return "", 0, &MarshallerError{
errMsg: fmt.Sprintf("invalid string size: '%d'", size),
offset: offset,
}
}
if len(buf)-offset < int(size) {
return "", 0, errBufTooSmall("string", false, offset)
}
return string(buf[offset : offset+int(size)]), offset + int(size), nil
}
func UInt8Marshal(buf []byte, offset int, value uint8) (int, error) {
if len(buf)-offset < 1 {
return 0, errBufTooSmall("uint8", true, offset)
}
buf[offset] = value
return offset + 1, nil
}
func UInt8Unmarshal(buf []byte, offset int) (uint8, int, error) {
if len(buf)-offset < 1 {
return 0, 0, errBufTooSmall("uint8", false, offset)
}
return buf[offset], offset + 1, nil
}
func ByteMarshal(buf []byte, offset int, value byte) (int, error) {
return UInt8Marshal(buf, offset, value)
}
func ByteUnmarshal(buf []byte, offset int) (byte, int, error) {
return UInt8Unmarshal(buf, offset)
}
func BoolMarshal(buf []byte, offset int, value bool) (int, error) {
if value {
return UInt8Marshal(buf, offset, byteTrue)
}
return UInt8Marshal(buf, offset, byteFalse)
}
func BoolUnmarshal(buf []byte, offset int) (bool, int, error) {
v, offset, err := UInt8Unmarshal(buf, offset)
if err != nil {
return false, 0, err
}
if v == byteTrue {
return true, offset, nil
}
if v == byteFalse {
return false, offset, nil
}
return false, 0, &MarshallerError{
errMsg: fmt.Sprintf("invalid marshalled value for bool: %d", v),
offset: offset - BoolSize,
}
}

View file

@ -1,313 +0,0 @@
package marshal
import (
"encoding/binary"
"math"
"testing"
"github.com/stretchr/testify/require"
)
func TestMarshalling(t *testing.T) {
t.Parallel()
t.Run("slice", func(t *testing.T) {
t.Parallel()
t.Run("nil slice", func(t *testing.T) {
t.Parallel()
var int64s []int64
expectedSize := SliceSize(int64s, Int64Size)
require.Equal(t, 1, expectedSize)
buf := make([]byte, expectedSize)
offset, err := SliceMarshal(buf, 0, int64s, Int64Marshal)
require.NoError(t, err)
require.NoError(t, VerifyMarshal(buf, offset))
result, offset, err := SliceUnmarshal(buf, 0, Int64Unmarshal)
require.NoError(t, err)
require.NoError(t, VerifyUnmarshal(buf, offset))
require.Nil(t, result)
})
t.Run("empty slice", func(t *testing.T) {
t.Parallel()
int64s := make([]int64, 0)
expectedSize := SliceSize(int64s, Int64Size)
require.Equal(t, 1, expectedSize)
buf := make([]byte, expectedSize)
offset, err := SliceMarshal(buf, 0, int64s, Int64Marshal)
require.NoError(t, err)
require.NoError(t, VerifyMarshal(buf, offset))
result, offset, err := SliceUnmarshal(buf, 0, Int64Unmarshal)
require.NoError(t, err)
require.NoError(t, VerifyUnmarshal(buf, offset))
require.NotNil(t, result)
require.Len(t, result, 0)
})
t.Run("non empty slice", func(t *testing.T) {
t.Parallel()
int64s := make([]int64, 100)
for i := range int64s {
int64s[i] = int64(i)
}
expectedSize := SliceSize(int64s, Int64Size)
buf := make([]byte, expectedSize)
offset, err := SliceMarshal(buf, 0, int64s, Int64Marshal)
require.NoError(t, err)
require.NoError(t, VerifyMarshal(buf, offset))
result, offset, err := SliceUnmarshal(buf, 0, Int64Unmarshal)
require.NoError(t, err)
require.NoError(t, VerifyUnmarshal(buf, offset))
require.Equal(t, int64s, result)
})
t.Run("corrupted slice size", func(t *testing.T) {
t.Parallel()
int64s := make([]int64, 100)
for i := range int64s {
int64s[i] = int64(i)
}
expectedSize := SliceSize(int64s, Int64Size)
buf := make([]byte, expectedSize)
offset, err := SliceMarshal(buf, 0, int64s, Int64Marshal)
require.NoError(t, err)
require.NoError(t, VerifyMarshal(buf, offset))
for i := 0; i < binary.MaxVarintLen64; i++ {
buf[i] = 129
}
_, _, err = SliceUnmarshal(buf, 0, Int64Unmarshal)
var mErr *MarshallerError
require.ErrorAs(t, err, &mErr)
for i := 0; i < binary.MaxVarintLen64; i++ {
buf[i] = 127
}
_, _, err = SliceUnmarshal(buf, 0, Int64Unmarshal)
require.ErrorAs(t, err, &mErr)
})
t.Run("corrupted slice item", func(t *testing.T) {
t.Parallel()
int64s := make([]int64, 100)
for i := range int64s {
int64s[i] = int64(i)
}
expectedSize := SliceSize(int64s, Int64Size)
buf := make([]byte, expectedSize)
offset, err := SliceMarshal(buf, 0, int64s, Int64Marshal)
require.NoError(t, err)
require.NoError(t, VerifyMarshal(buf, offset))
for i := 2; i < binary.MaxVarintLen64+2; i++ {
buf[i] = 129
}
_, _, err = SliceUnmarshal(buf, 0, Int64Unmarshal)
var mErr *MarshallerError
require.ErrorAs(t, err, &mErr)
})
t.Run("small buffer", func(t *testing.T) {
t.Parallel()
int64s := make([]int64, 100)
for i := range int64s {
int64s[i] = int64(i)
}
buf := make([]byte, 1)
_, err := SliceMarshal(buf, 0, int64s, Int64Marshal)
var mErr *MarshallerError
require.ErrorAs(t, err, &mErr)
buf = make([]byte, 10)
_, err = SliceMarshal(buf, 0, int64s, Int64Marshal)
require.ErrorAs(t, err, &mErr)
})
})
t.Run("int64", func(t *testing.T) {
t.Parallel()
t.Run("success", func(t *testing.T) {
t.Parallel()
require.Equal(t, 1, Int64Size(0))
require.Equal(t, binary.MaxVarintLen64, Int64Size(math.MaxInt64))
require.Equal(t, binary.MaxVarintLen64, Int64Size(math.MinInt64))
for _, v := range []int64{0, math.MinInt64, math.MaxInt64} {
size := Int64Size(v)
buf := make([]byte, size)
offset, err := Int64Marshal(buf, 0, v)
require.NoError(t, err)
require.NoError(t, VerifyMarshal(buf, offset))
uv, offset, err := Int64Unmarshal(buf, 0)
require.NoError(t, err)
require.NoError(t, VerifyUnmarshal(buf, offset))
require.Equal(t, v, uv)
}
})
t.Run("invalid buffer", func(t *testing.T) {
t.Parallel()
var mErr *MarshallerError
_, err := Int64Marshal([]byte{}, 0, 100500)
require.ErrorAs(t, err, &mErr)
_, _, err = Int64Unmarshal(nil, 0)
require.ErrorAs(t, err, &mErr)
})
t.Run("overflow", func(t *testing.T) {
t.Parallel()
var mErr *MarshallerError
var v int64 = math.MaxInt64
buf := make([]byte, Int64Size(v))
_, err := Int64Marshal(buf, 0, v)
require.NoError(t, err)
buf[9] = 2
_, _, err = Int64Unmarshal(buf, 0)
require.ErrorAs(t, err, &mErr)
})
})
t.Run("string", func(t *testing.T) {
t.Parallel()
t.Run("success", func(t *testing.T) {
t.Parallel()
for _, v := range []string{
"", "arn:aws:iam::namespace:group/some_group", "$Object:homomorphicHash",
"native:container/ns/9LPLUFZpEmfidG4n44vi2cjXKXSqWT492tCvLJiJ8W1J",
} {
size := StringSize(v)
buf := make([]byte, size)
offset, err := StringMarshal(buf, 0, v)
require.NoError(t, err)
require.NoError(t, VerifyMarshal(buf, offset))
uv, offset, err := StringUnmarshal(buf, 0)
require.NoError(t, err)
require.NoError(t, VerifyUnmarshal(buf, offset))
require.Equal(t, v, uv)
}
})
t.Run("invalid buffer", func(t *testing.T) {
t.Parallel()
str := "avada kedavra"
var mErr *MarshallerError
_, err := StringMarshal(nil, 0, str)
require.ErrorAs(t, err, &mErr)
_, _, err = StringUnmarshal(nil, 0)
require.ErrorAs(t, err, &mErr)
buf := make([]byte, StringSize(str))
offset, err := StringMarshal(buf, 0, str)
require.NoError(t, err)
require.NoError(t, VerifyMarshal(buf, offset))
buf = buf[:len(buf)-1]
_, _, err = StringUnmarshal(buf, 0)
require.ErrorAs(t, err, &mErr)
})
})
t.Run("uint8, byte", func(t *testing.T) {
t.Parallel()
for _, v := range []byte{0, 8, 16, 32, 64, 128, 255} {
buf := make([]byte, ByteSize)
offset, err := ByteMarshal(buf, 0, v)
require.NoError(t, err)
require.NoError(t, VerifyMarshal(buf, offset))
ub, offset, err := ByteUnmarshal(buf, 0)
require.NoError(t, err)
require.NoError(t, VerifyUnmarshal(buf, offset))
require.Equal(t, v, ub)
buf = make([]byte, UInt8Size)
offset, err = UInt8Marshal(buf, 0, v)
require.NoError(t, err)
require.NoError(t, VerifyMarshal(buf, offset))
uu, offset, err := UInt8Unmarshal(buf, 0)
require.NoError(t, err)
require.NoError(t, VerifyUnmarshal(buf, offset))
require.Equal(t, v, uu)
}
})
t.Run("bool", func(t *testing.T) {
t.Parallel()
t.Run("success", func(t *testing.T) {
t.Parallel()
for _, v := range []bool{false, true} {
buf := make([]byte, BoolSize)
offset, err := BoolMarshal(buf, 0, v)
require.NoError(t, err)
require.NoError(t, VerifyMarshal(buf, offset))
ub, offset, err := BoolUnmarshal(buf, 0)
require.NoError(t, err)
require.NoError(t, VerifyUnmarshal(buf, offset))
require.Equal(t, v, ub)
}
})
t.Run("invalid value", func(t *testing.T) {
t.Parallel()
buf := make([]byte, BoolSize)
offset, err := BoolMarshal(buf, 0, true)
require.NoError(t, err)
require.NoError(t, VerifyMarshal(buf, offset))
buf[0] = 2
_, _, err = BoolUnmarshal(buf, 0)
var mErr *MarshallerError
require.ErrorAs(t, err, &mErr)
})
t.Run("invalid buffer", func(t *testing.T) {
t.Parallel()
var mErr *MarshallerError
_, err := BoolMarshal(nil, 0, true)
require.ErrorAs(t, err, &mErr)
buf := append(make([]byte, BoolSize), 100)
offset, err := BoolMarshal(buf, 0, true)
require.NoError(t, err)
require.ErrorAs(t, VerifyMarshal(buf, offset), &mErr)
v, offset, err := BoolUnmarshal(buf, 0)
require.NoError(t, err)
require.True(t, v)
require.ErrorAs(t, VerifyUnmarshal(buf, offset), &mErr)
_, _, err = BoolUnmarshal(nil, 0)
require.ErrorAs(t, err, &mErr)
})
})
}

View file

@ -10,8 +10,7 @@ import (
client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/policy" client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/policy"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"github.com/google/uuid" "github.com/mr-tron/base58"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
@ -24,20 +23,13 @@ var (
ErrEngineTargetTypeUnsupported = errors.New("this target type is not supported yet") ErrEngineTargetTypeUnsupported = errors.New("this target type is not supported yet")
) )
// ContractStorage is the interface to manage chain rules within Policy contract. // ContractStorage is the interface to manage chain rules within the policy contract.
type ContractStorage struct { type ContractStorage struct {
contractInterface *client.Contract contractInterface *client.Contract
} }
var _ engine.MorphRuleChainStorage = (*ContractStorage)(nil) var _ engine.MorphRuleChainStorage = (*ContractStorage)(nil)
// ContractStorageReader is the interface to read data from Policy contract.
type ContractStorageReader struct {
contractReaderInterface *client.ContractReader
}
var _ engine.MorphRuleChainStorageReader = (*ContractStorageReader)(nil)
func NewContractStorage(actor client.Actor, contract util.Uint160) *ContractStorage { func NewContractStorage(actor client.Actor, contract util.Uint160) *ContractStorage {
return &ContractStorage{ return &ContractStorage{
contractInterface: client.New(actor, contract), contractInterface: client.New(actor, contract),
@ -52,8 +44,18 @@ func NewContractStorageWithSimpleActor(rpcActor actor.RPCActor, acc *wallet.Acco
return NewContractStorage(act, contract), nil return NewContractStorage(act, contract), nil
} }
func transformNameIfContainer(target engine.Target) (name string) {
name = target.Name
if target.Type == engine.Container {
// Container name can be too long and, thus, cannot be
// used as a key name for policy-contract storage.
name = base58.FastBase58Encoding([]byte(target.Name))
}
return
}
func (s *ContractStorage) AddMorphRuleChain(name chain.Name, target engine.Target, c *chain.Chain) (txHash util.Uint256, vub uint32, err error) { func (s *ContractStorage) AddMorphRuleChain(name chain.Name, target engine.Target, c *chain.Chain) (txHash util.Uint256, vub uint32, err error) {
if len(c.ID) == 0 { if c.ID == "" {
err = ErrEmptyChainID err = ErrEmptyChainID
return return
} }
@ -64,13 +66,14 @@ func (s *ContractStorage) AddMorphRuleChain(name chain.Name, target engine.Targe
return return
} }
fullName := prefixedChainName(name, c.ID) fullName := prefixedChainName(name, c.ID)
targetName := transformNameIfContainer(target)
txHash, vub, err = s.contractInterface.AddChain(big.NewInt(int64(kind)), target.Name, fullName, c.Bytes()) txHash, vub, err = s.contractInterface.AddChain(big.NewInt(int64(kind)), targetName, fullName, c.Bytes())
return return
} }
func (s *ContractStorage) RemoveMorphRuleChain(name chain.Name, target engine.Target, chainID chain.ID) (txHash util.Uint256, vub uint32, err error) { func (s *ContractStorage) RemoveMorphRuleChain(name chain.Name, target engine.Target, chainID chain.ID) (txHash util.Uint256, vub uint32, err error) {
if len(chainID) == 0 { if chainID == "" {
err = ErrEmptyChainID err = ErrEmptyChainID
return return
} }
@ -81,20 +84,9 @@ func (s *ContractStorage) RemoveMorphRuleChain(name chain.Name, target engine.Ta
return return
} }
fullName := prefixedChainName(name, chainID) fullName := prefixedChainName(name, chainID)
targetName := transformNameIfContainer(target)
txHash, vub, err = s.contractInterface.RemoveChain(big.NewInt(int64(kind)), target.Name, fullName) txHash, vub, err = s.contractInterface.RemoveChain(big.NewInt(int64(kind)), targetName, fullName)
return
}
func (s *ContractStorage) RemoveMorphRuleChainsByTarget(name chain.Name, target engine.Target) (txHash util.Uint256, vub uint32, err error) {
var kind policy.Kind
kind, err = policyKind(target.Type)
if err != nil {
return
}
fullName := prefixedChainName(name, nil)
txHash, vub, err = s.contractInterface.RemoveChainsByPrefix(big.NewInt(int64(kind)), target.Name, fullName)
return return
} }
@ -103,8 +95,9 @@ func (s *ContractStorage) ListMorphRuleChains(name chain.Name, target engine.Tar
if err != nil { if err != nil {
return nil, err return nil, err
} }
targetName := transformNameIfContainer(target)
items, err := s.contractInterface.ListChainsByPrefix(big.NewInt(int64(kind)), target.Name, []byte(name)) items, err := s.contractInterface.ListChainsByPrefix(big.NewInt(int64(kind)), targetName, []byte(name))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -125,71 +118,16 @@ func (s *ContractStorage) ListMorphRuleChains(name chain.Name, target engine.Tar
return chains, nil return chains, nil
} }
func (s *ContractStorage) ListTargetsIterator(targetType engine.TargetType) (uuid.UUID, result.Iterator, error) {
kind, err := policyKind(targetType)
if err != nil {
return uuid.UUID{}, result.Iterator{}, err
}
return s.contractInterface.ListTargets(big.NewInt(int64(kind)))
}
func (s *ContractStorage) GetAdmin() (util.Uint160, error) {
return s.contractInterface.GetAdmin()
}
func (s *ContractStorage) SetAdmin(addr util.Uint160) (util.Uint256, uint32, error) {
return s.contractInterface.SetAdmin(addr)
}
func NewContractStorageReader(inv client.Invoker, contract util.Uint160) *ContractStorageReader {
return &ContractStorageReader{
contractReaderInterface: client.NewReader(inv, contract),
}
}
func (s *ContractStorageReader) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
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) {
return s.contractReaderInterface.GetAdmin()
}
func (s *ContractStorageReader) ListTargetsIterator(targetType engine.TargetType) (uuid.UUID, result.Iterator, error) {
kind, err := policyKind(targetType)
if err != nil {
return uuid.UUID{}, result.Iterator{}, err
}
return s.contractReaderInterface.ListTargets(big.NewInt(int64(kind)))
}
func bytesFromStackItem(param stackitem.Item) ([]byte, error) { func bytesFromStackItem(param stackitem.Item) ([]byte, error) {
switch param.Type() { switch param.Type() {
case stackitem.BufferT, stackitem.ByteArrayT, stackitem.IntegerT: case stackitem.BufferT, stackitem.ByteArrayT:
return param.TryBytes() return param.TryBytes()
case stackitem.IntegerT:
n, err := param.TryInteger()
if err != nil {
return nil, fmt.Errorf("can't parse integer bytes: %w", err)
}
return n.Bytes(), nil
case stackitem.AnyT: case stackitem.AnyT:
if param.Value() == nil { if param.Value() == nil {
return nil, nil return nil, nil

View file

@ -1,5 +0,0 @@
package common
const (
PropertyKeyFrostFSIDGroupID = "frostfsid:groupID"
)

View file

@ -9,16 +9,6 @@ const (
MethodRangeObject = "RangeObject" MethodRangeObject = "RangeObject"
MethodHashObject = "HashObject" MethodHashObject = "HashObject"
MethodPutContainer = "PutContainer"
MethodDeleteContainer = "DeleteContainer"
MethodGetContainer = "GetContainer"
MethodListContainers = "ListContainers"
MethodSetContainerEACL = "SetContainerEACL"
MethodGetContainerEACL = "GetContainerEACL"
ObjectPrefix = "native:object"
ContainerPrefix = "native:container"
ResourceFormatNamespaceObjects = "native:object/%s/*" ResourceFormatNamespaceObjects = "native:object/%s/*"
ResourceFormatNamespaceContainerObjects = "native:object/%s/%s/*" ResourceFormatNamespaceContainerObjects = "native:object/%s/%s/*"
ResourceFormatNamespaceContainerObject = "native:object/%s/%s/%s" ResourceFormatNamespaceContainerObject = "native:object/%s/%s/%s"
@ -39,7 +29,6 @@ const (
PropertyKeyActorPublicKey = "$Actor:publicKey" PropertyKeyActorPublicKey = "$Actor:publicKey"
PropertyKeyActorRole = "$Actor:role" PropertyKeyActorRole = "$Actor:role"
PropertyKeyObjectVersion = "$Object:version" PropertyKeyObjectVersion = "$Object:version"
PropertyKeyObjectID = "$Object:objectID" PropertyKeyObjectID = "$Object:objectID"
PropertyKeyObjectContainerID = "$Object:containerID" PropertyKeyObjectContainerID = "$Object:containerID"
@ -49,11 +38,4 @@ const (
PropertyKeyObjectPayloadHash = "$Object:payloadHash" PropertyKeyObjectPayloadHash = "$Object:payloadHash"
PropertyKeyObjectType = "$Object:objectType" PropertyKeyObjectType = "$Object:objectType"
PropertyKeyObjectHomomorphicHash = "$Object:homomorphicHash" PropertyKeyObjectHomomorphicHash = "$Object:homomorphicHash"
PropertyKeyContainerOwnerID = "$Container:ownerID"
PropertyValueContainerRoleOwner = "owner"
PropertyValueContainerRoleIR = "ir"
PropertyValueContainerRoleContainer = "container"
PropertyValueContainerRoleOthers = "others"
) )

View file

@ -1,45 +0,0 @@
package util
import (
"strings"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
)
var nativePatterns = []string{
native.ResourceFormatNamespaceObjects, native.ResourceFormatNamespaceContainerObjects,
native.ResourceFormatNamespaceContainerObject, native.ResourceFormatRootObjects,
native.ResourceFormatRootContainerObjects, native.ResourceFormatRootContainerObject,
native.ResourceFormatAllObjects, native.ResourceFormatNamespaceContainer,
native.ResourceFormatNamespaceContainers, native.ResourceFormatRootContainer,
native.ResourceFormatRootContainers, native.ResourceFormatAllContainers,
}
func match(resource, pattern string) bool {
rTokens := strings.Split(resource, "/")
pToken := strings.Split(pattern, "/")
if len(rTokens) != len(pToken) {
return false
}
for i := range rTokens {
if pToken[i] == "%s" {
continue
}
if pToken[i] != rTokens[i] {
return false
}
}
return true
}
func IsNativeResourceNameValid(resource string) bool {
for _, pattern := range nativePatterns {
if match(resource, pattern) {
return true
}
}
return false
}

View file

@ -1,97 +0,0 @@
package util
import (
"testing"
"github.com/stretchr/testify/require"
)
var tests = []struct {
name string
expected bool
resource string
}{
{
name: "ResourceFormatNamespaceObjects",
expected: true,
resource: "native:object/RootNamespace/*",
},
{
name: "ResourceFormatNamespaceContainerObjects",
expected: true,
resource: "native:object/RootNamespace/BzQw5HH3feoxFDD5tCT87Y1726qzgLfxEE7wgtoRzB3R/*",
},
{
name: "ResourceFormatNamespaceContainerObject",
expected: true,
resource: "native:object/RootNamespace/BzQw5HH3feoxFDD5tCT87Y1726qzgLfxEE7wgtoRzB3R/AeZa5HH3feoxFDD5tCT87Y1726qzgLfxEE7wgtoRzB4E",
},
{
name: "ResourceFormatRootObjects",
expected: true,
resource: "native:object//*",
},
{
name: "ResourceFormatRootContainerObjects",
expected: true,
resource: "native:object//BzQw5HH3feoxFDD5tCT87Y1726qzgLfxEE7wgtoRzB3R/*",
},
{
name: "ResourceFormatRootContainerObject",
expected: true,
resource: "native:object//BzQw5HH3feoxFDD5tCT87Y1726qzgLfxEE7wgtoRzB3R/AeZa5HH3feoxFDD5tCT87Y1726qzgLfxEE7wgtoRzB4E",
},
{
name: "ResourceFormatAllObjects",
expected: true,
resource: "native:object/*",
},
{
name: "ResourceFormatNamespaceContainer",
expected: true,
resource: "native:container/RootNamespace/BzQw5HH3feoxFDD5tCT87Y1726qzgLfxEE7wgtoRzB3R",
},
{
name: "ResourceFormatNamespaceContainers",
expected: true,
resource: "native:container/RootNamespace/*",
},
{
name: "ResourceFormatRootContainers",
expected: true,
resource: "native:container//*",
},
{
name: "ResourceFormatAllContainers",
expected: true,
resource: "native:container/*",
},
{
name: "Invalid resource 1",
expected: false,
resource: "native:::container/*",
},
{
name: "Invalid resource 2",
expected: false,
resource: "native:container/RootNamespace/w5HH3feoxFDD5tCTtoRzB3R/Bz726qzgLfxEE7wgtoRzB3R/RootNamespace",
},
}
func TestIsNativeResourceNameValid(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.expected, IsNativeResourceNameValid(test.resource))
})
}
}
func BenchmarkIsNativeResourceNameValid(b *testing.B) {
for _, test := range tests {
b.Run(test.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = IsNativeResourceNameValid(test.resource)
}
})
}
}

View file

@ -6,12 +6,4 @@ const (
PropertyKeyDelimiter = "s3:delimiter" PropertyKeyDelimiter = "s3:delimiter"
PropertyKeyPrefix = "s3:prefix" PropertyKeyPrefix = "s3:prefix"
PropertyKeyVersionID = "s3:VersionId" PropertyKeyVersionID = "s3:VersionId"
ResourceFormatS3All = "arn:aws:s3:::*"
ResourceFormatS3Bucket = "arn:aws:s3:::%s"
ResourceFormatS3BucketObjects = "arn:aws:s3:::%s/*"
ResourceFormatS3BucketObject = "arn:aws:s3:::%s/%s"
ResourceFormatIAMNamespaceUser = "arn:aws:iam::%s:user/%s"
ResourceFormatIAMNamespaceGroup = "arn:aws:iam::%s:group/%s"
) )