generated from TrueCloudLab/basic
Compare commits
No commits in common. "master" and "master" have entirely different histories.
58 changed files with 1030 additions and 9126 deletions
|
@ -13,9 +13,9 @@ jobs:
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
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 }}'
|
||||||
|
|
|
@ -11,7 +11,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: '1.21'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install linters
|
- name: Install linters
|
||||||
|
@ -25,7 +25,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go_versions: [ '1.22', '1.23' ]
|
go_versions: [ '1.20', '1.21' ]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
@ -48,7 +48,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: '1.21'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
@ -63,7 +63,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: '1.21'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install staticcheck
|
- name: Install staticcheck
|
||||||
|
|
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -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
|
|
||||||
|
|
|
@ -12,8 +12,7 @@ run:
|
||||||
# output configuration options
|
# output configuration options
|
||||||
output:
|
output:
|
||||||
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
|
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
|
||||||
formats:
|
format: tab
|
||||||
- format: tab
|
|
||||||
|
|
||||||
# all available settings of specific linters
|
# all available settings of specific linters
|
||||||
linters-settings:
|
linters-settings:
|
||||||
|
|
|
@ -2,6 +2,13 @@ ci:
|
||||||
autofix_prs: false
|
autofix_prs: false
|
||||||
|
|
||||||
repos:
|
repos:
|
||||||
|
- repo: https://github.com/jorisroovers/gitlint
|
||||||
|
rev: v0.19.1
|
||||||
|
hooks:
|
||||||
|
- id: gitlint
|
||||||
|
stages: [commit-msg]
|
||||||
|
- id: gitlint-ci
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.4.0
|
rev: v4.4.0
|
||||||
hooks:
|
hooks:
|
||||||
|
@ -35,7 +42,7 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: go-unit-tests
|
- id: go-unit-tests
|
||||||
name: go unit tests
|
name: go unit tests
|
||||||
entry: make test GOFLAGS=''
|
entry: make test
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
types: [go]
|
types: [go]
|
||||||
language: system
|
language: system
|
||||||
|
|
21
Makefile
21
Makefile
|
@ -1,12 +1,10 @@
|
||||||
#!/usr/bin/make -f
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
TRUECLOUDLAB_LINT_VERSION ?= 0.0.6
|
TRUECLOUDLAB_LINT_VERSION ?= 0.0.2
|
||||||
TMP_DIR := .cache
|
TMP_DIR := .cache
|
||||||
OUTPUT_LINT_DIR ?= $(shell pwd)/bin
|
OUTPUT_LINT_DIR ?= $(shell pwd)/bin
|
||||||
LINT_VERSION ?= 1.60.1
|
LINT_VERSION ?= 1.55.1
|
||||||
LINT_DIR = $(OUTPUT_LINT_DIR)/golangci-lint-$(LINT_VERSION)-v$(TRUECLOUDLAB_LINT_VERSION)
|
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
|
||||||
|
@ -22,10 +20,9 @@ imports:
|
||||||
@goimports -w .
|
@goimports -w .
|
||||||
|
|
||||||
# Run Unit Test with go test
|
# Run Unit Test with go test
|
||||||
test: GOFLAGS ?= "-count=1"
|
|
||||||
test:
|
test:
|
||||||
@echo "⇒ Running go test"
|
@echo "⇒ Running go test"
|
||||||
@GOFLAGS="$(GOFLAGS)" go test ./...
|
@go test ./... -count=1
|
||||||
|
|
||||||
# Activate pre-commit hooks
|
# Activate pre-commit hooks
|
||||||
pre-commit:
|
pre-commit:
|
||||||
|
@ -63,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
|
|
||||||
|
|
229
chain.go
Normal file
229
chain.go
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
package policyengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Engine ...
|
||||||
|
type Engine interface {
|
||||||
|
// IsAllowed returns status for the operation after all checks.
|
||||||
|
// The second return value signifies whether a matching rule was found.
|
||||||
|
IsAllowed(name Name, namespace string, r Request) (Status, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainID is the ID of rule chain.
|
||||||
|
type ChainID string
|
||||||
|
|
||||||
|
type Chain struct {
|
||||||
|
ID ChainID
|
||||||
|
|
||||||
|
Rules []Rule
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chain) Bytes() []byte {
|
||||||
|
data, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chain) DecodeBytes(b []byte) error {
|
||||||
|
return json.Unmarshal(b, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rule struct {
|
||||||
|
Status Status
|
||||||
|
// Actions the operation is applied to.
|
||||||
|
Actions Actions
|
||||||
|
// List of the resources the operation is applied to.
|
||||||
|
Resources Resources
|
||||||
|
// True iff individual conditions must be combined with the logical OR.
|
||||||
|
// By default AND is used, so _each_ condition must pass.
|
||||||
|
Any bool
|
||||||
|
Condition []Condition
|
||||||
|
}
|
||||||
|
|
||||||
|
type Actions struct {
|
||||||
|
Inverted bool
|
||||||
|
Names []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resources struct {
|
||||||
|
Inverted bool
|
||||||
|
Names []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Condition struct {
|
||||||
|
Op ConditionType
|
||||||
|
Object ObjectType
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObjectType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
ObjectResource ObjectType = iota
|
||||||
|
ObjectRequest
|
||||||
|
ObjectActor
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConditionType byte
|
||||||
|
|
||||||
|
// TODO @fyrchik: reduce the number of conditions.
|
||||||
|
// Everything from here should be expressable, but we do not need them all.
|
||||||
|
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html
|
||||||
|
const (
|
||||||
|
// String condition operators.
|
||||||
|
CondStringEquals ConditionType = iota
|
||||||
|
CondStringNotEquals
|
||||||
|
CondStringEqualsIgnoreCase
|
||||||
|
CondStringNotEqualsIgnoreCase
|
||||||
|
CondStringLike
|
||||||
|
CondStringNotLike
|
||||||
|
CondStringLessThan
|
||||||
|
CondStringLessThanEquals
|
||||||
|
CondStringGreaterThan
|
||||||
|
CondStringGreaterThanEquals
|
||||||
|
|
||||||
|
// Numeric condition operators.
|
||||||
|
CondNumericEquals
|
||||||
|
CondNumericNotEquals
|
||||||
|
CondNumericLessThan
|
||||||
|
CondNumericLessThanEquals
|
||||||
|
CondNumericGreaterThan
|
||||||
|
CondNumericGreaterThanEquals
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c ConditionType) String() string {
|
||||||
|
switch c {
|
||||||
|
case CondStringEquals:
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Condition) Match(req Request) bool {
|
||||||
|
var val string
|
||||||
|
switch c.Object {
|
||||||
|
case ObjectResource:
|
||||||
|
val = req.Resource().Property(c.Key)
|
||||||
|
case ObjectRequest:
|
||||||
|
val = req.Property(c.Key)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown condition type: %d", c.Object))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.Op {
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unimplemented: %d", c.Op))
|
||||||
|
case CondStringEquals:
|
||||||
|
return val == c.Value
|
||||||
|
case CondStringNotEquals:
|
||||||
|
return val != c.Value
|
||||||
|
case CondStringEqualsIgnoreCase:
|
||||||
|
return strings.EqualFold(val, c.Value)
|
||||||
|
case CondStringNotEqualsIgnoreCase:
|
||||||
|
return !strings.EqualFold(val, c.Value)
|
||||||
|
case CondStringLike:
|
||||||
|
return globMatch(val, c.Value)
|
||||||
|
case CondStringNotLike:
|
||||||
|
return !globMatch(val, c.Value)
|
||||||
|
case CondStringLessThan:
|
||||||
|
return val < c.Value
|
||||||
|
case CondStringLessThanEquals:
|
||||||
|
return val <= c.Value
|
||||||
|
case CondStringGreaterThan:
|
||||||
|
return val > c.Value
|
||||||
|
case CondStringGreaterThanEquals:
|
||||||
|
return val >= c.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rule) Match(req Request) (status Status, matched bool) {
|
||||||
|
found := len(r.Resources.Names) == 0
|
||||||
|
for i := range r.Resources.Names {
|
||||||
|
if globMatch(req.Resource().Name(), r.Resources.Names[i]) != r.Resources.Inverted {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return NoRuleFound, false
|
||||||
|
}
|
||||||
|
for i := range r.Actions.Names {
|
||||||
|
if globMatch(req.Operation(), r.Actions.Names[i]) != r.Actions.Inverted {
|
||||||
|
return r.matchCondition(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NoRuleFound, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rule) matchCondition(obj Request) (status Status, matched bool) {
|
||||||
|
if r.Any {
|
||||||
|
return r.matchAny(obj)
|
||||||
|
}
|
||||||
|
return r.matchAll(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rule) matchAny(obj Request) (status Status, matched bool) {
|
||||||
|
for i := range r.Condition {
|
||||||
|
if r.Condition[i].Match(obj) {
|
||||||
|
return r.Status, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NoRuleFound, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rule) matchAll(obj Request) (status Status, matched bool) {
|
||||||
|
for i := range r.Condition {
|
||||||
|
if !r.Condition[i].Match(obj) {
|
||||||
|
return NoRuleFound, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.Status, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chain) Match(req Request) (status Status, matched bool) {
|
||||||
|
for i := range c.Rules {
|
||||||
|
status, matched := c.Rules[i].Match(req)
|
||||||
|
if matched {
|
||||||
|
return status, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NoRuleFound, false
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package chain
|
package policyengine
|
||||||
|
|
||||||
// Name represents the place in the request lifecycle where policy is applied.
|
// Name represents the place in the request lifecycle where policy is applied.
|
||||||
type Name string
|
type Name string
|
||||||
|
@ -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"
|
|
||||||
)
|
)
|
33
chain_test.go
Normal file
33
chain_test.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package policyengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncodeDecode(t *testing.T) {
|
||||||
|
expected := Chain{
|
||||||
|
Rules: []Rule{
|
||||||
|
{
|
||||||
|
Status: Allow,
|
||||||
|
Actions: Actions{Names: []string{
|
||||||
|
"native::PutObject",
|
||||||
|
}},
|
||||||
|
Resources: Resources{Names: []string{"*"}},
|
||||||
|
Condition: []Condition{
|
||||||
|
{
|
||||||
|
Op: CondStringEquals,
|
||||||
|
Key: "Name",
|
||||||
|
Value: "NNS",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data := expected.Bytes()
|
||||||
|
|
||||||
|
var actual Chain
|
||||||
|
require.NoError(t, actual.DecodeBytes(data))
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
}
|
85
docs/ape.md
85
docs/ape.md
|
@ -1,85 +0,0 @@
|
||||||
# Access policy engine
|
|
||||||
|
|
||||||
## General overview
|
|
||||||
|
|
||||||
### Purpose
|
|
||||||
|
|
||||||
Access policy engine (APE) is aimed at checking if a request can be performed over a resource by looking up the set chains of rules.
|
|
||||||
|
|
||||||
#### Terms
|
|
||||||
|
|
||||||
| Term | Description | Structure overview |
|
|
||||||
| -------------- | -------------------------------------------------------------- | -------------- |
|
|
||||||
| `Request` | The action that is being performed on the `Resource`. | <ul><li>`Operation` - `GetObject`,`PutObject` etc.;</li><li>`Properties` - actor's public key, actor's attributes;</li><li>`Resource`.</li></ul> |
|
|
||||||
| `Resource` | The object that the request is being performed on. Check also [resource.md](./resource.md). | <ul><li>`Name` - strictly formatted string value;</li><li>`Properties`.</li></ul> |
|
|
||||||
| `Chain` | A chain of `Rule`-s defined for a specific target. Chains are strictly distinguished by `Name`-s , i.e. chains with name `ingress` are not intersected with chains with name `s3`. Chains are stored in serialized format. | <ul><li>Base64-encoded `ID`;</li><li>List of `Rule`-s;</li><li>`MatchType` - defines rule status selection priority.</li></ul> |
|
|
||||||
| `Rule` | `Rule` defines which status is returned if `Request` matches all conditions. | <ul><li>`Status`: `Allow`, `AccessDenied`, `QuotaLimitReached`, `NoRuleFound`;</li><li>`Actions` - operation defined by a schema (`GetObject`, `PutContainer` etc.);</li><li>`Resources`;</li><li>`Any` - if `true` then `Reqeust` matches `Rule` if any `Condition` is `true`;</li><li>`Conditions`.</li></ul> |
|
|
||||||
| `Name` | `Name` of a chain (do not confuse with chain ID). `Name` defines a layer of `Chain`'s usage, so chains are distinguished by `Name`-s. Basically, `Name` refers to a protocol. | String value (`ingress`, `s3`, `iam`). |
|
|
||||||
| `Target` | A scope of request. `Target` can be either simple (only namespace; only container; only user; only groups) or compound (namespace + container). | <ul><li>`Namespace`;</li><li>`Container`;</li><li>`User`;</li><li>`Groups`.</li></ul> |
|
|
||||||
| `Engine` | `Engine` checks a request in a scope defined by `Target`. First, it is trying to match a request with rules defined in `LocalOverrideStorage` and, then, in `MorphRuleChainStorage`. | <ul><li>`LocalOverrideStorage` - chains stored in the local override storage have the highest priority</li><li>`MorphRuleChainStorage` - basically, chains stored in `Policy` contract;</li><li>`ChainRouter` - looks up chains and try to match them with `Request`.</li></ul> |
|
|
||||||
|
|
||||||
#### Details
|
|
||||||
|
|
||||||
Here some entities are overviewed in more detail.
|
|
||||||
|
|
||||||
##### Resource
|
|
||||||
|
|
||||||
`Resource`'s name is strictly formatted, the format is defined by a schema (`native`, `aws` etc.). Examples:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# The resource is the particular object with the address within Root namespace
|
|
||||||
native:object//HRwWbb1bJjRms33kkA21hy4JdPfARaH3fW9NfuNN6Fgj/EbxzAdz5LB4uqxuz6crWKAumBNtZyK2rKsqQP7TdZvwr
|
|
||||||
# The resource is all objects within the container within Root namespace
|
|
||||||
native:object//HRwWbb1bJjRms33kkA21hy4JdPfARaH3fW9NfuNN6Fgj/*
|
|
||||||
# The resource is the particular container within the namespace
|
|
||||||
native:container/namespace1/HRwWbb1bJjRms33kkA21hy4JdPfARaH3fW9NfuNN6Fgj
|
|
||||||
# The resource is all containers within the namespace
|
|
||||||
native:container/namespace1/*
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Rule
|
|
||||||
|
|
||||||
`Rule` works out if:
|
|
||||||
|
|
||||||
1. a requests's operation matches the rule's `Actions`;
|
|
||||||
2. resource name matches the rule's `Resources`;
|
|
||||||
3. if all (or at least one if `Any=true`) conditions in `Condition` is met. Each condition defines how to retrieve
|
|
||||||
and compare the retrieved value. If `Condition`'s `Object` is set to `Resource` then the value is retrieved from the
|
|
||||||
resource's properties (example: container zone attribute). If `Object` is set to `Request`, the it's retrieved from the request's properties (example: actor's public key).
|
|
||||||
|
|
||||||
###### Name matching
|
|
||||||
|
|
||||||
`Resource`'s name in `Rule` may contain wildcard '*' that can be considered as a regular expression:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# The resource is all objects within the container within Root namespace
|
|
||||||
native:object//HRwWbb1bJjRms33kkA21hy4JdPfARaH3fW9NfuNN6Fgj/*
|
|
||||||
```
|
|
||||||
|
|
||||||
If an incoming request has such a resource name, then names are matched:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# The resource is all objects within the container within Root namespace
|
|
||||||
native:object//HRwWbb1bJjRms33kkA21hy4JdPfARaH3fW9NfuNN6Fgj/EbxzAdz5LB4uqxuz6crWKAumBNtZyK2rKsqQP7TdZvwr
|
|
||||||
```
|
|
||||||
|
|
||||||
If the incoming request has such a resource name that specifies a container's object within namespace, for instance, `namespicy`,
|
|
||||||
then matching does not work out:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# The resource is all objects within the container within `namespicy` namespace:
|
|
||||||
native:object/namespicy/HRwWbb1bJjRms33kkA21hy4JdPfARaH3fW9NfuNN6Fgj/EbxzAdz5LB4uqxuz6crWKAumBNtZyK2rKsqQP7TdZvwr
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Engine
|
|
||||||
|
|
||||||
`Engine` is trying to match the request against **the target** looking up chain rules, firstly, in `LocalOverrideStorage` (these rules are also known as *local overrides*) and then in `MorphRuleChainStorage` (contract `Policy`). Both storages iterate chain rules according to the specified priority of the targets: `namespace` -> `container` -> `user` -> `groups`.
|
|
||||||
|
|
||||||
#### Diagrams
|
|
||||||
|
|
||||||
The diagram demonstrates a scenario in Storage node. The request `A` cannot be performed as APE matched
|
|
||||||
the request and returned `Access Denied` status. The request `B` is allowed and the client gets `OK` status.
|
|
||||||
![Storage node](images/ape/storage_node_ape.svg)
|
|
||||||
|
|
||||||
The diagram demonstrates a complex scenario with S3, IAM and Storage node.
|
|
||||||
![S3 and IAM](images/ape/s3_ape.svg)
|
|
|
@ -1,61 +0,0 @@
|
||||||
@startuml s3 ape
|
|
||||||
|
|
||||||
participant "Client" as client
|
|
||||||
|
|
||||||
participant "IAM" as iam
|
|
||||||
participant "IAM -> APE converter" as converter
|
|
||||||
|
|
||||||
box "S3" #HotPink
|
|
||||||
participant "S3 gateway" as s3
|
|
||||||
end box
|
|
||||||
|
|
||||||
box "Access Policy Engine (as s3 middleware)" #LightPink
|
|
||||||
participant "Local override storage" as s3localOverrides
|
|
||||||
participant "Chain router" as s3chainRouter
|
|
||||||
end box
|
|
||||||
|
|
||||||
box "Policy contract (shared)"
|
|
||||||
participant "Morph rule storage" as morphRuleStorage
|
|
||||||
end box
|
|
||||||
|
|
||||||
box "Access Policy Engine (as storage middleware)" #LightGreen
|
|
||||||
participant "Chain Router" as storageChainRouter
|
|
||||||
participant "Local override storage" as storageLocalOverrides
|
|
||||||
end box
|
|
||||||
|
|
||||||
box "Storage node" #Green
|
|
||||||
participant "Object service" as obj
|
|
||||||
participant "Control service" as control
|
|
||||||
end box
|
|
||||||
|
|
||||||
group Request IAM to set a policy
|
|
||||||
client -> iam : Set IAM policy
|
|
||||||
iam -> converter : Convert IAM policy
|
|
||||||
converter -> iam : Return APE chain
|
|
||||||
iam -> morphRuleStorage : Store IAM policy and APE chain
|
|
||||||
iam -> s3localOverrides : Set S3 local overrides
|
|
||||||
iam -> client : OK
|
|
||||||
end
|
|
||||||
|
|
||||||
group Request S3 to set a policy
|
|
||||||
client -> s3 : Set bucket policy
|
|
||||||
s3 -> converter : Convert IAM policy
|
|
||||||
converter -> s3 : Return APE chain
|
|
||||||
s3 -> morphRuleStorage : Store bucket policy and APE chain
|
|
||||||
s3 -> client : OK
|
|
||||||
end
|
|
||||||
|
|
||||||
group Get object
|
|
||||||
client -> s3: GetObject
|
|
||||||
s3 -> s3chainRouter: Check if APE allows request for S3
|
|
||||||
note over s3chainRouter: matching the request with overrides and rules
|
|
||||||
s3chainRouter -> s3: Status: ALLOW
|
|
||||||
s3 -> obj: Get object
|
|
||||||
obj -> storageChainRouter: Check if APE allows the request
|
|
||||||
note over storageChainRouter : matching the request with overrides and rules
|
|
||||||
storageChainRouter -> obj: Status: ALLOW
|
|
||||||
obj -> s3: Response: OK, Object
|
|
||||||
s3 -> client: Response: OK, Object
|
|
||||||
end
|
|
||||||
|
|
||||||
@enduml
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 22 KiB |
|
@ -1,46 +0,0 @@
|
||||||
@startuml storage node ape
|
|
||||||
!pragma teoz true
|
|
||||||
|
|
||||||
participant "Administrator" as administrator
|
|
||||||
participant "Client" as client
|
|
||||||
|
|
||||||
box "Storage node" #Green
|
|
||||||
participant "Object service" as obj
|
|
||||||
participant "Control service" as control
|
|
||||||
end box
|
|
||||||
|
|
||||||
box "Access Policy Engine" #LightGreen
|
|
||||||
participant "Local override storage" as localOverrides
|
|
||||||
participant "Chain Router" as chainRouter
|
|
||||||
participant "Morph rule storage" as morphRuleStorage
|
|
||||||
end box
|
|
||||||
|
|
||||||
group Set local override
|
|
||||||
client -> control: Add local override
|
|
||||||
control -> localOverrides: Save override in DB
|
|
||||||
localOverrides -> control: OK
|
|
||||||
control -> client: OK
|
|
||||||
end
|
|
||||||
|
|
||||||
group Update state in Policy contract
|
|
||||||
administrator -> morphRuleStorage: Add chain
|
|
||||||
morphRuleStorage -> administrator: OK
|
|
||||||
end
|
|
||||||
|
|
||||||
group Perform a request A
|
|
||||||
client -> obj : Sending a request
|
|
||||||
obj -> chainRouter: Check if APE allows the request
|
|
||||||
note over chainRouter : Fetches local overrides and rules defined for a target/targets and looks for a match
|
|
||||||
chainRouter -> obj: APE returns status: "ACCESS DENIED"
|
|
||||||
obj -> client: Response: "the request is denied"
|
|
||||||
end
|
|
||||||
|
|
||||||
group Perform a request B
|
|
||||||
client -> obj : Sending a request
|
|
||||||
obj -> chainRouter: Check if APE allows the request
|
|
||||||
note over chainRouter : Fetches local overrides and rules defined for a target/targets and looks for a match
|
|
||||||
chainRouter -> obj: APE returns status: "ALLOW"
|
|
||||||
obj -> client: Response: "OK"
|
|
||||||
end
|
|
||||||
|
|
||||||
@enduml
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 17 KiB |
|
@ -1,439 +0,0 @@
|
||||||
# Policy converters
|
|
||||||
|
|
||||||
This repository contains converters that provide opportunities to
|
|
||||||
transform [AWS IAM policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html) to inner
|
|
||||||
FrostFS policy format. This document describes such transformations.
|
|
||||||
|
|
||||||
## FrostFS
|
|
||||||
|
|
||||||
As it was mentioned there are converters that transform AWS IAM policies to FrostFS.
|
|
||||||
Here common examples of AWS:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Version": "2012-10-17",
|
|
||||||
"Statement": [
|
|
||||||
{
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Action": [
|
|
||||||
"s3:*"
|
|
||||||
],
|
|
||||||
"Resource": "*"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
and FrostFS:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"ID": "c29tZS1pZA==",
|
|
||||||
"Rules": [
|
|
||||||
{
|
|
||||||
"Status": "Allow",
|
|
||||||
"Actions": {
|
|
||||||
"Inverted": false,
|
|
||||||
"Names": [
|
|
||||||
"s3:*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Resources": {
|
|
||||||
"Inverted": false,
|
|
||||||
"Names": [
|
|
||||||
"*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Any": false,
|
|
||||||
"Condition": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"MatchType": "DenyPriority"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
policies.
|
|
||||||
|
|
||||||
Despite there is only one FrostFS format, we have two converters (`s3` and `native`). The reason is S3 gateway and
|
|
||||||
Storage node have different actions and resource naming:
|
|
||||||
|
|
||||||
* S3 has [a lot of methods](https://docs.aws.amazon.com/AmazonS3/latest/API/API_Operations.html) and operates with
|
|
||||||
bucket/object
|
|
||||||
* Storage node has only 6 container and 7 object methods and operates container/object (that has different format)
|
|
||||||
|
|
||||||
The following sections describe each transformation more precisely ([common](#common) sections contains shared concepts)
|
|
||||||
|
|
||||||
### Common
|
|
||||||
|
|
||||||
#### Fields
|
|
||||||
|
|
||||||
Rough json main fields mapping:
|
|
||||||
|
|
||||||
| AWS policy field | FrostFS policy field | Comment |
|
|
||||||
|------------------|----------------------|------------------------------------------------------------------|
|
|
||||||
| `Version` | - | Not applicable |
|
|
||||||
| `Statement` | `Rules` | |
|
|
||||||
| `Effect` | `Status` | |
|
|
||||||
| `Action` | `Actions.Names` | `Actions.Inverted` = false |
|
|
||||||
| `NotAction` | `Actions.Names` | `Actions.Inverted` = true |
|
|
||||||
| `Resource` | `Resources.Names` | `Resources.Inverted` = false |
|
|
||||||
| `NotResource` | `Resources.Names` | `Resources.Inverted` = true |
|
|
||||||
| `Condition` | `Condition` | `Any` = false, that means the conditions must be hold altogether |
|
|
||||||
| `Principal` | - | Expressed via conditions (depends on s3/native converters) |
|
|
||||||
|
|
||||||
### Conditions
|
|
||||||
|
|
||||||
Each condition in FrostFS policy can add requirements to some request/resource properties
|
|
||||||
and consists of the following fields:
|
|
||||||
|
|
||||||
| Field | Description |
|
|
||||||
|----------|-------------------------------------------------------------------------------------------|
|
|
||||||
| `Op` | Condition type operation (`StringEqual`, `NumericEqual` etc) |
|
|
||||||
| `Object` | Property type to which condition can be applied (`Request` property, `Resource` property) |
|
|
||||||
| `Key` | Property key |
|
|
||||||
| `Value` | Property value |
|
|
||||||
|
|
||||||
Conditions operators:
|
|
||||||
|
|
||||||
| AWS conditions operator | FrostFS condition operator | Comment |
|
|
||||||
|-----------------------------|-----------------------------|-------------------------------------------------------------------|
|
|
||||||
| `StringEquals` | `StringEquals` | |
|
|
||||||
| `StringNotEquals` | `StringNotEquals` | |
|
|
||||||
| `StringEqualsIgnoreCase` | `StringEqualsIgnoreCase` | |
|
|
||||||
| `StringNotEqualsIgnoreCase` | `StringNotEqualsIgnoreCase` | |
|
|
||||||
| `StringLike` | `StringLike` | |
|
|
||||||
| `StringNotLike` | `StringNotLike` | |
|
|
||||||
| `NumericEquals` | `NumericEquals` | |
|
|
||||||
| `NumericNotEquals` | `NumericNotEquals` | |
|
|
||||||
| `NumericLessThan` | `NumericLessThan` | |
|
|
||||||
| `NumericLessThanEquals` | `NumericLessThanEquals` | |
|
|
||||||
| `NumericGreaterThan` | `NumericGreaterThan` | |
|
|
||||||
| `NumericGreaterThanEquals` | `NumericGreaterThanEquals` | |
|
|
||||||
| `DateEquals` | `StringEquals` | Date transforms to unix timestamp to be compared as string |
|
|
||||||
| `DateNotEquals` | `StringNotEquals` | Date transforms to unix timestamp to be compared as string |
|
|
||||||
| `DateLessThan` | `StringEqualsIgnoreCase` | Date transforms to unix timestamp to be compared as string |
|
|
||||||
| `DateLessThanEquals` | `StringNotEqualsIgnoreCase` | Date transforms to unix timestamp to be compared as string |
|
|
||||||
| `DateGreaterThan` | `StringLike` | Date transforms to unix timestamp to be compared as string |
|
|
||||||
| `DateGreaterThanEquals` | `StringNotLike` | Date transforms to unix timestamp to be compared as string |
|
|
||||||
| `Bool` | `StringEqualsIgnoreCase` | |
|
|
||||||
| `IpAddress` | `IPAddress` | |
|
|
||||||
| `NotIpAddress` | `NotIPAddress` | |
|
|
||||||
| `ArnEquals` | `StringEquals` | |
|
|
||||||
| `ArnLike` | `StringLike` | |
|
|
||||||
| `ArnNotEquals` | `StringNotEquals` | |
|
|
||||||
| `ArnNotLike` | `StringNotLike` | |
|
|
||||||
| `SliceContains` | `SliceContains` | AWS spec doesn't contain such operator. This is FrostFS extension |
|
|
||||||
|
|
||||||
For example, AWS conditions:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Condition": {
|
|
||||||
"ArnEquals": {"key16": ["val16"]},
|
|
||||||
"ArnNotEquals": {"key18": ["val18"]},
|
|
||||||
"ArnNotLike": {"key19": ["val19"]},
|
|
||||||
"Bool": {"key13": ["True"]},
|
|
||||||
"DateEquals": {"key7": ["2006-01-02T15:04:05+07:00"]},
|
|
||||||
"DateGreaterThan": {"key11": ["2006-01-02T15:04:05-01:00"]},
|
|
||||||
"DateGreaterThanEquals": {"key12": ["2006-01-02T15:04:05-03:00"]},
|
|
||||||
"DateLessThan": {"key9": ["2006-01-02T15:04:05+06:00"]},
|
|
||||||
"DateLessThanEquals": {"key10": ["2006-01-02T15:04:05+03:00"]},
|
|
||||||
"DateNotEquals": {"key8": ["2006-01-02T15:04:05Z"]},
|
|
||||||
"NumericEquals": {"key20": ["-20"]},
|
|
||||||
"NumericGreaterThan": {"key24": ["-24.24"]},
|
|
||||||
"NumericGreaterThanEquals": {"key25": ["+25.25"]},
|
|
||||||
"NumericLessThan": {"key22": ["0"]},
|
|
||||||
"NumericLessThanEquals": {"key23": ["23.23"]},
|
|
||||||
"NumericNotEquals": {"key21": ["+21"]},
|
|
||||||
"StringEquals": {"key1": ["val0"]},
|
|
||||||
"StringEqualsIgnoreCase": {"key3": ["val3"]},
|
|
||||||
"StringLike": {"key5": ["val5"]},
|
|
||||||
"StringNotEquals": {"key2": ["val2"]},
|
|
||||||
"StringNotEqualsIgnoreCase": {"key4": ["val4"]},
|
|
||||||
"StringNotLike": {"key6": ["val6"]}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
transforms to FrostFS conditions:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Condition": [
|
|
||||||
{"Op": "StringLike", "Object": "Request", "Key": "key5", "Value": "val5"},
|
|
||||||
{"Op": "StringNotEquals", "Object": "Request", "Key": "key2", "Value": "val2"},
|
|
||||||
{"Op": "StringGreaterThan", "Object": "Request", "Key": "key11", "Value": "1136217845"},
|
|
||||||
{"Op": "StringGreaterThanEquals", "Object": "Request", "Key": "key12", "Value": "1136225045"},
|
|
||||||
{"Op": "StringLessThan", "Object": "Request", "Key": "key9", "Value": "1136192645"},
|
|
||||||
{"Op": "StringEqualsIgnoreCase", "Object": "Request", "Key": "key3", "Value": "val3"},
|
|
||||||
{"Op": "StringEquals", "Object": "Request", "Key": "key16", "Value": "val16"},
|
|
||||||
{"Op": "NumericLessThanEquals", "Object": "Request", "Key": "key23", "Value": "23.23"},
|
|
||||||
{"Op": "StringNotEqualsIgnoreCase", "Object": "Request", "Key": "key4", "Value": "val4"},
|
|
||||||
{"Op": "StringEquals", "Object": "Request", "Key": "key1", "Value": "val0"},
|
|
||||||
{"Op": "StringLessThanEquals", "Object": "Request", "Key": "key10", "Value": "1136203445"},
|
|
||||||
{"Op": "NumericGreaterThan", "Object": "Request", "Key": "key24", "Value": "-24.24"},
|
|
||||||
{"Op": "NumericGreaterThanEquals", "Object": "Request", "Key": "key25", "Value": "+25.25"},
|
|
||||||
{"Op": "NumericLessThan", "Object": "Request", "Key": "key22", "Value": "0"},
|
|
||||||
{"Op": "StringNotEquals", "Object": "Request", "Key": "key8", "Value": "1136214245"},
|
|
||||||
{"Op": "NumericEquals", "Object": "Request", "Key": "key20", "Value": "-20"},
|
|
||||||
{"Op": "NumericNotEquals", "Object": "Request", "Key": "key21", "Value": "+21"},
|
|
||||||
{"Op": "StringNotLike", "Object": "Request", "Key": "key6", "Value": "val6"},
|
|
||||||
{"Op": "StringNotEquals", "Object": "Request", "Key": "key18", "Value": "val18"},
|
|
||||||
{"Op": "StringNotLike", "Object": "Request", "Key": "key19", "Value": "val19"},
|
|
||||||
{"Op": "StringEqualsIgnoreCase", "Object": "Request", "Key": "key13", "Value": "True"},
|
|
||||||
{"Op": "StringEquals", "Object": "Request", "Key": "key7", "Value": "1136189045"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### S3
|
|
||||||
|
|
||||||
#### Actions
|
|
||||||
|
|
||||||
Each action allows some s3-gw methods, so we must transform action to specific method names
|
|
||||||
(you can see exact mapping in table in [this file](../iam/converter_s3.go)).
|
|
||||||
|
|
||||||
For example the following actions:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Action": [
|
|
||||||
"s3:DeleteObject",
|
|
||||||
"iam:CreateUser"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
transforms to
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Actions": {
|
|
||||||
"Inverted": false,
|
|
||||||
"Names": [
|
|
||||||
"s3:DeleteObject",
|
|
||||||
"s3:DeleteMultipleObjects",
|
|
||||||
"iam:CreateUser"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
As we can see any `iam:*` action transformed as it is. But `s3:*` actions transforms according to
|
|
||||||
[spec rules](https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazons3.html) and s3-gw
|
|
||||||
[method names](https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/src/commit/2ab655b909c40db6f7a4e41e07d8b99167f791bd/api/middleware/constants.go#L3-L76).
|
|
||||||
|
|
||||||
#### Resources
|
|
||||||
|
|
||||||
Resource is transformed as it is:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Resource": [
|
|
||||||
"arn:aws:s3:::bucket/object"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Resources": {
|
|
||||||
"Inverted": false,
|
|
||||||
"Names": [
|
|
||||||
"arn:aws:s3:::bucket/object"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Principals
|
|
||||||
|
|
||||||
To check user s3-gw uses special condition request property (`Owner`), so when AWS policy contains principal field
|
|
||||||
it transforms to rule with appropriate condition. To get correct `Owner` property value special user resolver
|
|
||||||
(`S3Resolver` interface in [converter_s3 file](../iam/converter_s3.go)) must be provided into convert function.
|
|
||||||
|
|
||||||
For example such AWS json statement:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Action": "*",
|
|
||||||
"Resource": "*",
|
|
||||||
"Principal": {
|
|
||||||
"AWS": "arn:aws:iam::111122223333:user/JohnDoe"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
transforms to the following FrostFS rule:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Status": "Allow",
|
|
||||||
"Actions": {
|
|
||||||
"Inverted": false,
|
|
||||||
"Names": [
|
|
||||||
"*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Resources": {
|
|
||||||
"Inverted": false,
|
|
||||||
"Names": [
|
|
||||||
"*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Any": false,
|
|
||||||
"Condition": [
|
|
||||||
{
|
|
||||||
"Op": "StringEquals",
|
|
||||||
"Object": "Request",
|
|
||||||
"Key": "Owner",
|
|
||||||
"Value": "NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Native
|
|
||||||
|
|
||||||
#### Actions
|
|
||||||
|
|
||||||
Each action allows some frostfs methods, so we must transform action to specific method names
|
|
||||||
(you can see exact mapping in table in [this file](../iam/converter_native.go)).
|
|
||||||
|
|
||||||
For example the following actions:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Action": [
|
|
||||||
"s3:DeleteObject",
|
|
||||||
"iam:CreateUser"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
transforms to
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Actions": {
|
|
||||||
"Inverted": false,
|
|
||||||
"Names": [
|
|
||||||
"PutObject",
|
|
||||||
"HeadObject",
|
|
||||||
"GetObject",
|
|
||||||
"RangeObject",
|
|
||||||
"GetContainer",
|
|
||||||
"DeleteObject"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note:** Only subset of s3:* actions can be transformed (exact value you can see in mapping table mentioned before).
|
|
||||||
> If all provided actions is not applicable converter function returns appropriate error.
|
|
||||||
|
|
||||||
Native methods (to which original actions are transformed) depend on which methods are invoked by appropriate s3-gw
|
|
||||||
[method](https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/src/commit/2ab655b909c40db6f7a4e41e07d8b99167f791bd/api/middleware/constants.go#L3-L76).
|
|
||||||
|
|
||||||
So in example above s3-gw during performing DeleteObject methods invokes the following methods:
|
|
||||||
`["PutObject","HeadObject","GetObject","RangeObject","GetContainer","DeleteObject"]`
|
|
||||||
|
|
||||||
#### Resources
|
|
||||||
|
|
||||||
To transform resources the following is being performed:
|
|
||||||
|
|
||||||
* Bucket name is resoled to container id (by providing `NativeResolver` interface implementation to converter)
|
|
||||||
* Object name is transformed to condition with special `FilePath` attribute
|
|
||||||
(that present on every object that was uploaded via s3-gw)
|
|
||||||
|
|
||||||
For example, the following AWS policy statement:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Principal": "*",
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Action": "*",
|
|
||||||
"Resource": "arn:aws:s3:::bucket/object"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
transforms to FrostFS native policy rule:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Status": "Allow",
|
|
||||||
"Actions": {
|
|
||||||
"Inverted": false,
|
|
||||||
"Names": [
|
|
||||||
"*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Resources": {
|
|
||||||
"Inverted": false,
|
|
||||||
"Names": [
|
|
||||||
"native:object//bucket/HFq67qbfhFEiEL7qDXqayo3F78yAvxXSXzwSa2hKM9bH/*",
|
|
||||||
"native:container//bucket/HFq67qbfhFEiEL7qDXqayo3F78yAvxXSXzwSa2hKM9bH"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Any": false,
|
|
||||||
"Condition": [
|
|
||||||
{
|
|
||||||
"Op": "StringLike",
|
|
||||||
"Object": "Resource",
|
|
||||||
"Key": "FilePath",
|
|
||||||
"Value": "object"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Principals
|
|
||||||
|
|
||||||
To check user s3-gw uses special condition request property (`$Actor:publicKey`), so when AWS policy contains principal
|
|
||||||
field it transforms to rule with appropriate condition. To get correct `$Actor:publicKey` property value
|
|
||||||
special user resolver (`NativeResolver` interface in [converter_native file](../iam/converter_native.go)) must be
|
|
||||||
provided into convert function.
|
|
||||||
|
|
||||||
For example such AWS json statement:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Action": "*",
|
|
||||||
"Resource": "*",
|
|
||||||
"Principal": {
|
|
||||||
"AWS": "arn:aws:iam::111122223333:user/JohnDoe"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
transforms to the following FrostFS rule:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Status": "Allow",
|
|
||||||
"Actions": {
|
|
||||||
"Inverted": false,
|
|
||||||
"Names": [
|
|
||||||
"*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Resources": {
|
|
||||||
"Inverted": false,
|
|
||||||
"Names": [
|
|
||||||
"native:object/*",
|
|
||||||
"native:container/*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Any": false,
|
|
||||||
"Condition": [
|
|
||||||
{
|
|
||||||
"Op": "StringEquals",
|
|
||||||
"Object": "Request",
|
|
||||||
"Key": "$Actor:publicKey",
|
|
||||||
"Value": "031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -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.
|
|
|
@ -1,4 +1,4 @@
|
||||||
package chain
|
package policyengine
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package util
|
package policyengine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -9,7 +9,7 @@ import (
|
||||||
// ? in pattern correspond to any symbol.
|
// ? in pattern correspond to any symbol.
|
||||||
// * in pattern correspond to any sequence of symbols.
|
// * in pattern correspond to any sequence of symbols.
|
||||||
// Currently only '*' in the suffix is supported.
|
// Currently only '*' in the suffix is supported.
|
||||||
func GlobMatch(s, pattern string) bool {
|
func globMatch(s, pattern string) bool {
|
||||||
index := strings.IndexByte(pattern, '*')
|
index := strings.IndexByte(pattern, '*')
|
||||||
switch index {
|
switch index {
|
||||||
default:
|
default:
|
28
go.mod
28
go.mod
|
@ -1,35 +1,11 @@
|
||||||
module git.frostfs.info/TrueCloudLab/policy-engine
|
module git.frostfs.info/TrueCloudLab/policy-engine
|
||||||
|
|
||||||
go 1.22
|
go 1.20
|
||||||
|
|
||||||
require (
|
require github.com/stretchr/testify v1.8.1
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240409111539-e7a05a49ff45
|
|
||||||
github.com/google/uuid v1.3.1
|
|
||||||
github.com/mailru/easyjson v0.7.7
|
|
||||||
github.com/nspcc-dev/neo-go v0.105.0
|
|
||||||
github.com/stretchr/testify v1.8.4
|
|
||||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|
167
go.sum
167
go.sum
|
@ -1,166 +1,17 @@
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240409111539-e7a05a49ff45 h1:Tp4I+XOLp3VCJORfxSamQtj3RZNISbaLM4WD5iIzXxg=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240409111539-e7a05a49ff45/go.mod h1:F/fe1OoIDKr5Bz99q4sriuHDuf3aZefZy9ZsCqEtgxc=
|
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
|
||||||
github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
|
|
||||||
github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
|
|
||||||
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
|
|
||||||
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb h1:f0BMgIjhZy4lSRHCXFbQst85f5agZAjtDMixQqBWNpc=
|
|
||||||
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 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/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/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/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
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/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
|
|
||||||
github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
|
||||||
github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
|
|
||||||
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
|
|
||||||
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
|
|
||||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
|
||||||
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
|
||||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20231123160306-3374ff1e7a3c h1:OOQeE613BH93ICPq3eke5N78gWNeMjcBWkmD2NKyXVg=
|
|
||||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20231123160306-3374ff1e7a3c/go.mod h1:79bEUDEviBHJMFV6Iq6in57FEOCMcRhfQnfaf0ETA5U=
|
|
||||||
github.com/nspcc-dev/neo-go v0.105.0 h1:vtNZYFEFySK8zRDhLzQYha849VzWrcKezlnq/oNQg/w=
|
|
||||||
github.com/nspcc-dev/neo-go v0.105.0/go.mod h1:6pchIHg5okeZO955RxpTh5q0sUI0vtpgPM6Q+no1rlI=
|
|
||||||
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-20231127165613-b35f351f0ba0/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/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs=
|
|
||||||
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/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
|
||||||
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo=
|
|
||||||
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c=
|
|
||||||
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
|
|
||||||
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
|
||||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
|
||||||
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
|
||||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
|
||||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
|
||||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
|
||||||
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/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
|
||||||
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.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
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/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E=
|
|
||||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
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=
|
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
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=
|
|
||||||
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
|
|
||||||
|
|
487
iam/converter.go
487
iam/converter.go
|
@ -1,78 +1,16 @@
|
||||||
package iam
|
package iam
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
s3ActionAbortMultipartUpload = "s3:AbortMultipartUpload"
|
RequestOwnerProperty = "Owner"
|
||||||
s3ActionCreateBucket = "s3:CreateBucket"
|
|
||||||
s3ActionDeleteBucket = "s3:DeleteBucket"
|
|
||||||
s3ActionDeleteBucketPolicy = "s3:DeleteBucketPolicy"
|
|
||||||
s3ActionDeleteObject = "s3:DeleteObject"
|
|
||||||
s3ActionDeleteObjectTagging = "s3:DeleteObjectTagging"
|
|
||||||
s3ActionDeleteObjectVersion = "s3:DeleteObjectVersion"
|
|
||||||
s3ActionDeleteObjectVersionTagging = "s3:DeleteObjectVersionTagging"
|
|
||||||
s3ActionGetBucketACL = "s3:GetBucketAcl"
|
|
||||||
s3ActionGetBucketCORS = "s3:GetBucketCORS"
|
|
||||||
s3ActionGetBucketLocation = "s3:GetBucketLocation"
|
|
||||||
s3ActionGetBucketNotification = "s3:GetBucketNotification"
|
|
||||||
s3ActionGetBucketObjectLockConfiguration = "s3:GetBucketObjectLockConfiguration"
|
|
||||||
s3ActionGetBucketPolicy = "s3:GetBucketPolicy"
|
|
||||||
s3ActionGetBucketPolicyStatus = "s3:GetBucketPolicyStatus"
|
|
||||||
s3ActionGetBucketTagging = "s3:GetBucketTagging"
|
|
||||||
s3ActionGetBucketVersioning = "s3:GetBucketVersioning"
|
|
||||||
s3ActionGetLifecycleConfiguration = "s3:GetLifecycleConfiguration"
|
|
||||||
s3ActionGetObject = "s3:GetObject"
|
|
||||||
s3ActionGetObjectACL = "s3:GetObjectAcl"
|
|
||||||
s3ActionGetObjectAttributes = "s3:GetObjectAttributes"
|
|
||||||
s3ActionGetObjectLegalHold = "s3:GetObjectLegalHold"
|
|
||||||
s3ActionGetObjectRetention = "s3:GetObjectRetention"
|
|
||||||
s3ActionGetObjectTagging = "s3:GetObjectTagging"
|
|
||||||
s3ActionGetObjectVersion = "s3:GetObjectVersion"
|
|
||||||
s3ActionGetObjectVersionACL = "s3:GetObjectVersionAcl"
|
|
||||||
s3ActionGetObjectVersionAttributes = "s3:GetObjectVersionAttributes"
|
|
||||||
s3ActionGetObjectVersionTagging = "s3:GetObjectVersionTagging"
|
|
||||||
s3ActionListAllMyBuckets = "s3:ListAllMyBuckets"
|
|
||||||
s3ActionListBucket = "s3:ListBucket"
|
|
||||||
s3ActionListBucketMultipartUploads = "s3:ListBucketMultipartUploads"
|
|
||||||
s3ActionListBucketVersions = "s3:ListBucketVersions"
|
|
||||||
s3ActionListMultipartUploadParts = "s3:ListMultipartUploadParts"
|
|
||||||
s3ActionPutBucketACL = "s3:PutBucketAcl"
|
|
||||||
s3ActionPutBucketCORS = "s3:PutBucketCORS"
|
|
||||||
s3ActionPutBucketNotification = "s3:PutBucketNotification"
|
|
||||||
s3ActionPutBucketObjectLockConfiguration = "s3:PutBucketObjectLockConfiguration"
|
|
||||||
s3ActionPutBucketPolicy = "s3:PutBucketPolicy"
|
|
||||||
s3ActionPutBucketTagging = "s3:PutBucketTagging"
|
|
||||||
s3ActionPutBucketVersioning = "s3:PutBucketVersioning"
|
|
||||||
s3ActionPutLifecycleConfiguration = "s3:PutLifecycleConfiguration"
|
|
||||||
s3ActionPutObject = "s3:PutObject"
|
|
||||||
s3ActionPutObjectACL = "s3:PutObjectAcl"
|
|
||||||
s3ActionPutObjectLegalHold = "s3:PutObjectLegalHold"
|
|
||||||
s3ActionPutObjectRetention = "s3:PutObjectRetention"
|
|
||||||
s3ActionPutObjectTagging = "s3:PutObjectTagging"
|
|
||||||
s3ActionPutObjectVersionACL = "s3:PutObjectVersionAcl"
|
|
||||||
s3ActionPutObjectVersionTagging = "s3:PutObjectVersionTagging"
|
|
||||||
s3ActionPatchObject = "s3:PatchObject"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
condKeyAWSPrincipalARN = "aws:PrincipalArn"
|
|
||||||
condKeyAWSSourceIP = "aws:SourceIp"
|
|
||||||
condKeyAWSPrincipalTagPrefix = "aws:PrincipalTag/"
|
|
||||||
condKeyAWSRequestTagPrefix = "aws:RequestTag/"
|
|
||||||
condKeyAWSResourceTagPrefix = "aws:ResourceTag/"
|
|
||||||
userClaimTagPrefix = "tag-"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -112,213 +50,178 @@ 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 (
|
func (p Policy) ToChain() (*policyengine.Chain, error) {
|
||||||
arnIAMPrefix = "arn:aws:iam::"
|
if err := p.Validate(GeneralPolicyType); err != nil {
|
||||||
s3ResourcePrefix = "arn:aws:s3:::"
|
return nil, err
|
||||||
s3ActionPrefix = "s3:"
|
}
|
||||||
iamActionPrefix = "iam:"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var chain policyengine.Chain
|
||||||
// ErrInvalidPrincipalFormat occurs when principal has unknown/unsupported format.
|
|
||||||
ErrInvalidPrincipalFormat = errors.New("invalid principal format")
|
|
||||||
|
|
||||||
// ErrInvalidResourceFormat occurs when resource has unknown/unsupported format.
|
for _, statement := range p.Statement {
|
||||||
ErrInvalidResourceFormat = errors.New("invalid resource format")
|
status := policyengine.AccessDenied
|
||||||
|
if statement.Effect == AllowEffect {
|
||||||
|
status = policyengine.Allow
|
||||||
|
}
|
||||||
|
|
||||||
// ErrInvalidActionFormat occurs when action has unknown/unsupported format.
|
var principals []string
|
||||||
ErrInvalidActionFormat = errors.New("invalid action format")
|
var op policyengine.ConditionType
|
||||||
|
statementPrincipal, inverted := statement.principal()
|
||||||
|
if _, ok := statementPrincipal[Wildcard]; ok { // this can be true only if 'inverted' false
|
||||||
|
principals = []string{Wildcard}
|
||||||
|
op = policyengine.CondStringLike
|
||||||
|
} else {
|
||||||
|
for _, principal := range statementPrincipal {
|
||||||
|
principals = append(principals, principal...)
|
||||||
|
}
|
||||||
|
|
||||||
// ErrActionsNotApplicable occurs when failed to convert any actions.
|
op = policyengine.CondStringEquals
|
||||||
ErrActionsNotApplicable = errors.New("actions not applicable")
|
if inverted {
|
||||||
)
|
op = policyengine.CondStringNotEquals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type formPrincipalConditionFunc func(string) chain.Condition
|
var conditions []policyengine.Condition
|
||||||
|
for _, principal := range principals {
|
||||||
|
conditions = append(conditions, policyengine.Condition{
|
||||||
|
Op: op,
|
||||||
|
Object: policyengine.ObjectRequest,
|
||||||
|
Key: RequestOwnerProperty,
|
||||||
|
Value: principal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type transformConditionFunc func(gr GroupedConditions) (GroupedConditions, error)
|
conds, err := statement.Conditions.ToChainCondition()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conditions = append(conditions, conds...)
|
||||||
|
|
||||||
func convertToChainConditions(c Conditions, transformer transformConditionFunc) ([]GroupedConditions, error) {
|
action, actionInverted := statement.action()
|
||||||
conditions, err := convertToChainCondition(c)
|
ruleAction := policyengine.Actions{Inverted: actionInverted, Names: action}
|
||||||
|
|
||||||
|
resource, resourceInverted := statement.resource()
|
||||||
|
ruleResource := policyengine.Resources{Inverted: resourceInverted, Names: resource}
|
||||||
|
|
||||||
|
r := policyengine.Rule{
|
||||||
|
Status: status,
|
||||||
|
Actions: ruleAction,
|
||||||
|
Resources: ruleResource,
|
||||||
|
Any: true,
|
||||||
|
Condition: conditions,
|
||||||
|
}
|
||||||
|
chain.Rules = append(chain.Rules, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &chain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:funlen
|
||||||
|
func (c Conditions) ToChainCondition() ([]policyengine.Condition, error) {
|
||||||
|
var conditions []policyengine.Condition
|
||||||
|
|
||||||
|
var convertValue convertFunction
|
||||||
|
|
||||||
|
for op, KVs := range c {
|
||||||
|
var condType policyengine.ConditionType
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(op, "String"):
|
||||||
|
convertValue = noConvertFunction
|
||||||
|
switch op {
|
||||||
|
case CondStringEquals:
|
||||||
|
condType = policyengine.CondStringEquals
|
||||||
|
case CondStringNotEquals:
|
||||||
|
condType = policyengine.CondStringNotEquals
|
||||||
|
case CondStringEqualsIgnoreCase:
|
||||||
|
condType = policyengine.CondStringEqualsIgnoreCase
|
||||||
|
case CondStringNotEqualsIgnoreCase:
|
||||||
|
condType = policyengine.CondStringNotEqualsIgnoreCase
|
||||||
|
case CondStringLike:
|
||||||
|
condType = policyengine.CondStringLike
|
||||||
|
case CondStringNotLike:
|
||||||
|
condType = policyengine.CondStringNotLike
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(op, "Arn"):
|
||||||
|
convertValue = noConvertFunction
|
||||||
|
switch op {
|
||||||
|
case CondArnEquals:
|
||||||
|
condType = policyengine.CondStringEquals
|
||||||
|
case CondArnNotEquals:
|
||||||
|
condType = policyengine.CondStringNotEquals
|
||||||
|
case CondArnLike:
|
||||||
|
condType = policyengine.CondStringLike
|
||||||
|
case CondArnNotLike:
|
||||||
|
condType = policyengine.CondStringNotLike
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(op, "Numeric"):
|
||||||
|
// TODO
|
||||||
|
case strings.HasPrefix(op, "Date"):
|
||||||
|
convertValue = dateConvertFunction
|
||||||
|
switch op {
|
||||||
|
case CondDateEquals:
|
||||||
|
condType = policyengine.CondStringEquals
|
||||||
|
case CondDateNotEquals:
|
||||||
|
condType = policyengine.CondStringNotEquals
|
||||||
|
case CondDateLessThan:
|
||||||
|
condType = policyengine.CondStringLessThan
|
||||||
|
case CondDateLessThanEquals:
|
||||||
|
condType = policyengine.CondStringLessThanEquals
|
||||||
|
case CondDateGreaterThan:
|
||||||
|
condType = policyengine.CondStringGreaterThan
|
||||||
|
case CondDateGreaterThanEquals:
|
||||||
|
condType = policyengine.CondStringGreaterThanEquals
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||||
|
}
|
||||||
|
case op == CondBool:
|
||||||
|
convertValue = noConvertFunction
|
||||||
|
condType = policyengine.CondStringEqualsIgnoreCase
|
||||||
|
case op == CondIPAddress:
|
||||||
|
// todo consider using converters
|
||||||
|
// "203.0.113.0/24" -> "203.0.113.*",
|
||||||
|
// "2001:DB8:1234:5678::/64" -> "2001:DB8:1234:5678:*"
|
||||||
|
// or having specific condition type for IP
|
||||||
|
convertValue = noConvertFunction
|
||||||
|
condType = policyengine.CondStringLike
|
||||||
|
case op == CondNotIPAddress:
|
||||||
|
convertValue = noConvertFunction
|
||||||
|
condType = policyengine.CondStringNotLike
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, values := range KVs {
|
||||||
|
for _, val := range values {
|
||||||
|
converted, err := convertValue(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range conditions {
|
conditions = append(conditions, policyengine.Condition{
|
||||||
if conditions[i], err = transformer(conditions[i]); err != nil {
|
Op: condType,
|
||||||
return nil, fmt.Errorf("transform condition: %w", err)
|
Object: policyengine.ObjectRequest,
|
||||||
|
Key: key,
|
||||||
|
Value: converted,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return conditions, nil
|
return conditions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupedConditions struct {
|
|
||||||
Conditions []chain.Condition
|
|
||||||
Any bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertToChainCondition(c Conditions) ([]GroupedConditions, error) {
|
|
||||||
var grouped []GroupedConditions
|
|
||||||
|
|
||||||
for op, KVs := range c {
|
|
||||||
condType, convertValue, err := getConditionTypeAndConverter(op)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, values := range KVs {
|
|
||||||
group := GroupedConditions{
|
|
||||||
Conditions: make([]chain.Condition, len(values)),
|
|
||||||
Any: len(values) > 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, val := range values {
|
|
||||||
converted, err := convertValue(val)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
group.Conditions[i] = chain.Condition{
|
|
||||||
Op: condType,
|
|
||||||
Kind: chain.KindRequest,
|
|
||||||
Key: transformKey(key),
|
|
||||||
Value: converted,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
grouped = append(grouped, group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return grouped, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func transformKey(key string) string {
|
|
||||||
tagName, isTag := strings.CutPrefix(key, condKeyAWSPrincipalTagPrefix)
|
|
||||||
if isTag {
|
|
||||||
return fmt.Sprintf(common.PropertyKeyFormatFrostFSIDUserClaim, userClaimTagPrefix+tagName)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case condKeyAWSSourceIP:
|
|
||||||
return common.PropertyKeyFrostFSSourceIP
|
|
||||||
}
|
|
||||||
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConditionTypeAndConverter(op string) (chain.ConditionType, convertFunction, error) {
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(op, "String"):
|
|
||||||
switch op {
|
|
||||||
case CondStringEquals:
|
|
||||||
return chain.CondStringEquals, noConvertFunction, nil
|
|
||||||
case CondStringNotEquals:
|
|
||||||
return chain.CondStringNotEquals, noConvertFunction, nil
|
|
||||||
case CondStringEqualsIgnoreCase:
|
|
||||||
return chain.CondStringEqualsIgnoreCase, noConvertFunction, nil
|
|
||||||
case CondStringNotEqualsIgnoreCase:
|
|
||||||
return chain.CondStringNotEqualsIgnoreCase, noConvertFunction, nil
|
|
||||||
case CondStringLike:
|
|
||||||
return chain.CondStringLike, noConvertFunction, nil
|
|
||||||
case CondStringNotLike:
|
|
||||||
return chain.CondStringNotLike, noConvertFunction, nil
|
|
||||||
default:
|
|
||||||
return 0, nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
|
||||||
}
|
|
||||||
case strings.HasPrefix(op, "Arn"):
|
|
||||||
switch op {
|
|
||||||
case CondArnEquals:
|
|
||||||
return chain.CondStringEquals, noConvertFunction, nil
|
|
||||||
case CondArnNotEquals:
|
|
||||||
return chain.CondStringNotEquals, noConvertFunction, nil
|
|
||||||
case CondArnLike:
|
|
||||||
return chain.CondStringLike, noConvertFunction, nil
|
|
||||||
case CondArnNotLike:
|
|
||||||
return chain.CondStringNotLike, noConvertFunction, nil
|
|
||||||
default:
|
|
||||||
return 0, nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
|
||||||
}
|
|
||||||
case strings.HasPrefix(op, "Numeric"):
|
|
||||||
return numericConditionTypeAndConverter(op)
|
|
||||||
case strings.HasPrefix(op, "Date"):
|
|
||||||
switch op {
|
|
||||||
case CondDateEquals:
|
|
||||||
return chain.CondStringEquals, dateConvertFunction, nil
|
|
||||||
case CondDateNotEquals:
|
|
||||||
return chain.CondStringNotEquals, dateConvertFunction, nil
|
|
||||||
case CondDateLessThan:
|
|
||||||
return chain.CondStringLessThan, dateConvertFunction, nil
|
|
||||||
case CondDateLessThanEquals:
|
|
||||||
return chain.CondStringLessThanEquals, dateConvertFunction, nil
|
|
||||||
case CondDateGreaterThan:
|
|
||||||
return chain.CondStringGreaterThan, dateConvertFunction, nil
|
|
||||||
case CondDateGreaterThanEquals:
|
|
||||||
return chain.CondStringGreaterThanEquals, dateConvertFunction, nil
|
|
||||||
default:
|
|
||||||
return 0, nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
|
||||||
}
|
|
||||||
case op == CondBool:
|
|
||||||
return chain.CondStringEqualsIgnoreCase, noConvertFunction, nil
|
|
||||||
case op == CondIPAddress:
|
|
||||||
return chain.CondIPAddress, ipConvertFunction, nil
|
|
||||||
case op == CondNotIPAddress:
|
|
||||||
return chain.CondNotIPAddress, ipConvertFunction, nil
|
|
||||||
case op == CondSliceContains:
|
|
||||||
return chain.CondSliceContains, noConvertFunction, nil
|
|
||||||
default:
|
|
||||||
return 0, nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func numericConditionTypeAndConverter(op string) (chain.ConditionType, convertFunction, error) {
|
|
||||||
switch op {
|
|
||||||
case CondNumericEquals:
|
|
||||||
return chain.CondNumericEquals, numericConvertFunction, nil
|
|
||||||
case CondNumericNotEquals:
|
|
||||||
return chain.CondNumericNotEquals, numericConvertFunction, nil
|
|
||||||
case CondNumericLessThan:
|
|
||||||
return chain.CondNumericLessThan, numericConvertFunction, nil
|
|
||||||
case CondNumericLessThanEquals:
|
|
||||||
return chain.CondNumericLessThanEquals, numericConvertFunction, nil
|
|
||||||
case CondNumericGreaterThan:
|
|
||||||
return chain.CondNumericGreaterThan, numericConvertFunction, nil
|
|
||||||
case CondNumericGreaterThanEquals:
|
|
||||||
return chain.CondNumericGreaterThanEquals, numericConvertFunction, nil
|
|
||||||
default:
|
|
||||||
return 0, nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type convertFunction func(string) (string, error)
|
type convertFunction func(string) (string, error)
|
||||||
|
|
||||||
func noConvertFunction(val string) (string, error) {
|
func noConvertFunction(val string) (string, error) {
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func numericConvertFunction(val string) (string, error) {
|
|
||||||
if _, err := fixedn.Fixed8FromString(val); err == nil {
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("invalid numeric value: '%s'", val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ipConvertFunction(val string) (string, error) {
|
|
||||||
if _, err := netip.ParsePrefix(val); err != nil {
|
|
||||||
if _, err = netip.ParseAddr(val); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
val += "/32"
|
|
||||||
}
|
|
||||||
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dateConvertFunction(val string) (string, error) {
|
func dateConvertFunction(val string) (string, error) {
|
||||||
if _, err := strconv.ParseInt(val, 10, 64); err == nil {
|
if _, err := strconv.ParseInt(val, 10, 64); err == nil {
|
||||||
return val, nil
|
return val, nil
|
||||||
|
@ -331,95 +234,3 @@ func dateConvertFunction(val string) (string, error) {
|
||||||
|
|
||||||
return strconv.FormatInt(parsed.UTC().Unix(), 10), nil
|
return strconv.FormatInt(parsed.UTC().Unix(), 10), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePrincipalAsIAMUser(principal string) (account string, user string, err error) {
|
|
||||||
if !strings.HasPrefix(principal, arnIAMPrefix) {
|
|
||||||
return "", "", ErrInvalidPrincipalFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
// iam arn format arn:aws:iam::<account>:user/<user-name-with-path>
|
|
||||||
iamResource := strings.TrimPrefix(principal, arnIAMPrefix)
|
|
||||||
sepIndex := strings.Index(iamResource, ":user/")
|
|
||||||
if sepIndex < 0 {
|
|
||||||
return "", "", ErrInvalidPrincipalFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
account = iamResource[:sepIndex]
|
|
||||||
user = iamResource[sepIndex+6:]
|
|
||||||
if len(user) == 0 {
|
|
||||||
return "", "", ErrInvalidPrincipalFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
userNameIndex := strings.LastIndexByte(user, '/')
|
|
||||||
if userNameIndex > -1 {
|
|
||||||
user = user[userNameIndex+1:]
|
|
||||||
if len(user) == 0 {
|
|
||||||
return "", "", ErrInvalidPrincipalFormat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return account, user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateResource(resource string) error {
|
|
||||||
if resource == Wildcard {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(resource, s3ResourcePrefix) && !strings.HasPrefix(resource, arnIAMPrefix) {
|
|
||||||
return ErrInvalidResourceFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
index := strings.IndexByte(resource, Wildcard[0])
|
|
||||||
if index != -1 && index != utf8.RuneCountInString(resource)-1 {
|
|
||||||
return ErrInvalidResourceFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateAction(action string) (bool, error) {
|
|
||||||
isIAM := strings.HasPrefix(action, iamActionPrefix)
|
|
||||||
if !strings.HasPrefix(action, s3ActionPrefix) && !isIAM {
|
|
||||||
return false, ErrInvalidActionFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
index := strings.IndexByte(action, Wildcard[0])
|
|
||||||
if index != -1 && index != utf8.RuneCountInString(action)-1 {
|
|
||||||
return false, ErrInvalidActionFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
return isIAM, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitGroupedConditions(groupedConditions []GroupedConditions) [][]chain.Condition {
|
|
||||||
var orConditions []chain.Condition
|
|
||||||
commonConditions := make([]chain.Condition, 0, len(groupedConditions))
|
|
||||||
for _, grouped := range groupedConditions {
|
|
||||||
if grouped.Any {
|
|
||||||
orConditions = append(orConditions, grouped.Conditions...)
|
|
||||||
} else {
|
|
||||||
commonConditions = append(commonConditions, grouped.Conditions...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(orConditions) == 0 {
|
|
||||||
return [][]chain.Condition{commonConditions}
|
|
||||||
}
|
|
||||||
|
|
||||||
res := make([][]chain.Condition, len(orConditions))
|
|
||||||
for i, condition := range orConditions {
|
|
||||||
res[i] = append([]chain.Condition{condition}, commonConditions...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func formStatus(statement Statement) chain.Status {
|
|
||||||
status := chain.AccessDenied
|
|
||||||
if statement.Effect == AllowEffect {
|
|
||||||
status = chain.Allow
|
|
||||||
}
|
|
||||||
|
|
||||||
return status
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,427 +0,0 @@
|
||||||
package iam
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
|
||||||
)
|
|
||||||
|
|
||||||
const PropertyKeyFilePath = "FilePath"
|
|
||||||
|
|
||||||
var actionToNativeOpMap = map[string][]string{
|
|
||||||
s3ActionAbortMultipartUpload: {native.MethodGetContainer, native.MethodDeleteObject, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionCreateBucket: {native.MethodGetContainer, native.MethodPutContainer, native.MethodSetContainerEACL, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionDeleteBucket: {native.MethodGetContainer, native.MethodDeleteContainer, native.MethodSearchObject, native.MethodHeadObject, native.MethodGetObject},
|
|
||||||
s3ActionDeleteBucketPolicy: {native.MethodGetContainer},
|
|
||||||
s3ActionDeleteObject: {native.MethodGetContainer, native.MethodDeleteObject, native.MethodPutObject, native.MethodHeadObject, native.MethodGetObject, native.MethodRangeObject},
|
|
||||||
s3ActionDeleteObjectTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionDeleteObjectVersion: {native.MethodGetContainer, native.MethodDeleteObject, native.MethodPutObject, native.MethodHeadObject, native.MethodGetObject, native.MethodRangeObject},
|
|
||||||
s3ActionDeleteObjectVersionTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionGetBucketACL: {native.MethodGetContainer, native.MethodGetContainerEACL, native.MethodGetObject},
|
|
||||||
s3ActionGetBucketCORS: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject},
|
|
||||||
s3ActionGetBucketLocation: {native.MethodGetContainer},
|
|
||||||
s3ActionGetBucketNotification: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject},
|
|
||||||
s3ActionGetBucketObjectLockConfiguration: {native.MethodGetContainer, native.MethodGetObject},
|
|
||||||
s3ActionGetBucketPolicy: {native.MethodGetContainer},
|
|
||||||
s3ActionGetBucketPolicyStatus: {native.MethodGetContainer},
|
|
||||||
s3ActionGetBucketTagging: {native.MethodGetContainer, native.MethodGetObject},
|
|
||||||
s3ActionGetBucketVersioning: {native.MethodGetContainer, native.MethodGetObject},
|
|
||||||
s3ActionGetLifecycleConfiguration: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject},
|
|
||||||
s3ActionGetObject: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
|
|
||||||
s3ActionGetObjectACL: {native.MethodGetContainer, native.MethodGetContainerEACL, native.MethodGetObject, native.MethodHeadObject},
|
|
||||||
s3ActionGetObjectAttributes: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject},
|
|
||||||
s3ActionGetObjectLegalHold: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject},
|
|
||||||
s3ActionGetObjectRetention: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject},
|
|
||||||
s3ActionGetObjectTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject},
|
|
||||||
s3ActionGetObjectVersion: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
|
|
||||||
s3ActionGetObjectVersionACL: {native.MethodGetContainer, native.MethodGetContainerEACL, native.MethodGetObject, native.MethodHeadObject},
|
|
||||||
s3ActionGetObjectVersionAttributes: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject},
|
|
||||||
s3ActionGetObjectVersionTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject},
|
|
||||||
s3ActionListAllMyBuckets: {native.MethodListContainers, native.MethodGetContainer},
|
|
||||||
s3ActionListBucket: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
|
|
||||||
s3ActionListBucketMultipartUploads: {native.MethodGetContainer, native.MethodGetObject},
|
|
||||||
s3ActionListBucketVersions: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
|
|
||||||
s3ActionListMultipartUploadParts: {native.MethodGetContainer, native.MethodGetObject},
|
|
||||||
s3ActionPutBucketACL: {native.MethodGetContainer, native.MethodSetContainerEACL, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionPutBucketCORS: {native.MethodGetContainer, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionPutBucketNotification: {native.MethodGetContainer, native.MethodHeadObject, native.MethodDeleteObject, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionPutBucketObjectLockConfiguration: {native.MethodGetContainer, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionPutBucketPolicy: {native.MethodGetContainer},
|
|
||||||
s3ActionPutBucketTagging: {native.MethodGetContainer, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionPutBucketVersioning: {native.MethodGetContainer, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionPutLifecycleConfiguration: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodPutObject, native.MethodDeleteObject},
|
|
||||||
s3ActionPutObject: {native.MethodGetContainer, native.MethodPutObject, native.MethodGetObject, native.MethodHeadObject, native.MethodRangeObject},
|
|
||||||
s3ActionPutObjectACL: {native.MethodGetContainer, native.MethodGetContainerEACL, native.MethodSetContainerEACL, native.MethodGetObject, native.MethodHeadObject},
|
|
||||||
s3ActionPutObjectLegalHold: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionPutObjectRetention: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionPutObjectTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionPutObjectVersionACL: {native.MethodGetContainer, native.MethodGetContainerEACL, native.MethodSetContainerEACL, native.MethodGetObject, native.MethodHeadObject},
|
|
||||||
s3ActionPutObjectVersionTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject},
|
|
||||||
s3ActionPatchObject: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodPatchObject, native.MethodPutObject, native.MethodRangeObject},
|
|
||||||
}
|
|
||||||
|
|
||||||
var containerNativeOperations = map[string]struct{}{
|
|
||||||
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: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
var errConditionKeyNotApplicable = errors.New("condition key is not applicable")
|
|
||||||
|
|
||||||
type NativeResolver interface {
|
|
||||||
GetUserKey(account, name string) (string, error)
|
|
||||||
GetBucketInfo(bucket string) (*BucketInfo, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type BucketInfo struct {
|
|
||||||
Namespace string
|
|
||||||
Container string
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConvertToNativeChain(p Policy, resolver NativeResolver) (*chain.Chain, error) {
|
|
||||||
if err := p.Validate(ResourceBasedPolicyType); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var engineChain chain.Chain
|
|
||||||
|
|
||||||
for _, statement := range p.Statement {
|
|
||||||
status := formStatus(statement)
|
|
||||||
if status != chain.Allow {
|
|
||||||
// Most s3 methods share the same native operations. Deny rules must not affect shared native operations,
|
|
||||||
// therefore this code skips all deny rules for native protocol. Deny is applied for s3 protocol only, in this case.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
action, actionInverted := statement.action()
|
|
||||||
nativeActions, err := formNativeActionNames(action)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ruleAction := chain.Actions{Inverted: actionInverted, Names: nativeActions}
|
|
||||||
if len(ruleAction.Names) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
resource, resourceInverted := statement.resource()
|
|
||||||
groupedResources, err := formNativeResourceNamesAndConditions(resource, resolver, getActionTypes(nativeActions))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
groupedConditions, err := convertToNativeChainCondition(statement.Conditions, resolver)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, errConditionKeyNotApplicable) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
splitConditions := splitGroupedConditions(groupedConditions)
|
|
||||||
|
|
||||||
principals, principalCondFn, err := getNativePrincipalsAndConditionFunc(statement, resolver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, groupedResource := range groupedResources {
|
|
||||||
for _, principal := range principals {
|
|
||||||
for _, conditions := range splitConditions {
|
|
||||||
var principalCondition []chain.Condition
|
|
||||||
if principal != Wildcard {
|
|
||||||
principalCondition = []chain.Condition{principalCondFn(principal)}
|
|
||||||
}
|
|
||||||
|
|
||||||
ruleConditions := append(principalCondition, groupedResource.Conditions...)
|
|
||||||
|
|
||||||
r := chain.Rule{
|
|
||||||
Status: status,
|
|
||||||
Actions: ruleAction,
|
|
||||||
Resources: chain.Resources{
|
|
||||||
Inverted: resourceInverted,
|
|
||||||
Names: groupedResource.Names,
|
|
||||||
},
|
|
||||||
Condition: append(ruleConditions, conditions...),
|
|
||||||
}
|
|
||||||
engineChain.Rules = append(engineChain.Rules, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(engineChain.Rules) == 0 {
|
|
||||||
return nil, ErrActionsNotApplicable
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = res.Object || isObj || action == Wildcard
|
|
||||||
res.Container = res.Container || isCnr || action == Wildcard
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNativePrincipalsAndConditionFunc(statement Statement, resolver NativeResolver) ([]string, formPrincipalConditionFunc, error) {
|
|
||||||
var principals []string
|
|
||||||
var op chain.ConditionType
|
|
||||||
statementPrincipal, inverted := statement.principal()
|
|
||||||
if _, ok := statementPrincipal[Wildcard]; ok { // this can be true only if 'inverted' false
|
|
||||||
principals = []string{Wildcard}
|
|
||||||
op = chain.CondStringLike
|
|
||||||
} else {
|
|
||||||
for principalType, principal := range statementPrincipal {
|
|
||||||
if principalType != AWSPrincipalType {
|
|
||||||
return nil, nil, fmt.Errorf("unsupported principal type '%s'", principalType)
|
|
||||||
}
|
|
||||||
parsedPrincipal, err := formNativePrincipal(principal, resolver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("parse principal: %w", err)
|
|
||||||
}
|
|
||||||
principals = append(principals, parsedPrincipal...)
|
|
||||||
}
|
|
||||||
|
|
||||||
op = chain.CondStringEquals
|
|
||||||
if inverted {
|
|
||||||
op = chain.CondStringNotEquals
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return principals, func(principal string) chain.Condition {
|
|
||||||
return chain.Condition{
|
|
||||||
Op: op,
|
|
||||||
Kind: chain.KindRequest,
|
|
||||||
Key: native.PropertyKeyActorPublicKey,
|
|
||||||
Value: principal,
|
|
||||||
}
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertToNativeChainCondition(c Conditions, resolver NativeResolver) ([]GroupedConditions, error) {
|
|
||||||
return convertToChainConditions(c, func(gr GroupedConditions) (GroupedConditions, error) {
|
|
||||||
res := GroupedConditions{
|
|
||||||
Conditions: make([]chain.Condition, 0, len(gr.Conditions)),
|
|
||||||
Any: gr.Any,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range gr.Conditions {
|
|
||||||
switch {
|
|
||||||
case gr.Conditions[i].Key == condKeyAWSMFAPresent:
|
|
||||||
return GroupedConditions{}, errConditionKeyNotApplicable
|
|
||||||
case gr.Conditions[i].Key == condKeyAWSPrincipalARN:
|
|
||||||
gr.Conditions[i].Key = native.PropertyKeyActorPublicKey
|
|
||||||
val, err := formPrincipalKey(gr.Conditions[i].Value, resolver)
|
|
||||||
if err != nil {
|
|
||||||
return GroupedConditions{}, err
|
|
||||||
}
|
|
||||||
gr.Conditions[i].Value = val
|
|
||||||
res.Conditions = append(res.Conditions, gr.Conditions[i])
|
|
||||||
case strings.HasPrefix(gr.Conditions[i].Key, condKeyAWSRequestTagPrefix) ||
|
|
||||||
strings.HasPrefix(gr.Conditions[i].Key, condKeyAWSResourceTagPrefix):
|
|
||||||
// Tags exist only in S3 requests, so native protocol should not process such conditions.
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
res.Conditions = append(res.Conditions, gr.Conditions[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type GroupedResources struct {
|
|
||||||
Names []string
|
|
||||||
Conditions []chain.Condition
|
|
||||||
}
|
|
||||||
|
|
||||||
type ActionTypes struct {
|
|
||||||
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))
|
|
||||||
|
|
||||||
combined := make(map[string]struct{})
|
|
||||||
|
|
||||||
for _, resource := range names {
|
|
||||||
if err := validateResource(resource); err != nil {
|
|
||||||
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 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj == Wildcard && actionTypes.Object { // this corresponds to arn:aws:s3:::BUCKET/ or arn:aws:s3:::BUCKET/*
|
|
||||||
combined[fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, bktInfo.Namespace, bktInfo.Container)] = struct{}{}
|
|
||||||
combined[fmt.Sprintf(native.ResourceFormatNamespaceContainer, bktInfo.Namespace, bktInfo.Container)] = struct{}{}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if obj == "" && actionTypes.Container { // this corresponds to arn:aws:s3:::BUCKET
|
|
||||||
combined[fmt.Sprintf(native.ResourceFormatNamespaceContainer, bktInfo.Namespace, bktInfo.Container)] = struct{}{}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
res = append(res, GroupedResources{
|
|
||||||
Names: []string{
|
|
||||||
fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, bktInfo.Namespace, bktInfo.Container),
|
|
||||||
fmt.Sprintf(native.ResourceFormatNamespaceContainer, bktInfo.Namespace, bktInfo.Container),
|
|
||||||
},
|
|
||||||
Conditions: []chain.Condition{
|
|
||||||
{
|
|
||||||
Op: chain.CondStringLike,
|
|
||||||
Kind: chain.KindResource,
|
|
||||||
Key: PropertyKeyFilePath,
|
|
||||||
Value: obj,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(combined) != 0 {
|
|
||||||
gr := GroupedResources{Names: make([]string, 0, len(combined))}
|
|
||||||
for key := range combined {
|
|
||||||
gr.Names = append(gr.Names, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
res = append(res, gr)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
res := make([]string, len(principal))
|
|
||||||
|
|
||||||
var err error
|
|
||||||
for i := range principal {
|
|
||||||
if res[i], err = formPrincipalKey(principal[i], resolver); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func formPrincipalKey(principal string, resolver NativeResolver) (string, error) {
|
|
||||||
account, user, err := parsePrincipalAsIAMUser(principal)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := resolver.GetUserKey(account, user)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("get user key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func formNativeActionNames(names []string) ([]string, error) {
|
|
||||||
uniqueActions := make(map[string]struct{}, len(names))
|
|
||||||
|
|
||||||
for _, action := range names {
|
|
||||||
if action == Wildcard {
|
|
||||||
return []string{Wildcard}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
isIAM, err := validateAction(action)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if isIAM {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if action[len(s3ActionPrefix):] == Wildcard {
|
|
||||||
return []string{Wildcard}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
nativeActions := actionToNativeOpMap[action]
|
|
||||||
if len(nativeActions) == 0 {
|
|
||||||
return nil, ErrActionsNotApplicable
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, nativeAction := range nativeActions {
|
|
||||||
uniqueActions[nativeAction] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res := make([]string, 0, len(uniqueActions))
|
|
||||||
for key := range uniqueActions {
|
|
||||||
res = append(res, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
|
@ -1,270 +0,0 @@
|
||||||
package iam
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/schema/s3"
|
|
||||||
)
|
|
||||||
|
|
||||||
const condKeyAWSMFAPresent = "aws:MultiFactorAuthPresent"
|
|
||||||
|
|
||||||
var actionToS3OpMap = map[string][]string{
|
|
||||||
s3ActionAbortMultipartUpload: {s3ActionAbortMultipartUpload},
|
|
||||||
s3ActionCreateBucket: {s3ActionCreateBucket},
|
|
||||||
s3ActionDeleteBucket: {s3ActionDeleteBucket},
|
|
||||||
s3ActionDeleteBucketPolicy: {s3ActionDeleteBucketPolicy},
|
|
||||||
s3ActionDeleteObjectTagging: {s3ActionDeleteObjectTagging},
|
|
||||||
s3ActionGetBucketLocation: {s3ActionGetBucketLocation},
|
|
||||||
s3ActionGetBucketNotification: {s3ActionGetBucketNotification},
|
|
||||||
s3ActionGetBucketPolicy: {s3ActionGetBucketPolicy},
|
|
||||||
s3ActionGetBucketPolicyStatus: {s3ActionGetBucketPolicyStatus},
|
|
||||||
s3ActionGetBucketTagging: {s3ActionGetBucketTagging},
|
|
||||||
s3ActionGetBucketVersioning: {s3ActionGetBucketVersioning},
|
|
||||||
s3ActionGetObjectAttributes: {s3ActionGetObjectAttributes},
|
|
||||||
s3ActionGetObjectLegalHold: {s3ActionGetObjectLegalHold},
|
|
||||||
s3ActionGetObjectRetention: {s3ActionGetObjectRetention},
|
|
||||||
s3ActionGetObjectTagging: {s3ActionGetObjectTagging},
|
|
||||||
s3ActionPutBucketNotification: {s3ActionPutBucketNotification},
|
|
||||||
s3ActionPutBucketPolicy: {s3ActionPutBucketPolicy},
|
|
||||||
s3ActionPutBucketVersioning: {s3ActionPutBucketVersioning},
|
|
||||||
s3ActionPutObjectLegalHold: {s3ActionPutObjectLegalHold},
|
|
||||||
s3ActionPutObjectRetention: {s3ActionPutObjectRetention},
|
|
||||||
s3ActionPutObjectTagging: {s3ActionPutObjectTagging},
|
|
||||||
s3ActionPatchObject: {s3ActionPatchObject},
|
|
||||||
|
|
||||||
s3ActionListAllMyBuckets: {"s3:ListBuckets"},
|
|
||||||
s3ActionListBucket: {"s3:HeadBucket", "s3:GetBucketLocation", "s3:ListObjectsV1", "s3:ListObjectsV2"},
|
|
||||||
s3ActionListBucketVersions: {"s3:ListBucketObjectVersions"},
|
|
||||||
s3ActionListBucketMultipartUploads: {"s3:ListMultipartUploads"},
|
|
||||||
s3ActionGetBucketObjectLockConfiguration: {"s3:GetBucketObjectLockConfig"},
|
|
||||||
s3ActionGetLifecycleConfiguration: {"s3:GetBucketLifecycle"},
|
|
||||||
s3ActionGetBucketACL: {"s3:GetBucketACL"},
|
|
||||||
s3ActionGetBucketCORS: {"s3:GetBucketCors"},
|
|
||||||
s3ActionPutBucketTagging: {"s3:PutBucketTagging", "s3:DeleteBucketTagging"},
|
|
||||||
s3ActionPutBucketObjectLockConfiguration: {"s3:PutBucketObjectLockConfig"},
|
|
||||||
s3ActionPutLifecycleConfiguration: {"s3:PutBucketLifecycle", "s3:DeleteBucketLifecycle"},
|
|
||||||
s3ActionPutBucketACL: {"s3:PutBucketACL"},
|
|
||||||
s3ActionPutBucketCORS: {"s3:PutBucketCors", "s3:DeleteBucketCors"},
|
|
||||||
|
|
||||||
s3ActionListMultipartUploadParts: {"s3:ListParts"},
|
|
||||||
s3ActionGetObjectACL: {"s3:GetObjectACL"},
|
|
||||||
s3ActionGetObject: {"s3:GetObject", "s3:HeadObject"},
|
|
||||||
s3ActionGetObjectVersion: {"s3:GetObject", "s3:HeadObject"},
|
|
||||||
s3ActionGetObjectVersionACL: {"s3:GetObjectACL"},
|
|
||||||
s3ActionGetObjectVersionAttributes: {"s3:GetObjectAttributes"},
|
|
||||||
s3ActionGetObjectVersionTagging: {"s3:GetObjectTagging"},
|
|
||||||
s3ActionPutObjectACL: {"s3:PutObjectACL"},
|
|
||||||
s3ActionPutObjectVersionACL: {"s3:PutObjectACL"},
|
|
||||||
s3ActionPutObjectVersionTagging: {"s3:PutObjectTagging"},
|
|
||||||
s3ActionPutObject: {
|
|
||||||
"s3:PutObject", "s3:PostObject", "s3:CopyObject",
|
|
||||||
"s3:UploadPart", "s3:UploadPartCopy", "s3:CreateMultipartUpload", "s3:CompleteMultipartUpload",
|
|
||||||
},
|
|
||||||
s3ActionDeleteObjectVersionTagging: {"s3:DeleteObjectTagging"},
|
|
||||||
s3ActionDeleteObject: {"s3:DeleteObject", "s3:DeleteMultipleObjects"},
|
|
||||||
s3ActionDeleteObjectVersion: {"s3:DeleteObject", "s3:DeleteMultipleObjects"},
|
|
||||||
}
|
|
||||||
|
|
||||||
type S3Resolver interface {
|
|
||||||
GetUserAddress(account, user string) (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConvertToS3Chain(p Policy, resolver S3Resolver) (*chain.Chain, error) {
|
|
||||||
if err := p.Validate(ResourceBasedPolicyType); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var engineChain chain.Chain
|
|
||||||
|
|
||||||
for _, statement := range p.Statement {
|
|
||||||
status := formStatus(statement)
|
|
||||||
|
|
||||||
actions, actionInverted := statement.action()
|
|
||||||
s3Actions, err := formS3ActionNames(actions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ruleAction := chain.Actions{Inverted: actionInverted, Names: s3Actions}
|
|
||||||
if len(ruleAction.Names) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
resources, resourceInverted := statement.resource()
|
|
||||||
if err := validateS3ResourceNames(resources); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ruleResource := chain.Resources{Inverted: resourceInverted, Names: resources}
|
|
||||||
|
|
||||||
groupedConditions, err := convertToS3ChainCondition(statement.Conditions, resolver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
splitConditions := splitGroupedConditions(groupedConditions)
|
|
||||||
|
|
||||||
principals, principalCondFn, err := getS3PrincipalsAndConditionFunc(statement, resolver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, principal := range principals {
|
|
||||||
for _, conditions := range splitConditions {
|
|
||||||
var principalCondition []chain.Condition
|
|
||||||
if principal != Wildcard {
|
|
||||||
principalCondition = []chain.Condition{principalCondFn(principal)}
|
|
||||||
}
|
|
||||||
|
|
||||||
r := chain.Rule{
|
|
||||||
Status: status,
|
|
||||||
Actions: ruleAction,
|
|
||||||
Resources: ruleResource,
|
|
||||||
Condition: append(principalCondition, conditions...),
|
|
||||||
}
|
|
||||||
engineChain.Rules = append(engineChain.Rules, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(engineChain.Rules) == 0 {
|
|
||||||
return nil, ErrActionsNotApplicable
|
|
||||||
}
|
|
||||||
|
|
||||||
return &engineChain, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getS3PrincipalsAndConditionFunc(statement Statement, resolver S3Resolver) ([]string, formPrincipalConditionFunc, error) {
|
|
||||||
var principals []string
|
|
||||||
var op chain.ConditionType
|
|
||||||
statementPrincipal, inverted := statement.principal()
|
|
||||||
if _, ok := statementPrincipal[Wildcard]; ok { // this can be true only if 'inverted' false
|
|
||||||
principals = []string{Wildcard}
|
|
||||||
op = chain.CondStringLike
|
|
||||||
} else {
|
|
||||||
for principalType, principal := range statementPrincipal {
|
|
||||||
if principalType != AWSPrincipalType {
|
|
||||||
return nil, nil, fmt.Errorf("unsupported principal type '%s'", principalType)
|
|
||||||
}
|
|
||||||
parsedPrincipal, err := formS3Principal(principal, resolver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("parse principal: %w", err)
|
|
||||||
}
|
|
||||||
principals = append(principals, parsedPrincipal...)
|
|
||||||
}
|
|
||||||
|
|
||||||
op = chain.CondStringEquals
|
|
||||||
if inverted {
|
|
||||||
op = chain.CondStringNotEquals
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return principals, func(principal string) chain.Condition {
|
|
||||||
return chain.Condition{
|
|
||||||
Op: op,
|
|
||||||
Kind: chain.KindRequest,
|
|
||||||
Key: s3.PropertyKeyOwner,
|
|
||||||
Value: principal,
|
|
||||||
}
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertToS3ChainCondition(c Conditions, resolver S3Resolver) ([]GroupedConditions, error) {
|
|
||||||
return convertToChainConditions(c, func(gr GroupedConditions) (GroupedConditions, error) {
|
|
||||||
for i := range gr.Conditions {
|
|
||||||
switch {
|
|
||||||
case gr.Conditions[i].Key == condKeyAWSPrincipalARN:
|
|
||||||
gr.Conditions[i].Key = s3.PropertyKeyOwner
|
|
||||||
val, err := formPrincipalOwner(gr.Conditions[i].Value, resolver)
|
|
||||||
if err != nil {
|
|
||||||
return GroupedConditions{}, err
|
|
||||||
}
|
|
||||||
gr.Conditions[i].Value = val
|
|
||||||
|
|
||||||
case gr.Conditions[i].Key == condKeyAWSMFAPresent:
|
|
||||||
gr.Conditions[i].Key = s3.PropertyKeyAccessBoxAttrMFA
|
|
||||||
case strings.HasPrefix(gr.Conditions[i].Key, condKeyAWSResourceTagPrefix):
|
|
||||||
gr.Conditions[i].Kind = chain.KindResource
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return gr, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func formS3Principal(principal []string, resolver S3Resolver) ([]string, error) {
|
|
||||||
res := make([]string, len(principal))
|
|
||||||
|
|
||||||
var err error
|
|
||||||
for i := range principal {
|
|
||||||
if res[i], err = formPrincipalOwner(principal[i], resolver); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func formPrincipalOwner(principal string, resolver S3Resolver) (string, error) {
|
|
||||||
account, user, err := parsePrincipalAsIAMUser(principal)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
address, err := resolver.GetUserAddress(account, user)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("get user address: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return address, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateS3ResourceNames(names []string) error {
|
|
||||||
for i := range names {
|
|
||||||
if err := validateResource(names[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func formS3ActionNames(names []string) ([]string, error) {
|
|
||||||
uniqueActions := make(map[string]struct{}, len(names))
|
|
||||||
|
|
||||||
for _, action := range names {
|
|
||||||
if action == Wildcard {
|
|
||||||
return []string{Wildcard}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
isIAM, err := validateAction(action)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if isIAM {
|
|
||||||
uniqueActions[action] = struct{}{}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if action[len(s3ActionPrefix):] == Wildcard {
|
|
||||||
uniqueActions[action] = struct{}{}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
s3Actions := actionToS3OpMap[action]
|
|
||||||
if len(s3Actions) == 0 {
|
|
||||||
return nil, ErrActionsNotApplicable
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s3Action := range s3Actions {
|
|
||||||
uniqueActions[s3Action] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res := make([]string, 0, len(uniqueActions))
|
|
||||||
for key := range uniqueActions {
|
|
||||||
res = append(res, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -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,7 @@ 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 {
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"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/s3"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -217,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:*"},
|
||||||
|
@ -230,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:*"},
|
||||||
|
@ -243,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:*"},
|
||||||
|
@ -258,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:*"},
|
||||||
|
@ -272,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:*"},
|
||||||
|
@ -286,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:*"},
|
||||||
|
@ -300,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},
|
||||||
|
@ -313,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{},
|
||||||
|
@ -326,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,
|
||||||
}},
|
}},
|
||||||
|
@ -334,49 +320,9 @@ func TestValidatePolicies(t *testing.T) {
|
||||||
typ: GeneralPolicyType,
|
typ: GeneralPolicyType,
|
||||||
isValid: false,
|
isValid: false,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "missing statement block",
|
|
||||||
policy: Policy{},
|
|
||||||
typ: GeneralPolicyType,
|
|
||||||
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"},
|
||||||
|
@ -390,7 +336,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"},
|
||||||
|
@ -403,7 +348,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"},
|
||||||
|
@ -417,7 +361,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"},
|
||||||
|
@ -431,7 +374,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"},
|
||||||
|
@ -446,7 +388,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"},
|
||||||
|
@ -461,7 +402,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"},
|
||||||
|
@ -482,70 +422,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 := ConvertToS3Chain(identityPolicy, mockResolver)
|
|
||||||
require.NoError(t, err)
|
|
||||||
identityNativePolicy.MatchType = chain.MatchTypeFirstMatch
|
|
||||||
|
|
||||||
resourceNativePolicy, err := ConvertToS3Chain(resourcePolicy, mockResolver)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
s := inmemory.NewInMemory()
|
|
||||||
|
|
||||||
target := engine.NamespaceTarget("ns")
|
|
||||||
|
|
||||||
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chain.S3, target, identityNativePolicy)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chain.S3, target, resourceNativePolicy)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
resource := testutil.NewResource("arn:aws:s3:::test-bucket/object", nil)
|
|
||||||
request := testutil.NewRequest("s3:PutObject", resource, map[string]string{s3.PropertyKeyOwner: mockResolver.users["root/user-name"]})
|
|
||||||
|
|
||||||
status, found, err := s.IsAllowed(chain.S3, engine.NewRequestTarget("ns", ""), request)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, found)
|
|
||||||
require.Equal(t, chain.AccessDenied, status)
|
|
||||||
}
|
|
||||||
|
|
106
inmemory.go
Normal file
106
inmemory.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package policyengine
|
||||||
|
|
||||||
|
type inmemory struct {
|
||||||
|
namespace map[Name][]chain
|
||||||
|
resource map[Name][]chain
|
||||||
|
local map[Name][]*Chain
|
||||||
|
}
|
||||||
|
|
||||||
|
type chain struct {
|
||||||
|
object string
|
||||||
|
chain *Chain
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInMemory returns new inmemory instance of chain storage.
|
||||||
|
func NewInMemory() CachedChainStorage {
|
||||||
|
return &inmemory{
|
||||||
|
namespace: make(map[Name][]chain),
|
||||||
|
resource: make(map[Name][]chain),
|
||||||
|
local: make(map[Name][]*Chain),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAllowed implements the Engine interface.
|
||||||
|
func (s *inmemory) IsAllowed(name Name, namespace string, r Request) (Status, bool) {
|
||||||
|
var ruleFound bool
|
||||||
|
if local, ok := s.local[name]; ok {
|
||||||
|
for _, c := range local {
|
||||||
|
if status, matched := c.Match(r); matched && status != Allow {
|
||||||
|
return status, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cs, ok := s.namespace[name]; ok {
|
||||||
|
status, ok := matchArray(cs, namespace, r)
|
||||||
|
if ok && status != Allow {
|
||||||
|
return status, true
|
||||||
|
}
|
||||||
|
ruleFound = ruleFound || ok
|
||||||
|
}
|
||||||
|
if cs, ok := s.resource[name]; ok {
|
||||||
|
status, ok := matchArray(cs, r.Resource().Name(), r)
|
||||||
|
if ok {
|
||||||
|
return status, true
|
||||||
|
}
|
||||||
|
ruleFound = ruleFound || ok
|
||||||
|
}
|
||||||
|
if ruleFound {
|
||||||
|
return Allow, true
|
||||||
|
}
|
||||||
|
return NoRuleFound, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchArray(cs []chain, object string, r Request) (Status, bool) {
|
||||||
|
for _, c := range cs {
|
||||||
|
if !globMatch(object, c.object) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if status, matched := c.chain.Match(r); matched {
|
||||||
|
return status, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NoRuleFound, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inmemory) AddResourceChain(name Name, resource string, c *Chain) {
|
||||||
|
s.resource[name] = append(s.resource[name], chain{resource, c})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inmemory) AddNameSpaceChain(name Name, namespace string, c *Chain) {
|
||||||
|
s.namespace[name] = append(s.namespace[name], chain{namespace, c})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inmemory) AddOverride(name Name, c *Chain) {
|
||||||
|
s.local[name] = append(s.local[name], c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inmemory) GetOverride(name Name, chainID ChainID) (chain *Chain, found bool) {
|
||||||
|
chains := s.local[name]
|
||||||
|
|
||||||
|
for _, chain = range chains {
|
||||||
|
if chain.ID == chainID {
|
||||||
|
found = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inmemory) RemoveOverride(name Name, chainID ChainID) (found bool) {
|
||||||
|
chains := s.local[name]
|
||||||
|
|
||||||
|
for i, chain := range chains {
|
||||||
|
if chain.ID == chainID {
|
||||||
|
s.local[name] = append(chains[:i], chains[i+1:]...)
|
||||||
|
found = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inmemory) ListOverrides(name Name) []*Chain {
|
||||||
|
return s.local[name]
|
||||||
|
}
|
193
inmemory_test.go
Normal file
193
inmemory_test.go
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
package policyengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInmemory(t *testing.T) {
|
||||||
|
const (
|
||||||
|
object = "native::object::abc/xyz"
|
||||||
|
container = "native::object::abc/*"
|
||||||
|
namespace = "Tenant1"
|
||||||
|
namespace2 = "Tenant2"
|
||||||
|
actor1 = "owner1"
|
||||||
|
actor2 = "owner2"
|
||||||
|
)
|
||||||
|
|
||||||
|
s := NewInMemory()
|
||||||
|
|
||||||
|
// Object which was put via S3.
|
||||||
|
res := newResource(object, map[string]string{"FromS3": "true"})
|
||||||
|
// Request initiating from the trusted subnet and actor.
|
||||||
|
reqGood := newRequest("native::object::put", res, map[string]string{
|
||||||
|
"SourceIP": "10.1.1.12",
|
||||||
|
"Actor": actor1,
|
||||||
|
})
|
||||||
|
|
||||||
|
status, ok := s.IsAllowed(Ingress, namespace, reqGood)
|
||||||
|
require.Equal(t, NoRuleFound, status)
|
||||||
|
require.False(t, ok)
|
||||||
|
|
||||||
|
s.AddNameSpaceChain(Ingress, namespace, &Chain{
|
||||||
|
Rules: []Rule{
|
||||||
|
{ // Restrict to remove ANY object from the namespace.
|
||||||
|
Status: AccessDenied,
|
||||||
|
Actions: Actions{Names: []string{"native::object::delete"}},
|
||||||
|
Resources: Resources{Names: []string{"native::object::*"}},
|
||||||
|
},
|
||||||
|
{ // Allow to put object only from the trusted subnet AND trusted actor, deny otherwise.
|
||||||
|
Status: AccessDenied,
|
||||||
|
Actions: Actions{Names: []string{"native::object::put"}},
|
||||||
|
Resources: Resources{Names: []string{"native::object::*"}},
|
||||||
|
Any: true,
|
||||||
|
Condition: []Condition{
|
||||||
|
{
|
||||||
|
Op: CondStringNotLike,
|
||||||
|
Object: ObjectRequest,
|
||||||
|
Key: "SourceIP",
|
||||||
|
Value: "10.1.1.*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Op: CondStringNotEquals,
|
||||||
|
Object: ObjectRequest,
|
||||||
|
Key: "Actor",
|
||||||
|
Value: actor1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
s.AddNameSpaceChain(Ingress, namespace2, &Chain{
|
||||||
|
Rules: []Rule{
|
||||||
|
{ // Deny all expect "native::object::get" for all objects expect "native::object::abc/xyz".
|
||||||
|
Status: AccessDenied,
|
||||||
|
Actions: Actions{Inverted: true, Names: []string{"native::object::get"}},
|
||||||
|
Resources: Resources{Inverted: true, Names: []string{object}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
s.AddResourceChain(Ingress, container, &Chain{
|
||||||
|
Rules: []Rule{
|
||||||
|
{ // Allow to actor2 to get objects from the specific container only if they have `Department=HR` attribute.
|
||||||
|
Status: Allow,
|
||||||
|
Actions: Actions{Names: []string{"native::object::get"}},
|
||||||
|
Resources: Resources{Names: []string{"native::object::abc/*"}},
|
||||||
|
Condition: []Condition{
|
||||||
|
{
|
||||||
|
Op: CondStringEquals,
|
||||||
|
Object: ObjectResource,
|
||||||
|
Key: "Department",
|
||||||
|
Value: "HR",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Op: CondStringEquals,
|
||||||
|
Object: ObjectRequest,
|
||||||
|
Key: "Actor",
|
||||||
|
Value: actor2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad subnet, namespace deny", func(t *testing.T) {
|
||||||
|
// Request initiating from the untrusted subnet.
|
||||||
|
reqBadIP := newRequest("native::object::put", res, map[string]string{
|
||||||
|
"SourceIP": "10.122.1.20",
|
||||||
|
"Actor": actor1,
|
||||||
|
})
|
||||||
|
status, ok := s.IsAllowed(Ingress, namespace, reqBadIP)
|
||||||
|
require.Equal(t, AccessDenied, status)
|
||||||
|
require.True(t, ok)
|
||||||
|
})
|
||||||
|
t.Run("bad actor, namespace deny", func(t *testing.T) {
|
||||||
|
// Request initiating from the untrusted actor.
|
||||||
|
reqBadActor := newRequest("native::object::put", res, map[string]string{
|
||||||
|
"SourceIP": "10.1.1.13",
|
||||||
|
"Actor": actor2,
|
||||||
|
})
|
||||||
|
status, ok := s.IsAllowed(Ingress, namespace, reqBadActor)
|
||||||
|
require.Equal(t, AccessDenied, status)
|
||||||
|
require.True(t, ok)
|
||||||
|
})
|
||||||
|
t.Run("bad object, container deny", func(t *testing.T) {
|
||||||
|
objGood := newResource("native::object::abc/id1", map[string]string{"Department": "HR"})
|
||||||
|
objBadAttr := newResource("native::object::abc/id2", map[string]string{"Department": "Support"})
|
||||||
|
|
||||||
|
status, ok := s.IsAllowed(Ingress, namespace, newRequest("native::object::get", objGood, map[string]string{
|
||||||
|
"SourceIP": "10.1.1.14",
|
||||||
|
"Actor": actor2,
|
||||||
|
}))
|
||||||
|
require.Equal(t, Allow, status)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
status, ok = s.IsAllowed(Ingress, namespace, newRequest("native::object::get", objBadAttr, map[string]string{
|
||||||
|
"SourceIP": "10.1.1.14",
|
||||||
|
"Actor": actor2,
|
||||||
|
}))
|
||||||
|
require.Equal(t, NoRuleFound, status)
|
||||||
|
require.False(t, ok)
|
||||||
|
})
|
||||||
|
t.Run("bad operation, namespace deny", func(t *testing.T) {
|
||||||
|
// Request with the forbidden operation.
|
||||||
|
reqBadOperation := newRequest("native::object::delete", res, map[string]string{
|
||||||
|
"SourceIP": "10.1.1.12",
|
||||||
|
"Actor": actor1,
|
||||||
|
})
|
||||||
|
status, ok := s.IsAllowed(Ingress, namespace, reqBadOperation)
|
||||||
|
require.Equal(t, AccessDenied, status)
|
||||||
|
require.True(t, ok)
|
||||||
|
})
|
||||||
|
t.Run("inverted rules", func(t *testing.T) {
|
||||||
|
req := newRequest("native::object::put", newResource(object, nil), nil)
|
||||||
|
status, ok = s.IsAllowed(Ingress, namespace2, req)
|
||||||
|
require.Equal(t, NoRuleFound, status)
|
||||||
|
require.False(t, ok)
|
||||||
|
|
||||||
|
req = newRequest("native::object::put", newResource("native::object::cba/def", nil), nil)
|
||||||
|
status, ok = s.IsAllowed(Ingress, namespace2, req)
|
||||||
|
require.Equal(t, AccessDenied, status)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
req = newRequest("native::object::get", newResource("native::object::cba/def", nil), nil)
|
||||||
|
status, ok = s.IsAllowed(Ingress, namespace2, req)
|
||||||
|
require.Equal(t, NoRuleFound, status)
|
||||||
|
require.False(t, ok)
|
||||||
|
})
|
||||||
|
t.Run("good", func(t *testing.T) {
|
||||||
|
status, ok = s.IsAllowed(Ingress, namespace, reqGood)
|
||||||
|
require.Equal(t, NoRuleFound, status)
|
||||||
|
require.False(t, ok)
|
||||||
|
|
||||||
|
t.Run("quota on a different container", func(t *testing.T) {
|
||||||
|
s.AddOverride(Ingress, &Chain{
|
||||||
|
Rules: []Rule{{
|
||||||
|
Status: QuotaLimitReached,
|
||||||
|
Actions: Actions{Names: []string{"native::object::put"}},
|
||||||
|
Resources: Resources{Names: []string{"native::object::cba/*"}},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
status, ok = s.IsAllowed(Ingress, namespace, reqGood)
|
||||||
|
require.Equal(t, NoRuleFound, status)
|
||||||
|
require.False(t, ok)
|
||||||
|
})
|
||||||
|
t.Run("quota on the request container", func(t *testing.T) {
|
||||||
|
s.AddOverride(Ingress, &Chain{
|
||||||
|
Rules: []Rule{{
|
||||||
|
Status: QuotaLimitReached,
|
||||||
|
Actions: Actions{Names: []string{"native::object::put"}},
|
||||||
|
Resources: Resources{Names: []string{"native::object::abc/*"}},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
status, ok = s.IsAllowed(Ingress, namespace, reqGood)
|
||||||
|
require.Equal(t, QuotaLimitReached, status)
|
||||||
|
require.True(t, ok)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
18
interface.go
Normal file
18
interface.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package policyengine
|
||||||
|
|
||||||
|
// CachedChainStorage ...
|
||||||
|
type CachedChainStorage interface {
|
||||||
|
Engine
|
||||||
|
// Adds a policy chain used for all operations with a specific resource.
|
||||||
|
AddResourceChain(name Name, resource string, c *Chain)
|
||||||
|
// Adds a policy chain used for all operations in the namespace.
|
||||||
|
AddNameSpaceChain(name Name, namespace string, c *Chain)
|
||||||
|
// Adds a local policy chain used for all operations with this service.
|
||||||
|
AddOverride(name Name, c *Chain)
|
||||||
|
// Gets the local override with given chain id.
|
||||||
|
GetOverride(name Name, chainID ChainID) (chain *Chain, found bool)
|
||||||
|
// Remove the local override with given chain id.
|
||||||
|
RemoveOverride(name Name, chainID ChainID) (removed bool)
|
||||||
|
// ListOverrides returns the list of local overrides.
|
||||||
|
ListOverrides(name Name) []*Chain
|
||||||
|
}
|
|
@ -1,333 +0,0 @@
|
||||||
package chain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/util"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ID is the ID of rule chain.
|
|
||||||
type ID []byte
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
ID ID
|
|
||||||
|
|
||||||
Rules []Rule
|
|
||||||
|
|
||||||
MatchType MatchType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chain) Bytes() []byte {
|
|
||||||
data, err := c.MarshalBinary()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chain) DecodeBytes(b []byte) error {
|
|
||||||
return c.UnmarshalBinary(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rule struct {
|
|
||||||
Status Status
|
|
||||||
// Actions the operation is applied to.
|
|
||||||
Actions Actions
|
|
||||||
// List of the resources the operation is applied to.
|
|
||||||
Resources Resources
|
|
||||||
// True iff individual conditions must be combined with the logical OR.
|
|
||||||
// By default AND is used, so _each_ condition must pass.
|
|
||||||
Any bool
|
|
||||||
Condition []Condition
|
|
||||||
}
|
|
||||||
|
|
||||||
type Actions struct {
|
|
||||||
Inverted bool
|
|
||||||
Names []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Resources struct {
|
|
||||||
Inverted bool
|
|
||||||
Names []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Condition struct {
|
|
||||||
Op ConditionType
|
|
||||||
Kind ConditionKindType
|
|
||||||
Key string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConditionKindType byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
KindResource ConditionKindType = iota
|
|
||||||
KindRequest
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConditionType byte
|
|
||||||
|
|
||||||
// TODO @fyrchik: reduce the number of conditions.
|
|
||||||
// Everything from here should be expressable, but we do not need them all.
|
|
||||||
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html
|
|
||||||
const (
|
|
||||||
// String condition operators.
|
|
||||||
CondStringEquals ConditionType = iota
|
|
||||||
CondStringNotEquals
|
|
||||||
CondStringEqualsIgnoreCase
|
|
||||||
CondStringNotEqualsIgnoreCase
|
|
||||||
CondStringLike
|
|
||||||
CondStringNotLike
|
|
||||||
CondStringLessThan
|
|
||||||
CondStringLessThanEquals
|
|
||||||
CondStringGreaterThan
|
|
||||||
CondStringGreaterThanEquals
|
|
||||||
|
|
||||||
// Numeric condition operators.
|
|
||||||
CondNumericEquals
|
|
||||||
CondNumericNotEquals
|
|
||||||
CondNumericLessThan
|
|
||||||
CondNumericLessThanEquals
|
|
||||||
CondNumericGreaterThan
|
|
||||||
CondNumericGreaterThanEquals
|
|
||||||
|
|
||||||
CondSliceContains
|
|
||||||
|
|
||||||
CondIPAddress
|
|
||||||
CondNotIPAddress
|
|
||||||
)
|
|
||||||
|
|
||||||
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"},
|
|
||||||
{CondIPAddress, "IPAddress"},
|
|
||||||
{CondNotIPAddress, "NotIPAddress"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c ConditionType) String() string {
|
|
||||||
for _, v := range condToStr {
|
|
||||||
if v.ct == c {
|
|
||||||
return v.str
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
var val string
|
|
||||||
switch c.Kind {
|
|
||||||
case KindResource:
|
|
||||||
val = req.Resource().Property(c.Key)
|
|
||||||
case KindRequest:
|
|
||||||
val = req.Property(c.Key)
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unknown condition type: %d", c.Kind))
|
|
||||||
}
|
|
||||||
|
|
||||||
switch c.Op {
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unimplemented: %d", c.Op))
|
|
||||||
case CondStringEquals:
|
|
||||||
return val == c.Value
|
|
||||||
case CondStringNotEquals:
|
|
||||||
return val != c.Value
|
|
||||||
case CondStringEqualsIgnoreCase:
|
|
||||||
return strings.EqualFold(val, c.Value)
|
|
||||||
case CondStringNotEqualsIgnoreCase:
|
|
||||||
return !strings.EqualFold(val, c.Value)
|
|
||||||
case CondStringLike:
|
|
||||||
return util.GlobMatch(val, c.Value)
|
|
||||||
case CondStringNotLike:
|
|
||||||
return !util.GlobMatch(val, c.Value)
|
|
||||||
case CondStringLessThan:
|
|
||||||
return val < c.Value
|
|
||||||
case CondStringLessThanEquals:
|
|
||||||
return val <= c.Value
|
|
||||||
case CondStringGreaterThan:
|
|
||||||
return val > c.Value
|
|
||||||
case CondStringGreaterThanEquals:
|
|
||||||
return val >= c.Value
|
|
||||||
case CondSliceContains:
|
|
||||||
return slices.Contains(strings.Split(val, condSliceContainsDelimiter), c.Value)
|
|
||||||
case CondNumericEquals, CondNumericNotEquals, CondNumericLessThan, CondNumericLessThanEquals, CondNumericGreaterThan,
|
|
||||||
CondNumericGreaterThanEquals:
|
|
||||||
return c.matchNumeric(val)
|
|
||||||
case CondIPAddress, CondNotIPAddress:
|
|
||||||
return c.matchIP(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Condition) matchNumeric(val string) bool {
|
|
||||||
valDecimal, err := fixedn.Fixed8FromString(val)
|
|
||||||
if err != nil {
|
|
||||||
return c.Op == CondNumericNotEquals
|
|
||||||
}
|
|
||||||
|
|
||||||
condVal, err := fixedn.Fixed8FromString(c.Value)
|
|
||||||
if err != nil {
|
|
||||||
return c.Op == CondNumericNotEquals
|
|
||||||
}
|
|
||||||
|
|
||||||
switch c.Op {
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unimplemented: %d", c.Op))
|
|
||||||
case CondNumericEquals:
|
|
||||||
return valDecimal.Equal(condVal)
|
|
||||||
case CondNumericNotEquals:
|
|
||||||
return !valDecimal.Equal(condVal)
|
|
||||||
case CondNumericLessThan:
|
|
||||||
return valDecimal.LessThan(condVal)
|
|
||||||
case CondNumericLessThanEquals:
|
|
||||||
return valDecimal.LessThan(condVal) || valDecimal.Equal(condVal)
|
|
||||||
case CondNumericGreaterThan:
|
|
||||||
return valDecimal.GreaterThan(condVal)
|
|
||||||
case CondNumericGreaterThanEquals:
|
|
||||||
return valDecimal.GreaterThan(condVal) || valDecimal.Equal(condVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Condition) matchIP(val string) bool {
|
|
||||||
ipAddr, err := netip.ParseAddr(val)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix, err := netip.ParsePrefix(c.Value)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch c.Op {
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unimplemented: %d", c.Op))
|
|
||||||
case CondIPAddress:
|
|
||||||
return prefix.Contains(ipAddr)
|
|
||||||
case CondNotIPAddress:
|
|
||||||
return !prefix.Contains(ipAddr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Rule) Match(req resource.Request) (status Status, matched bool) {
|
|
||||||
found := len(r.Resources.Names) == 0
|
|
||||||
for i := range r.Resources.Names {
|
|
||||||
if util.GlobMatch(req.Resource().Name(), r.Resources.Names[i]) != r.Resources.Inverted {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return NoRuleFound, false
|
|
||||||
}
|
|
||||||
for i := range r.Actions.Names {
|
|
||||||
if util.GlobMatch(req.Operation(), r.Actions.Names[i]) != r.Actions.Inverted {
|
|
||||||
return r.matchCondition(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NoRuleFound, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Rule) matchCondition(obj resource.Request) (status Status, matched bool) {
|
|
||||||
if r.Any {
|
|
||||||
return r.matchAny(obj)
|
|
||||||
}
|
|
||||||
return r.matchAll(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Rule) matchAny(obj resource.Request) (status Status, matched bool) {
|
|
||||||
for i := range r.Condition {
|
|
||||||
if r.Condition[i].Match(obj) {
|
|
||||||
return r.Status, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NoRuleFound, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Rule) matchAll(obj resource.Request) (status Status, matched bool) {
|
|
||||||
for i := range r.Condition {
|
|
||||||
if !r.Condition[i].Match(obj) {
|
|
||||||
return NoRuleFound, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.Status, true
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
status, matched := c.Rules[i].Match(req)
|
|
||||||
if matched {
|
|
||||||
return status, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,463 +0,0 @@
|
||||||
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
|
|
||||||
|
|
||||||
package chain
|
|
||||||
|
|
||||||
import (
|
|
||||||
json "encoding/json"
|
|
||||||
easyjson "github.com/mailru/easyjson"
|
|
||||||
jlexer "github.com/mailru/easyjson/jlexer"
|
|
||||||
jwriter "github.com/mailru/easyjson/jwriter"
|
|
||||||
)
|
|
||||||
|
|
||||||
// suppress unused package warning
|
|
||||||
var (
|
|
||||||
_ *json.RawMessage
|
|
||||||
_ *jlexer.Lexer
|
|
||||||
_ *jwriter.Writer
|
|
||||||
_ easyjson.Marshaler
|
|
||||||
)
|
|
||||||
|
|
||||||
func easyjsonE2758465DecodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain(in *jlexer.Lexer, out *Chain) {
|
|
||||||
isTopLevel := in.IsStart()
|
|
||||||
if in.IsNull() {
|
|
||||||
if isTopLevel {
|
|
||||||
in.Consumed()
|
|
||||||
}
|
|
||||||
in.Skip()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
in.Delim('{')
|
|
||||||
for !in.IsDelim('}') {
|
|
||||||
key := in.UnsafeFieldName(false)
|
|
||||||
in.WantColon()
|
|
||||||
if in.IsNull() {
|
|
||||||
in.Skip()
|
|
||||||
in.WantComma()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "ID":
|
|
||||||
if in.IsNull() {
|
|
||||||
in.Skip()
|
|
||||||
out.ID = nil
|
|
||||||
} else {
|
|
||||||
out.ID = in.Bytes()
|
|
||||||
}
|
|
||||||
case "Rules":
|
|
||||||
if in.IsNull() {
|
|
||||||
in.Skip()
|
|
||||||
out.Rules = nil
|
|
||||||
} else {
|
|
||||||
in.Delim('[')
|
|
||||||
if out.Rules == nil {
|
|
||||||
if !in.IsDelim(']') {
|
|
||||||
out.Rules = make([]Rule, 0, 0)
|
|
||||||
} else {
|
|
||||||
out.Rules = []Rule{}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out.Rules = (out.Rules)[:0]
|
|
||||||
}
|
|
||||||
for !in.IsDelim(']') {
|
|
||||||
var v2 Rule
|
|
||||||
easyjsonE2758465DecodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain1(in, &v2)
|
|
||||||
out.Rules = append(out.Rules, v2)
|
|
||||||
in.WantComma()
|
|
||||||
}
|
|
||||||
in.Delim(']')
|
|
||||||
}
|
|
||||||
case "MatchType":
|
|
||||||
(out.MatchType).UnmarshalEasyJSON(in)
|
|
||||||
default:
|
|
||||||
in.SkipRecursive()
|
|
||||||
}
|
|
||||||
in.WantComma()
|
|
||||||
}
|
|
||||||
in.Delim('}')
|
|
||||||
if isTopLevel {
|
|
||||||
in.Consumed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func easyjsonE2758465EncodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain(out *jwriter.Writer, in Chain) {
|
|
||||||
out.RawByte('{')
|
|
||||||
first := true
|
|
||||||
_ = first
|
|
||||||
{
|
|
||||||
const prefix string = ",\"ID\":"
|
|
||||||
out.RawString(prefix[1:])
|
|
||||||
out.Base64Bytes(in.ID)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Rules\":"
|
|
||||||
out.RawString(prefix)
|
|
||||||
if in.Rules == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
|
|
||||||
out.RawString("null")
|
|
||||||
} else {
|
|
||||||
out.RawByte('[')
|
|
||||||
for v5, v6 := range in.Rules {
|
|
||||||
if v5 > 0 {
|
|
||||||
out.RawByte(',')
|
|
||||||
}
|
|
||||||
easyjsonE2758465EncodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain1(out, v6)
|
|
||||||
}
|
|
||||||
out.RawByte(']')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const prefix string = ",\"MatchType\":"
|
|
||||||
out.RawString(prefix)
|
|
||||||
(in.MatchType).MarshalEasyJSON(out)
|
|
||||||
}
|
|
||||||
out.RawByte('}')
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON supports json.Marshaler interface
|
|
||||||
func (v Chain) MarshalJSON() ([]byte, error) {
|
|
||||||
w := jwriter.Writer{}
|
|
||||||
easyjsonE2758465EncodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain(&w, v)
|
|
||||||
return w.Buffer.BuildBytes(), w.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalEasyJSON supports easyjson.Marshaler interface
|
|
||||||
func (v Chain) MarshalEasyJSON(w *jwriter.Writer) {
|
|
||||||
easyjsonE2758465EncodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain(w, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON supports json.Unmarshaler interface
|
|
||||||
func (v *Chain) UnmarshalJSON(data []byte) error {
|
|
||||||
r := jlexer.Lexer{Data: data}
|
|
||||||
easyjsonE2758465DecodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain(&r, v)
|
|
||||||
return r.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
|
|
||||||
func (v *Chain) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
|
||||||
easyjsonE2758465DecodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain(l, v)
|
|
||||||
}
|
|
||||||
func easyjsonE2758465DecodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain1(in *jlexer.Lexer, out *Rule) {
|
|
||||||
isTopLevel := in.IsStart()
|
|
||||||
if in.IsNull() {
|
|
||||||
if isTopLevel {
|
|
||||||
in.Consumed()
|
|
||||||
}
|
|
||||||
in.Skip()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
in.Delim('{')
|
|
||||||
for !in.IsDelim('}') {
|
|
||||||
key := in.UnsafeFieldName(false)
|
|
||||||
in.WantColon()
|
|
||||||
if in.IsNull() {
|
|
||||||
in.Skip()
|
|
||||||
in.WantComma()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "Status":
|
|
||||||
(out.Status).UnmarshalEasyJSON(in)
|
|
||||||
case "Actions":
|
|
||||||
easyjsonE2758465DecodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain2(in, &out.Actions)
|
|
||||||
case "Resources":
|
|
||||||
easyjsonE2758465DecodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain3(in, &out.Resources)
|
|
||||||
case "Any":
|
|
||||||
out.Any = bool(in.Bool())
|
|
||||||
case "Condition":
|
|
||||||
if in.IsNull() {
|
|
||||||
in.Skip()
|
|
||||||
out.Condition = nil
|
|
||||||
} else {
|
|
||||||
in.Delim('[')
|
|
||||||
if out.Condition == nil {
|
|
||||||
if !in.IsDelim(']') {
|
|
||||||
out.Condition = make([]Condition, 0, 1)
|
|
||||||
} else {
|
|
||||||
out.Condition = []Condition{}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out.Condition = (out.Condition)[:0]
|
|
||||||
}
|
|
||||||
for !in.IsDelim(']') {
|
|
||||||
var v7 Condition
|
|
||||||
easyjsonE2758465DecodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain4(in, &v7)
|
|
||||||
out.Condition = append(out.Condition, v7)
|
|
||||||
in.WantComma()
|
|
||||||
}
|
|
||||||
in.Delim(']')
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
in.SkipRecursive()
|
|
||||||
}
|
|
||||||
in.WantComma()
|
|
||||||
}
|
|
||||||
in.Delim('}')
|
|
||||||
if isTopLevel {
|
|
||||||
in.Consumed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func easyjsonE2758465EncodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain1(out *jwriter.Writer, in Rule) {
|
|
||||||
out.RawByte('{')
|
|
||||||
first := true
|
|
||||||
_ = first
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Status\":"
|
|
||||||
out.RawString(prefix[1:])
|
|
||||||
(in.Status).MarshalEasyJSON(out)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Actions\":"
|
|
||||||
out.RawString(prefix)
|
|
||||||
easyjsonE2758465EncodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain2(out, in.Actions)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Resources\":"
|
|
||||||
out.RawString(prefix)
|
|
||||||
easyjsonE2758465EncodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain3(out, in.Resources)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Any\":"
|
|
||||||
out.RawString(prefix)
|
|
||||||
out.Bool(bool(in.Any))
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Condition\":"
|
|
||||||
out.RawString(prefix)
|
|
||||||
if in.Condition == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
|
|
||||||
out.RawString("null")
|
|
||||||
} else {
|
|
||||||
out.RawByte('[')
|
|
||||||
for v8, v9 := range in.Condition {
|
|
||||||
if v8 > 0 {
|
|
||||||
out.RawByte(',')
|
|
||||||
}
|
|
||||||
easyjsonE2758465EncodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain4(out, v9)
|
|
||||||
}
|
|
||||||
out.RawByte(']')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.RawByte('}')
|
|
||||||
}
|
|
||||||
func easyjsonE2758465DecodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain4(in *jlexer.Lexer, out *Condition) {
|
|
||||||
isTopLevel := in.IsStart()
|
|
||||||
if in.IsNull() {
|
|
||||||
if isTopLevel {
|
|
||||||
in.Consumed()
|
|
||||||
}
|
|
||||||
in.Skip()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
in.Delim('{')
|
|
||||||
for !in.IsDelim('}') {
|
|
||||||
key := in.UnsafeFieldName(false)
|
|
||||||
in.WantColon()
|
|
||||||
if in.IsNull() {
|
|
||||||
in.Skip()
|
|
||||||
in.WantComma()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "Op":
|
|
||||||
(out.Op).UnmarshalEasyJSON(in)
|
|
||||||
case "Kind":
|
|
||||||
(out.Kind).UnmarshalEasyJSON(in)
|
|
||||||
case "Key":
|
|
||||||
out.Key = string(in.String())
|
|
||||||
case "Value":
|
|
||||||
out.Value = string(in.String())
|
|
||||||
default:
|
|
||||||
in.SkipRecursive()
|
|
||||||
}
|
|
||||||
in.WantComma()
|
|
||||||
}
|
|
||||||
in.Delim('}')
|
|
||||||
if isTopLevel {
|
|
||||||
in.Consumed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func easyjsonE2758465EncodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain4(out *jwriter.Writer, in Condition) {
|
|
||||||
out.RawByte('{')
|
|
||||||
first := true
|
|
||||||
_ = first
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Op\":"
|
|
||||||
out.RawString(prefix[1:])
|
|
||||||
(in.Op).MarshalEasyJSON(out)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Kind\":"
|
|
||||||
out.RawString(prefix)
|
|
||||||
(in.Kind).MarshalEasyJSON(out)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Key\":"
|
|
||||||
out.RawString(prefix)
|
|
||||||
out.String(string(in.Key))
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Value\":"
|
|
||||||
out.RawString(prefix)
|
|
||||||
out.String(string(in.Value))
|
|
||||||
}
|
|
||||||
out.RawByte('}')
|
|
||||||
}
|
|
||||||
func easyjsonE2758465DecodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain3(in *jlexer.Lexer, out *Resources) {
|
|
||||||
isTopLevel := in.IsStart()
|
|
||||||
if in.IsNull() {
|
|
||||||
if isTopLevel {
|
|
||||||
in.Consumed()
|
|
||||||
}
|
|
||||||
in.Skip()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
in.Delim('{')
|
|
||||||
for !in.IsDelim('}') {
|
|
||||||
key := in.UnsafeFieldName(false)
|
|
||||||
in.WantColon()
|
|
||||||
if in.IsNull() {
|
|
||||||
in.Skip()
|
|
||||||
in.WantComma()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "Inverted":
|
|
||||||
out.Inverted = bool(in.Bool())
|
|
||||||
case "Names":
|
|
||||||
if in.IsNull() {
|
|
||||||
in.Skip()
|
|
||||||
out.Names = nil
|
|
||||||
} else {
|
|
||||||
in.Delim('[')
|
|
||||||
if out.Names == nil {
|
|
||||||
if !in.IsDelim(']') {
|
|
||||||
out.Names = make([]string, 0, 4)
|
|
||||||
} else {
|
|
||||||
out.Names = []string{}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out.Names = (out.Names)[:0]
|
|
||||||
}
|
|
||||||
for !in.IsDelim(']') {
|
|
||||||
var v10 string
|
|
||||||
v10 = string(in.String())
|
|
||||||
out.Names = append(out.Names, v10)
|
|
||||||
in.WantComma()
|
|
||||||
}
|
|
||||||
in.Delim(']')
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
in.SkipRecursive()
|
|
||||||
}
|
|
||||||
in.WantComma()
|
|
||||||
}
|
|
||||||
in.Delim('}')
|
|
||||||
if isTopLevel {
|
|
||||||
in.Consumed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func easyjsonE2758465EncodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain3(out *jwriter.Writer, in Resources) {
|
|
||||||
out.RawByte('{')
|
|
||||||
first := true
|
|
||||||
_ = first
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Inverted\":"
|
|
||||||
out.RawString(prefix[1:])
|
|
||||||
out.Bool(bool(in.Inverted))
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Names\":"
|
|
||||||
out.RawString(prefix)
|
|
||||||
if in.Names == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
|
|
||||||
out.RawString("null")
|
|
||||||
} else {
|
|
||||||
out.RawByte('[')
|
|
||||||
for v11, v12 := range in.Names {
|
|
||||||
if v11 > 0 {
|
|
||||||
out.RawByte(',')
|
|
||||||
}
|
|
||||||
out.String(string(v12))
|
|
||||||
}
|
|
||||||
out.RawByte(']')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.RawByte('}')
|
|
||||||
}
|
|
||||||
func easyjsonE2758465DecodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain2(in *jlexer.Lexer, out *Actions) {
|
|
||||||
isTopLevel := in.IsStart()
|
|
||||||
if in.IsNull() {
|
|
||||||
if isTopLevel {
|
|
||||||
in.Consumed()
|
|
||||||
}
|
|
||||||
in.Skip()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
in.Delim('{')
|
|
||||||
for !in.IsDelim('}') {
|
|
||||||
key := in.UnsafeFieldName(false)
|
|
||||||
in.WantColon()
|
|
||||||
if in.IsNull() {
|
|
||||||
in.Skip()
|
|
||||||
in.WantComma()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "Inverted":
|
|
||||||
out.Inverted = bool(in.Bool())
|
|
||||||
case "Names":
|
|
||||||
if in.IsNull() {
|
|
||||||
in.Skip()
|
|
||||||
out.Names = nil
|
|
||||||
} else {
|
|
||||||
in.Delim('[')
|
|
||||||
if out.Names == nil {
|
|
||||||
if !in.IsDelim(']') {
|
|
||||||
out.Names = make([]string, 0, 4)
|
|
||||||
} else {
|
|
||||||
out.Names = []string{}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out.Names = (out.Names)[:0]
|
|
||||||
}
|
|
||||||
for !in.IsDelim(']') {
|
|
||||||
var v13 string
|
|
||||||
v13 = string(in.String())
|
|
||||||
out.Names = append(out.Names, v13)
|
|
||||||
in.WantComma()
|
|
||||||
}
|
|
||||||
in.Delim(']')
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
in.SkipRecursive()
|
|
||||||
}
|
|
||||||
in.WantComma()
|
|
||||||
}
|
|
||||||
in.Delim('}')
|
|
||||||
if isTopLevel {
|
|
||||||
in.Consumed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func easyjsonE2758465EncodeGitFrostfsInfoTrueCloudLabPolicyEnginePkgChain2(out *jwriter.Writer, in Actions) {
|
|
||||||
out.RawByte('{')
|
|
||||||
first := true
|
|
||||||
_ = first
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Inverted\":"
|
|
||||||
out.RawString(prefix[1:])
|
|
||||||
out.Bool(bool(in.Inverted))
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const prefix string = ",\"Names\":"
|
|
||||||
out.RawString(prefix)
|
|
||||||
if in.Names == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
|
|
||||||
out.RawString("null")
|
|
||||||
} else {
|
|
||||||
out.RawByte('[')
|
|
||||||
for v14, v15 := range in.Names {
|
|
||||||
if v14 > 0 {
|
|
||||||
out.RawByte(',')
|
|
||||||
}
|
|
||||||
out.String(string(v15))
|
|
||||||
}
|
|
||||||
out.RawByte(']')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.RawByte('}')
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -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.Kind))
|
|
||||||
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.Kind = ConditionKindType(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
|
|
||||||
}
|
|
|
@ -1,247 +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 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,
|
|
||||||
Kind: ot,
|
|
||||||
Key: "",
|
|
||||||
Value: "",
|
|
||||||
})
|
|
||||||
|
|
||||||
result[2] = append(result[2], Condition{
|
|
||||||
Op: ct,
|
|
||||||
Kind: ot,
|
|
||||||
Key: "key",
|
|
||||||
Value: "",
|
|
||||||
})
|
|
||||||
|
|
||||||
result[2] = append(result[2], Condition{
|
|
||||||
Op: ct,
|
|
||||||
Kind: ot,
|
|
||||||
Key: "",
|
|
||||||
Value: "value",
|
|
||||||
})
|
|
||||||
|
|
||||||
result[2] = append(result[2], Condition{
|
|
||||||
Op: ct,
|
|
||||||
Kind: 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() []ConditionKindType {
|
|
||||||
return []ConditionKindType{
|
|
||||||
KindResource,
|
|
||||||
KindRequest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateTestMatchTypes() []MatchType {
|
|
||||||
return []MatchType{
|
|
||||||
MatchTypeDenyPriority,
|
|
||||||
MatchTypeFirstMatch,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
//go:build gofuzz
|
|
||||||
// +build gofuzz
|
|
||||||
|
|
||||||
package chain
|
|
||||||
|
|
||||||
func DoFuzzChainUnmarshalBinary(data []byte) int {
|
|
||||||
var ch Chain
|
|
||||||
err := ch.UnmarshalBinary(data)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
//go:build gofuzz
|
|
||||||
// +build gofuzz
|
|
||||||
|
|
||||||
package chain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func FuzzUnmarshal(f *testing.F) {
|
|
||||||
for _, id := range generateTestIDs() {
|
|
||||||
for _, rules := range generateTestRules() {
|
|
||||||
for _, matchType := range generateTestMatchTypes() {
|
|
||||||
|
|
||||||
chain := Chain{
|
|
||||||
ID: id,
|
|
||||||
Rules: rules,
|
|
||||||
MatchType: matchType,
|
|
||||||
}
|
|
||||||
data, err := chain.MarshalBinary()
|
|
||||||
require.NoError(f, err)
|
|
||||||
f.Add(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Fuzz(func(t *testing.T, data []byte) {
|
|
||||||
require.NotPanics(t, func() {
|
|
||||||
DoFuzzChainUnmarshalBinary(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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 ConditionKindType
|
|
||||||
str string
|
|
||||||
}{
|
|
||||||
{KindRequest, "Request"},
|
|
||||||
{KindResource, "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 ConditionKindType) 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 *ConditionKindType) 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 = ConditionKindType(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)
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
Kind: KindRequest,
|
|
||||||
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,
|
|
||||||
Kind: KindResource,
|
|
||||||
Key: native.PropertyKeyObjectType,
|
|
||||||
Value: "regular",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Status: Status(100),
|
|
||||||
Condition: []Condition{
|
|
||||||
{
|
|
||||||
Op: ConditionType(255),
|
|
||||||
Kind: ConditionKindType(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)
|
|
||||||
}
|
|
75
pkg/chain/testdata/test_status_json.json
vendored
75
pkg/chain/testdata/test_status_json.json
vendored
|
@ -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",
|
|
||||||
"Kind": "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",
|
|
||||||
"Kind": "Resource",
|
|
||||||
"Key": "$Object:objectType",
|
|
||||||
"Value": "regular"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Status": "100",
|
|
||||||
"Actions": {
|
|
||||||
"Inverted": false,
|
|
||||||
"Names": null
|
|
||||||
},
|
|
||||||
"Resources": {
|
|
||||||
"Inverted": false,
|
|
||||||
"Names": null
|
|
||||||
},
|
|
||||||
"Any": false,
|
|
||||||
"Condition": [
|
|
||||||
{
|
|
||||||
"Op": "255",
|
|
||||||
"Kind": "128",
|
|
||||||
"Key": "",
|
|
||||||
"Value": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"MatchType": "FirstMatch"
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
package engine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
type defaultChainRouter struct {
|
|
||||||
morph MorphRuleChainStorageReader
|
|
||||||
|
|
||||||
local LocalOverrideStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDefaultChainRouter(morph MorphRuleChainStorageReader) ChainRouter {
|
|
||||||
return &defaultChainRouter{
|
|
||||||
morph: morph,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDefaultChainRouterWithLocalOverrides(morph MorphRuleChainStorageReader, local LocalOverrideStorage) ChainRouter {
|
|
||||||
return &defaultChainRouter{
|
|
||||||
morph: morph,
|
|
||||||
local: local,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dr *defaultChainRouter) IsAllowed(name chain.Name, rt RequestTarget, r resource.Request) (status chain.Status, ruleFound bool, err error) {
|
|
||||||
status, ruleFound, err = dr.checkLocal(name, rt, r)
|
|
||||||
if err != nil {
|
|
||||||
return chain.NoRuleFound, false, err
|
|
||||||
} else if ruleFound {
|
|
||||||
// The local overrides have the highest priority and thus
|
|
||||||
// morph rules are not considered if a local one is found.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
status, ruleFound, err = dr.checkMorph(name, rt, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dr *defaultChainRouter) checkLocal(name chain.Name, rt RequestTarget, r resource.Request) (status chain.Status, ruleFound bool, err error) {
|
|
||||||
if dr.local == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasAllow bool
|
|
||||||
for _, target := range rt.Targets() {
|
|
||||||
status, ruleFound, err = dr.matchLocalOverrides(name, target, r)
|
|
||||||
if err != nil || ruleFound && status != chain.Allow {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hasAllow = hasAllow || ruleFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasAllow {
|
|
||||||
return chain.Allow, true, nil
|
|
||||||
}
|
|
||||||
return chain.NoRuleFound, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dr *defaultChainRouter) checkMorph(name chain.Name, rt RequestTarget, r resource.Request) (status chain.Status, ruleFound bool, err error) {
|
|
||||||
var hasAllow bool
|
|
||||||
for _, target := range rt.Targets() {
|
|
||||||
status, ruleFound, err = dr.matchMorphRuleChains(name, target, r)
|
|
||||||
if err != nil || ruleFound && status != chain.Allow {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hasAllow = hasAllow || ruleFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasAllow {
|
|
||||||
return chain.Allow, true, nil
|
|
||||||
}
|
|
||||||
return chain.NoRuleFound, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dr *defaultChainRouter) matchLocalOverrides(name chain.Name, target Target, r resource.Request) (status chain.Status, ruleFound bool, err error) {
|
|
||||||
localOverrides, err := dr.local.ListOverrides(name, target)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
status, ruleFound = dr.getStatusFromChains(localOverrides, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return chain.NoRuleFound, false, err
|
|
||||||
}
|
|
||||||
status, ruleFound = dr.getStatusFromChains(namespaceChains, r)
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
package engine
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrUnknownTarget = errors.New("unknown target type")
|
|
||||||
ErrChainNotFound = errors.New("chain not found")
|
|
||||||
ErrChainNameNotFound = errors.New("chain name not found")
|
|
||||||
ErrResourceNotFound = errors.New("resource not found")
|
|
||||||
)
|
|
|
@ -1,48 +0,0 @@
|
||||||
package inmemory
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
type inmemory struct {
|
|
||||||
router engine.ChainRouter
|
|
||||||
|
|
||||||
morph engine.MorphRuleChainStorage
|
|
||||||
|
|
||||||
local engine.LocalOverrideStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInMemoryLocalOverrides returns new inmemory instance of chain storage with
|
|
||||||
// local overrides manager.
|
|
||||||
func NewInMemoryLocalOverrides() engine.LocalOverrideEngine {
|
|
||||||
morph := NewInmemoryMorphRuleChainStorage()
|
|
||||||
local := NewInmemoryLocalStorage()
|
|
||||||
return &inmemory{
|
|
||||||
router: engine.NewDefaultChainRouterWithLocalOverrides(morph, local),
|
|
||||||
morph: morph,
|
|
||||||
local: local,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInMemory returns new inmemory instance of chain storage.
|
|
||||||
func NewInMemory() engine.Engine {
|
|
||||||
morph := NewInmemoryMorphRuleChainStorage()
|
|
||||||
return &inmemory{
|
|
||||||
router: engine.NewDefaultChainRouter(morph),
|
|
||||||
morph: morph,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *inmemory) LocalStorage() engine.LocalOverrideStorage {
|
|
||||||
return im.local
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *inmemory) MorphRuleChainStorage() engine.MorphRuleChainStorage {
|
|
||||||
return im.morph
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *inmemory) IsAllowed(name chain.Name, rt engine.RequestTarget, r resource.Request) (status chain.Status, ruleFound bool, err error) {
|
|
||||||
return im.router.IsAllowed(name, rt, r)
|
|
||||||
}
|
|
|
@ -1,377 +0,0 @@
|
||||||
package inmemory
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
|
||||||
resourcetest "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource/testutil"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAddRootOverrides(t *testing.T) {
|
|
||||||
s := NewInMemoryLocalOverrides()
|
|
||||||
|
|
||||||
target := engine.NamespaceTarget("")
|
|
||||||
|
|
||||||
id, err := s.LocalStorage().AddOverride(chain.S3, target, &chain.Chain{
|
|
||||||
Rules: []chain.Rule{{
|
|
||||||
Status: chain.Allow,
|
|
||||||
Actions: chain.Actions{Names: []string{"s3:PutObject"}},
|
|
||||||
Resources: chain.Resources{Names: []string{"*"}},
|
|
||||||
}},
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
res, err := s.LocalStorage().ListOverrides(chain.S3, target)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, res, 1)
|
|
||||||
require.Equal(t, string(id), string(res[0].ID))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInmemory_MultipleTargets(t *testing.T) {
|
|
||||||
const op = "ape::test::op"
|
|
||||||
|
|
||||||
targets := []engine.Target{
|
|
||||||
engine.NamespaceTarget("ns1"),
|
|
||||||
engine.ContainerTarget("cnr1"),
|
|
||||||
engine.GroupTarget("group1"),
|
|
||||||
engine.UserTarget("user1"),
|
|
||||||
}
|
|
||||||
req := resourcetest.NewRequest(op, resourcetest.NewResource("r", nil), nil)
|
|
||||||
target := engine.NewRequestTargetExtended("ns1", "cnr1", "user1", []string{"group1"})
|
|
||||||
for _, tt := range targets {
|
|
||||||
t.Run("morph", func(t *testing.T) {
|
|
||||||
s := NewInMemoryLocalOverrides()
|
|
||||||
s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, tt, &chain.Chain{
|
|
||||||
Rules: []chain.Rule{{
|
|
||||||
Status: chain.Allow,
|
|
||||||
Actions: chain.Actions{Names: []string{op}},
|
|
||||||
}},
|
|
||||||
})
|
|
||||||
|
|
||||||
status, found, err := s.IsAllowed(chain.Ingress, target, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, found)
|
|
||||||
require.Equal(t, chain.Allow, status)
|
|
||||||
})
|
|
||||||
t.Run("override", func(t *testing.T) {
|
|
||||||
s := NewInMemoryLocalOverrides()
|
|
||||||
s.LocalStorage().AddOverride(chain.Ingress, tt, &chain.Chain{
|
|
||||||
Rules: []chain.Rule{{
|
|
||||||
Status: chain.Allow,
|
|
||||||
Actions: chain.Actions{Names: []string{op}},
|
|
||||||
}},
|
|
||||||
})
|
|
||||||
|
|
||||||
status, found, err := s.IsAllowed(chain.Ingress, target, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, found)
|
|
||||||
require.Equal(t, chain.Allow, status)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInmemory(t *testing.T) {
|
|
||||||
const (
|
|
||||||
object = "native::object::abc/xyz"
|
|
||||||
container = "native::object::abc/*"
|
|
||||||
namespace = "Tenant1"
|
|
||||||
namespace2 = "Tenant2"
|
|
||||||
actor1 = "owner1"
|
|
||||||
actor2 = "owner2"
|
|
||||||
)
|
|
||||||
|
|
||||||
s := NewInMemoryLocalOverrides()
|
|
||||||
|
|
||||||
// Object which was put via S3.
|
|
||||||
res := resourcetest.NewResource(object, map[string]string{"FromS3": "true"})
|
|
||||||
// Request initiating from the trusted subnet and actor.
|
|
||||||
reqGood := resourcetest.NewRequest("native::object::put", res, map[string]string{
|
|
||||||
"SourceIP": "10.1.1.12",
|
|
||||||
"Actor": actor1,
|
|
||||||
})
|
|
||||||
|
|
||||||
status, ok, _ := s.IsAllowed(chain.Ingress, engine.NewRequestTargetWithNamespace(namespace), reqGood)
|
|
||||||
require.Equal(t, chain.NoRuleFound, status)
|
|
||||||
require.False(t, ok)
|
|
||||||
|
|
||||||
s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(namespace), &chain.Chain{
|
|
||||||
Rules: []chain.Rule{
|
|
||||||
{ // Restrict to remove ANY object from the namespace.
|
|
||||||
Status: chain.AccessDenied,
|
|
||||||
Actions: chain.Actions{Names: []string{"native::object::delete"}},
|
|
||||||
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
|
||||||
},
|
|
||||||
{ // Allow to put object only from the trusted subnet AND trusted actor, deny otherwise.
|
|
||||||
Status: chain.AccessDenied,
|
|
||||||
Actions: chain.Actions{Names: []string{"native::object::put"}},
|
|
||||||
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
|
||||||
Any: true,
|
|
||||||
Condition: []chain.Condition{
|
|
||||||
{
|
|
||||||
Op: chain.CondStringNotLike,
|
|
||||||
Kind: chain.KindRequest,
|
|
||||||
Key: "SourceIP",
|
|
||||||
Value: "10.1.1.*",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Op: chain.CondStringNotEquals,
|
|
||||||
Kind: chain.KindRequest,
|
|
||||||
Key: "Actor",
|
|
||||||
Value: actor1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
_, 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{
|
|
||||||
Rules: []chain.Rule{
|
|
||||||
{ // Deny all expect "native::object::get" for all objects expect "native::object::abc/xyz".
|
|
||||||
Status: chain.AccessDenied,
|
|
||||||
Actions: chain.Actions{Inverted: true, Names: []string{"native::object::get"}},
|
|
||||||
Resources: chain.Resources{Inverted: true, Names: []string{object}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
_, 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{
|
|
||||||
Rules: []chain.Rule{
|
|
||||||
{ // Allow to actor2 to get objects from the specific container only if they have `Department=HR` attribute.
|
|
||||||
Status: chain.Allow,
|
|
||||||
Actions: chain.Actions{Names: []string{"native::object::get"}},
|
|
||||||
Resources: chain.Resources{Names: []string{"native::object::abc/*"}},
|
|
||||||
Condition: []chain.Condition{
|
|
||||||
{
|
|
||||||
Op: chain.CondStringEquals,
|
|
||||||
Kind: chain.KindResource,
|
|
||||||
Key: "Department",
|
|
||||||
Value: "HR",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Op: chain.CondStringEquals,
|
|
||||||
Kind: chain.KindRequest,
|
|
||||||
Key: "Actor",
|
|
||||||
Value: actor2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
_, 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) {
|
|
||||||
// Request initiating from the untrusted subnet.
|
|
||||||
reqBadIP := resourcetest.NewRequest("native::object::put", res, map[string]string{
|
|
||||||
"SourceIP": "10.122.1.20",
|
|
||||||
"Actor": actor1,
|
|
||||||
})
|
|
||||||
status, ok, _ := s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqBadIP)
|
|
||||||
require.Equal(t, chain.AccessDenied, status)
|
|
||||||
require.True(t, ok)
|
|
||||||
})
|
|
||||||
t.Run("bad actor, namespace deny", func(t *testing.T) {
|
|
||||||
// Request initiating from the untrusted actor.
|
|
||||||
reqBadActor := resourcetest.NewRequest("native::object::put", res, map[string]string{
|
|
||||||
"SourceIP": "10.1.1.13",
|
|
||||||
"Actor": actor2,
|
|
||||||
})
|
|
||||||
status, ok, _ := s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqBadActor)
|
|
||||||
require.Equal(t, chain.AccessDenied, status)
|
|
||||||
require.True(t, ok)
|
|
||||||
})
|
|
||||||
t.Run("bad object, container deny", func(t *testing.T) {
|
|
||||||
objGood := resourcetest.NewResource("native::object::abc/id1", map[string]string{"Department": "HR"})
|
|
||||||
objBadAttr := resourcetest.NewResource("native::object::abc/id2", map[string]string{"Department": "Support"})
|
|
||||||
|
|
||||||
status, ok, _ := s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), resourcetest.NewRequest("native::object::get", objGood, map[string]string{
|
|
||||||
"SourceIP": "10.1.1.14",
|
|
||||||
"Actor": actor2,
|
|
||||||
}))
|
|
||||||
require.Equal(t, chain.Allow, status)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), resourcetest.NewRequest("native::object::get", objBadAttr, map[string]string{
|
|
||||||
"SourceIP": "10.1.1.14",
|
|
||||||
"Actor": actor2,
|
|
||||||
}))
|
|
||||||
require.Equal(t, chain.NoRuleFound, status)
|
|
||||||
require.False(t, ok)
|
|
||||||
})
|
|
||||||
t.Run("bad operation, namespace deny", func(t *testing.T) {
|
|
||||||
// Request with the forbidden operation.
|
|
||||||
reqBadOperation := resourcetest.NewRequest("native::object::delete", res, map[string]string{
|
|
||||||
"SourceIP": "10.1.1.12",
|
|
||||||
"Actor": actor1,
|
|
||||||
})
|
|
||||||
status, ok, _ := s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqBadOperation)
|
|
||||||
require.Equal(t, chain.AccessDenied, status)
|
|
||||||
require.True(t, ok)
|
|
||||||
})
|
|
||||||
t.Run("inverted rules", func(t *testing.T) {
|
|
||||||
req := resourcetest.NewRequest("native::object::put", resourcetest.NewResource(object, nil), nil)
|
|
||||||
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace2, container), req)
|
|
||||||
require.Equal(t, chain.NoRuleFound, status)
|
|
||||||
require.False(t, ok)
|
|
||||||
|
|
||||||
req = resourcetest.NewRequest("native::object::put", resourcetest.NewResource("native::object::cba/def", nil), nil)
|
|
||||||
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace2, container), req)
|
|
||||||
require.Equal(t, chain.AccessDenied, status)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
req = resourcetest.NewRequest("native::object::get", resourcetest.NewResource("native::object::cba/def", nil), nil)
|
|
||||||
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace2, container), req)
|
|
||||||
require.Equal(t, chain.NoRuleFound, status)
|
|
||||||
require.False(t, ok)
|
|
||||||
})
|
|
||||||
t.Run("good", func(t *testing.T) {
|
|
||||||
status, ok, _ = s.IsAllowed(chain.Ingress, engine.NewRequestTarget(namespace, container), reqGood)
|
|
||||||
require.Equal(t, chain.NoRuleFound, status)
|
|
||||||
require.False(t, ok)
|
|
||||||
|
|
||||||
t.Run("quota on a different container", func(t *testing.T) {
|
|
||||||
s.LocalStorage().AddOverride(chain.Ingress, engine.ContainerTarget(container), &chain.Chain{
|
|
||||||
Rules: []chain.Rule{{
|
|
||||||
Status: chain.QuotaLimitReached,
|
|
||||||
Actions: chain.Actions{Names: []string{"native::object::put"}},
|
|
||||||
Resources: chain.Resources{Names: []string{"native::object::cba/*"}},
|
|
||||||
}},
|
|
||||||
})
|
|
||||||
|
|
||||||
_, 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)
|
|
||||||
require.Equal(t, chain.NoRuleFound, status)
|
|
||||||
require.False(t, ok)
|
|
||||||
})
|
|
||||||
|
|
||||||
var quotaRuleChainID chain.ID
|
|
||||||
t.Run("quota on the request container", func(t *testing.T) {
|
|
||||||
quotaRuleChainID, _ = s.LocalStorage().AddOverride(chain.Ingress, engine.ContainerTarget(container), &chain.Chain{
|
|
||||||
Rules: []chain.Rule{{
|
|
||||||
Status: chain.QuotaLimitReached,
|
|
||||||
Actions: chain.Actions{Names: []string{"native::object::put"}},
|
|
||||||
Resources: chain.Resources{Names: []string{"native::object::abc/*"}},
|
|
||||||
}},
|
|
||||||
})
|
|
||||||
|
|
||||||
_, 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)
|
|
||||||
require.Equal(t, chain.QuotaLimitReached, status)
|
|
||||||
require.True(t, ok)
|
|
||||||
})
|
|
||||||
t.Run("removed quota on the request container", func(t *testing.T) {
|
|
||||||
err := s.LocalStorage().RemoveOverride(chain.Ingress, engine.ContainerTarget(container), quotaRuleChainID)
|
|
||||||
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)
|
|
||||||
require.Equal(t, chain.NoRuleFound, status)
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,180 +0,0 @@
|
||||||
package inmemory
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
type targetToChain map[engine.Target][]*chain.Chain
|
|
||||||
|
|
||||||
type inmemoryLocalStorage struct {
|
|
||||||
usedChainID map[string]struct{}
|
|
||||||
nameToResourceChains map[chain.Name]targetToChain
|
|
||||||
guard *sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInmemoryLocalStorage() engine.LocalOverrideStorage {
|
|
||||||
return &inmemoryLocalStorage{
|
|
||||||
usedChainID: map[string]struct{}{},
|
|
||||||
nameToResourceChains: make(map[chain.Name]targetToChain),
|
|
||||||
guard: &sync.RWMutex{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *inmemoryLocalStorage) generateChainID(name chain.Name, target engine.Target) chain.ID {
|
|
||||||
var id chain.ID
|
|
||||||
for {
|
|
||||||
suffix := rand.Uint32() % 100
|
|
||||||
sid := fmt.Sprintf("%s:%s/%d", name, target.Name, suffix)
|
|
||||||
sid = strings.ReplaceAll(sid, "*", "")
|
|
||||||
sid = strings.ReplaceAll(sid, "/", ":")
|
|
||||||
sid = strings.ReplaceAll(sid, "::", ":")
|
|
||||||
_, ok := s.usedChainID[sid]
|
|
||||||
if ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s.usedChainID[sid] = struct{}{}
|
|
||||||
|
|
||||||
id = chain.ID(sid)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *inmemoryLocalStorage) AddOverride(name chain.Name, target engine.Target, c *chain.Chain) (chain.ID, error) {
|
|
||||||
s.guard.Lock()
|
|
||||||
defer s.guard.Unlock()
|
|
||||||
|
|
||||||
if target.Name == "" {
|
|
||||||
target.Name = "root"
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddOverride assigns generated chain ID if it has not been assigned.
|
|
||||||
if len(c.ID) == 0 {
|
|
||||||
c.ID = s.generateChainID(name, target)
|
|
||||||
}
|
|
||||||
if s.nameToResourceChains[name] == nil {
|
|
||||||
s.nameToResourceChains[name] = make(targetToChain)
|
|
||||||
}
|
|
||||||
rc := s.nameToResourceChains[name]
|
|
||||||
for i := range rc[target] {
|
|
||||||
if bytes.Equal(rc[target][i].ID, c.ID) {
|
|
||||||
rc[target][i] = c
|
|
||||||
return c.ID, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rc[target] = append(rc[target], c)
|
|
||||||
return c.ID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return nil, engine.ErrChainNameNotFound
|
|
||||||
}
|
|
||||||
if target.Name == "" {
|
|
||||||
target.Name = "root"
|
|
||||||
}
|
|
||||||
chains, ok := s.nameToResourceChains[name][target]
|
|
||||||
if !ok {
|
|
||||||
return nil, engine.ErrResourceNotFound
|
|
||||||
}
|
|
||||||
for _, c := range chains {
|
|
||||||
if bytes.Equal(c.ID, chainID) {
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, engine.ErrChainNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return engine.ErrChainNameNotFound
|
|
||||||
}
|
|
||||||
if target.Name == "" {
|
|
||||||
target.Name = "root"
|
|
||||||
}
|
|
||||||
chains, ok := s.nameToResourceChains[name][target]
|
|
||||||
if !ok {
|
|
||||||
return engine.ErrResourceNotFound
|
|
||||||
}
|
|
||||||
for i, c := range chains {
|
|
||||||
if bytes.Equal(c.ID, chainID) {
|
|
||||||
s.nameToResourceChains[name][target] = append(chains[:i], chains[i+1:]...)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
s.guard.RLock()
|
|
||||||
defer s.guard.RUnlock()
|
|
||||||
|
|
||||||
rcs, ok := s.nameToResourceChains[name]
|
|
||||||
if !ok {
|
|
||||||
return []*chain.Chain{}, nil
|
|
||||||
}
|
|
||||||
if target.Name == "" {
|
|
||||||
target.Name = "root"
|
|
||||||
}
|
|
||||||
for t, chains := range rcs {
|
|
||||||
if t.Type != target.Type {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !util.GlobMatch(target.Name, t.Name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return chains, nil
|
|
||||||
}
|
|
||||||
return []*chain.Chain{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *inmemoryLocalStorage) DropAllOverrides(name chain.Name) error {
|
|
||||||
s.guard.Lock()
|
|
||||||
defer s.guard.Unlock()
|
|
||||||
|
|
||||||
s.nameToResourceChains[name] = make(targetToChain)
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,268 +0,0 @@
|
||||||
package inmemory
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
container = "native:::object/ExYw/*"
|
|
||||||
chainID = "ingress:ExYw"
|
|
||||||
nonExistChainId = "ingress:LxGyWyL"
|
|
||||||
)
|
|
||||||
|
|
||||||
var resrc = engine.ContainerTarget(container)
|
|
||||||
|
|
||||||
func testInmemLocalStorage() *inmemoryLocalStorage {
|
|
||||||
return NewInmemoryLocalStorage().(*inmemoryLocalStorage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddOverride(t *testing.T) {
|
|
||||||
inmem := testInmemLocalStorage()
|
|
||||||
|
|
||||||
inmem.AddOverride(chain.Ingress, resrc, &chain.Chain{
|
|
||||||
Rules: []chain.Rule{
|
|
||||||
{
|
|
||||||
Status: chain.AccessDenied,
|
|
||||||
Actions: chain.Actions{Names: []string{"native::object::delete"}},
|
|
||||||
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
ingressChains, ok := inmem.nameToResourceChains[chain.Ingress]
|
|
||||||
require.True(t, ok)
|
|
||||||
resourceChains, ok := ingressChains[resrc]
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Len(t, resourceChains, 1)
|
|
||||||
require.Len(t, resourceChains[0].Rules, 1)
|
|
||||||
|
|
||||||
inmem.AddOverride(chain.Ingress, resrc, &chain.Chain{
|
|
||||||
Rules: []chain.Rule{
|
|
||||||
{
|
|
||||||
Status: chain.QuotaLimitReached,
|
|
||||||
Actions: chain.Actions{Names: []string{"native::object::put"}},
|
|
||||||
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Status: chain.AccessDenied,
|
|
||||||
Actions: chain.Actions{Names: []string{"native::object::get"}},
|
|
||||||
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
ingressChains, ok = inmem.nameToResourceChains[chain.Ingress]
|
|
||||||
require.True(t, ok)
|
|
||||||
resourceChains, ok = ingressChains[resrc]
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Len(t, resourceChains, 2)
|
|
||||||
require.Len(t, resourceChains[1].Rules, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoveOverride(t *testing.T) {
|
|
||||||
t.Run("remove from empty storage", func(t *testing.T) {
|
|
||||||
inmem := testInmemLocalStorage()
|
|
||||||
err := inmem.RemoveOverride(chain.Ingress, resrc, chain.ID(chainID))
|
|
||||||
require.ErrorIs(t, err, engine.ErrChainNameNotFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("remove not added chain id", func(t *testing.T) {
|
|
||||||
inmem := testInmemLocalStorage()
|
|
||||||
inmem.AddOverride(chain.Ingress, resrc, &chain.Chain{
|
|
||||||
ID: chain.ID(chainID),
|
|
||||||
Rules: []chain.Rule{
|
|
||||||
{ // Restrict to remove ANY object from the namespace.
|
|
||||||
Status: chain.AccessDenied,
|
|
||||||
Actions: chain.Actions{Names: []string{"native::object::delete"}},
|
|
||||||
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
err := inmem.RemoveOverride(chain.Ingress, resrc, chain.ID(nonExistChainId))
|
|
||||||
require.ErrorIs(t, err, engine.ErrChainNotFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("remove existing chain id", func(t *testing.T) {
|
|
||||||
inmem := testInmemLocalStorage()
|
|
||||||
inmem.AddOverride(chain.Ingress, resrc, &chain.Chain{
|
|
||||||
ID: chain.ID(chainID),
|
|
||||||
Rules: []chain.Rule{
|
|
||||||
{ // Restrict to remove ANY object from the namespace.
|
|
||||||
Status: chain.AccessDenied,
|
|
||||||
Actions: chain.Actions{Names: []string{"native::object::delete"}},
|
|
||||||
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
err := inmem.RemoveOverride(chain.Ingress, resrc, chain.ID(chainID))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ingressChains, ok := inmem.nameToResourceChains[chain.Ingress]
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Len(t, ingressChains, 1)
|
|
||||||
resourceChains, ok := ingressChains[resrc]
|
|
||||||
require.True(t, ok)
|
|
||||||
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) {
|
|
||||||
addChain := &chain.Chain{
|
|
||||||
ID: chain.ID(chainID),
|
|
||||||
Rules: []chain.Rule{
|
|
||||||
{ // Restrict to remove ANY object from the namespace.
|
|
||||||
Status: chain.AccessDenied,
|
|
||||||
Actions: chain.Actions{Names: []string{"native::object::delete"}},
|
|
||||||
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("get from empty storage", func(t *testing.T) {
|
|
||||||
inmem := testInmemLocalStorage()
|
|
||||||
_, err := inmem.GetOverride(chain.Ingress, resrc, chain.ID(chainID))
|
|
||||||
require.ErrorIs(t, err, engine.ErrChainNameNotFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("get not added chain id", func(t *testing.T) {
|
|
||||||
inmem := testInmemLocalStorage()
|
|
||||||
inmem.AddOverride(chain.Ingress, resrc, addChain)
|
|
||||||
|
|
||||||
const nonExistingChainID = "ingress:LxGyWyL"
|
|
||||||
|
|
||||||
_, err := inmem.GetOverride(chain.Ingress, resrc, chain.ID(nonExistingChainID))
|
|
||||||
require.ErrorIs(t, err, engine.ErrChainNotFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("get existing chain id", func(t *testing.T) {
|
|
||||||
inmem := testInmemLocalStorage()
|
|
||||||
inmem.AddOverride(chain.Ingress, resrc, addChain)
|
|
||||||
|
|
||||||
c, err := inmem.GetOverride(chain.Ingress, resrc, chain.ID(chainID))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, *addChain, *c)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("get removed chain id", func(t *testing.T) {
|
|
||||||
inmem := testInmemLocalStorage()
|
|
||||||
inmem.AddOverride(chain.Ingress, resrc, addChain)
|
|
||||||
|
|
||||||
err := inmem.RemoveOverride(chain.Ingress, resrc, chain.ID(chainID))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = inmem.GetOverride(chain.Ingress, resrc, chain.ID(chainID))
|
|
||||||
require.ErrorIs(t, err, engine.ErrChainNotFound)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListOverrides(t *testing.T) {
|
|
||||||
addChain := &chain.Chain{
|
|
||||||
ID: chain.ID(chainID),
|
|
||||||
Rules: []chain.Rule{
|
|
||||||
{ // Restrict to remove ANY object from the namespace.
|
|
||||||
Status: chain.AccessDenied,
|
|
||||||
Actions: chain.Actions{Names: []string{"native::object::delete"}},
|
|
||||||
Resources: chain.Resources{Names: []string{"native::object::*"}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("list empty storage", func(t *testing.T) {
|
|
||||||
inmem := testInmemLocalStorage()
|
|
||||||
l, _ := inmem.ListOverrides(chain.Ingress, resrc)
|
|
||||||
require.Len(t, l, 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("list with one added resource", func(t *testing.T) {
|
|
||||||
inmem := testInmemLocalStorage()
|
|
||||||
inmem.AddOverride(chain.Ingress, resrc, addChain)
|
|
||||||
l, _ := inmem.ListOverrides(chain.Ingress, resrc)
|
|
||||||
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) {
|
|
||||||
inmem := testInmemLocalStorage()
|
|
||||||
inmem.AddOverride(chain.Ingress, resrc, addChain)
|
|
||||||
l, _ := inmem.ListOverrides(chain.Ingress, resrc)
|
|
||||||
require.Len(t, l, 1)
|
|
||||||
|
|
||||||
_ = inmem.DropAllOverrides(chain.Ingress)
|
|
||||||
l, _ = inmem.ListOverrides(chain.Ingress, resrc)
|
|
||||||
require.Len(t, l, 0)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateID(t *testing.T) {
|
|
||||||
inmem := testInmemLocalStorage()
|
|
||||||
ids := make([]chain.ID, 0, 100)
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
ids = append(ids, inmem.generateChainID(chain.Ingress, resrc))
|
|
||||||
}
|
|
||||||
require.False(t, hasDuplicates(ids))
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasDuplicates(ids []chain.ID) bool {
|
|
||||||
seen := make(map[string]bool)
|
|
||||||
for _, id := range ids {
|
|
||||||
if seen[string(id)] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
seen[string(id)] = true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,112 +0,0 @@
|
||||||
package inmemory
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
||||||
"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/vm/stackitem"
|
|
||||||
)
|
|
||||||
|
|
||||||
type inmemoryMorphRuleChainStorage struct {
|
|
||||||
storage engine.LocalOverrideStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInmemoryMorphRuleChainStorage() engine.MorphRuleChainStorage {
|
|
||||||
return &inmemoryMorphRuleChainStorage{
|
|
||||||
storage: NewInmemoryLocalStorage(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *inmemoryMorphRuleChainStorage) AddMorphRuleChain(name chain.Name, target engine.Target, c *chain.Chain) (_ util.Uint256, _ uint32, err error) {
|
|
||||||
switch target.Type {
|
|
||||||
case engine.Namespace, engine.Container, engine.User, engine.Group:
|
|
||||||
_, err = s.storage.AddOverride(name, target, c)
|
|
||||||
default:
|
|
||||||
err = engine.ErrUnknownTarget
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *inmemoryMorphRuleChainStorage) RemoveMorphRuleChain(name chain.Name, target engine.Target, chainID chain.ID) (_ util.Uint256, _ uint32, err error) {
|
|
||||||
switch target.Type {
|
|
||||||
case engine.Namespace, engine.Container, engine.User, engine.Group:
|
|
||||||
err = s.storage.RemoveOverride(name, target, chainID)
|
|
||||||
default:
|
|
||||||
err = engine.ErrUnknownTarget
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *inmemoryMorphRuleChainStorage) RemoveMorphRuleChainsByTarget(name chain.Name, target engine.Target) (_ util.Uint256, _ uint32, err error) {
|
|
||||||
switch target.Type {
|
|
||||||
case engine.Namespace, engine.Container, engine.User, engine.Group:
|
|
||||||
err = s.storage.RemoveOverridesByTarget(name, target)
|
|
||||||
default:
|
|
||||||
err = engine.ErrUnknownTarget
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *inmemoryMorphRuleChainStorage) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
|
|
||||||
switch target.Type {
|
|
||||||
case engine.Namespace, engine.Container, engine.User, engine.Group:
|
|
||||||
return s.storage.ListOverrides(name, target)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
package engine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ChainRouter interface {
|
|
||||||
// IsAllowed returns status for the operation after all checks.
|
|
||||||
// The second return value signifies whether a matching rule was found.
|
|
||||||
IsAllowed(name chain.Name, reqTarget RequestTarget, r resource.Request) (status chain.Status, found bool, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalOverrideStorage is the interface to manage local overrides defined
|
|
||||||
// for a node. Local overrides have a higher priority than chains got from morph storage.
|
|
||||||
type LocalOverrideStorage interface {
|
|
||||||
AddOverride(name chain.Name, target Target, c *chain.Chain) (chain.ID, error)
|
|
||||||
|
|
||||||
GetOverride(name chain.Name, target Target, chainID chain.ID) (*chain.Chain, 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)
|
|
||||||
|
|
||||||
DropAllOverrides(name chain.Name) error
|
|
||||||
|
|
||||||
ListOverrideDefinedTargets(name chain.Name) ([]Target, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TargetType rune
|
|
||||||
|
|
||||||
const (
|
|
||||||
Namespace TargetType = 'n'
|
|
||||||
Container TargetType = 'c'
|
|
||||||
User TargetType = 'u'
|
|
||||||
Group TargetType = 'g'
|
|
||||||
)
|
|
||||||
|
|
||||||
type Target struct {
|
|
||||||
Type TargetType
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestTarget combines several targets on which the request is performed.
|
|
||||||
type RequestTarget struct {
|
|
||||||
Namespace *Target
|
|
||||||
Container *Target
|
|
||||||
User *Target
|
|
||||||
Groups []Target
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequestTargetWithNamespace(namespace string) RequestTarget {
|
|
||||||
nt := NamespaceTarget(namespace)
|
|
||||||
return RequestTarget{
|
|
||||||
Namespace: &nt,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequestTargetWithContainer(container string) RequestTarget {
|
|
||||||
ct := ContainerTarget(container)
|
|
||||||
return RequestTarget{
|
|
||||||
Container: &ct,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequestTarget(namespace, container string) RequestTarget {
|
|
||||||
nt := NamespaceTarget(namespace)
|
|
||||||
ct := ContainerTarget(container)
|
|
||||||
return RequestTarget{
|
|
||||||
Namespace: &nt,
|
|
||||||
Container: &ct,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequestTargetExtended(namespace, container, user string, groups []string) RequestTarget {
|
|
||||||
nt := NamespaceTarget(namespace)
|
|
||||||
ct := ContainerTarget(container)
|
|
||||||
u := UserTarget(user)
|
|
||||||
rt := RequestTarget{
|
|
||||||
Namespace: &nt,
|
|
||||||
Container: &ct,
|
|
||||||
User: &u,
|
|
||||||
}
|
|
||||||
if len(groups) != 0 {
|
|
||||||
rt.Groups = make([]Target, len(groups))
|
|
||||||
for i := range groups {
|
|
||||||
rt.Groups[i] = GroupTarget(groups[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *RequestTarget) Targets() (targets []Target) {
|
|
||||||
if rt.Namespace != nil {
|
|
||||||
targets = append(targets, *rt.Namespace)
|
|
||||||
}
|
|
||||||
if rt.Container != nil {
|
|
||||||
targets = append(targets, *rt.Container)
|
|
||||||
}
|
|
||||||
if rt.User != nil {
|
|
||||||
targets = append(targets, *rt.User)
|
|
||||||
}
|
|
||||||
if len(rt.Groups) != 0 {
|
|
||||||
targets = append(targets, rt.Groups...)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func NamespaceTarget(namespace string) Target {
|
|
||||||
return Target{
|
|
||||||
Type: Namespace,
|
|
||||||
Name: namespace,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ContainerTarget(container string) Target {
|
|
||||||
return Target{
|
|
||||||
Type: Container,
|
|
||||||
Name: container,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func UserTarget(user string) Target {
|
|
||||||
return Target{
|
|
||||||
Type: User,
|
|
||||||
Name: user,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GroupTarget(group string) Target {
|
|
||||||
return Target{
|
|
||||||
Type: Group,
|
|
||||||
Name: group,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MorphRuleChainStorageReader is the interface that provides read-only methods to receive
|
|
||||||
// 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.
|
|
||||||
type MorphRuleChainStorage interface {
|
|
||||||
MorphRuleChainStorageReader
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// RemoveMorphRuleChainsByTarget removes all chains by target and returns transaction hash, VUB and error.
|
|
||||||
RemoveMorphRuleChainsByTarget(name chain.Name, target Target) (util.Uint256, uint32, error)
|
|
||||||
|
|
||||||
SetAdmin(addr util.Uint160) (util.Uint256, uint32, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Engine is the interface that provides methods to check request permissions checking
|
|
||||||
// chain rules from morph client - this implies using the policy contract.
|
|
||||||
type Engine interface {
|
|
||||||
ChainRouter
|
|
||||||
|
|
||||||
MorphRuleChainStorage() MorphRuleChainStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalOverrideEngine is extended Engine that also provides methods to manage a local
|
|
||||||
// chain rule storage. Local overrides must have the highest priority during request checking.
|
|
||||||
type LocalOverrideEngine interface {
|
|
||||||
Engine
|
|
||||||
|
|
||||||
LocalStorage() LocalOverrideStorage
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,248 +0,0 @@
|
||||||
package policy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
|
|
||||||
client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/policy"
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
||||||
"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/rpcclient/actor"
|
|
||||||
neoinvoker "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrEmptyChainID = errors.New("chain id is not set")
|
|
||||||
|
|
||||||
ErrEngineTargetTypeUnsupported = errors.New("this target type is not supported yet")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ContractStorage is the interface to manage chain rules within Policy contract.
|
|
||||||
type ContractStorage struct {
|
|
||||||
hash util.Uint160
|
|
||||||
|
|
||||||
actor ContractStorageActor
|
|
||||||
|
|
||||||
contractInterface *client.Contract
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ engine.MorphRuleChainStorage = (*ContractStorage)(nil)
|
|
||||||
|
|
||||||
// ContractStorageReader is the interface to read data from Policy contract.
|
|
||||||
type ContractStorageReader struct {
|
|
||||||
hash util.Uint160
|
|
||||||
|
|
||||||
invoker ContractStorageInvoker
|
|
||||||
|
|
||||||
contractReaderInterface *client.ContractReader
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContractStorageActor interface {
|
|
||||||
client.Actor
|
|
||||||
GetRPCInvoker() neoinvoker.RPCInvoke
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ engine.MorphRuleChainStorageReader = (*ContractStorageReader)(nil)
|
|
||||||
|
|
||||||
func NewContractStorage(actor ContractStorageActor, contract util.Uint160) *ContractStorage {
|
|
||||||
return &ContractStorage{
|
|
||||||
hash: contract,
|
|
||||||
actor: actor,
|
|
||||||
contractInterface: client.New(actor, contract),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type contractStorageActorImpl struct {
|
|
||||||
client.Actor
|
|
||||||
rpcActor actor.RPCActor
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ContractStorageActor = &contractStorageActorImpl{}
|
|
||||||
|
|
||||||
func (c *contractStorageActorImpl) GetRPCInvoker() neoinvoker.RPCInvoke {
|
|
||||||
return c.rpcActor
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContractStorageWithSimpleActor constructs core actor from `rpcActor`.
|
|
||||||
//
|
|
||||||
// Note: NewContractStorageWithSimpleActor is appropriate only for call-only-once cases (for example, in CLIs). Otherwise, it is unsafe,
|
|
||||||
// because core actor may use invalidated `rpcActor` if some connection errors occurred.
|
|
||||||
func NewContractStorageWithSimpleActor(rpcActor actor.RPCActor, acc *wallet.Account, contract util.Uint160) (*ContractStorage, error) {
|
|
||||||
act, err := actor.NewSimple(rpcActor, acc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create simple actor: %w", err)
|
|
||||||
}
|
|
||||||
return NewContractStorage(&contractStorageActorImpl{Actor: act, rpcActor: rpcActor}, contract), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
err = ErrEmptyChainID
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var kind policy.Kind
|
|
||||||
kind, err = policyKind(target.Type)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fullName := prefixedChainName(name, c.ID)
|
|
||||||
|
|
||||||
txHash, vub, err = s.contractInterface.AddChain(big.NewInt(int64(kind)), target.Name, fullName, c.Bytes())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ContractStorage) RemoveMorphRuleChain(name chain.Name, target engine.Target, chainID chain.ID) (txHash util.Uint256, vub uint32, err error) {
|
|
||||||
if len(chainID) == 0 {
|
|
||||||
err = ErrEmptyChainID
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var kind policy.Kind
|
|
||||||
kind, err = policyKind(target.Type)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fullName := prefixedChainName(name, chainID)
|
|
||||||
|
|
||||||
txHash, vub, err = s.contractInterface.RemoveChain(big.NewInt(int64(kind)), target.Name, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func listChains(name chain.Name, target engine.Target, rpcInvoker neoinvoker.RPCInvoke, hash util.Uint160) ([]*chain.Chain, error) {
|
|
||||||
kind, err := policyKind(target.Type)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
method = "iteratorChainsByPrefix"
|
|
||||||
batchSize = neoinvoker.DefaultIteratorResultItems
|
|
||||||
)
|
|
||||||
|
|
||||||
inv := neoinvoker.New(rpcInvoker, nil)
|
|
||||||
params := []any{
|
|
||||||
big.NewInt(int64(kind)), target.Name, []byte(name),
|
|
||||||
}
|
|
||||||
|
|
||||||
items, err := commonclient.ReadIteratorItems(inv, batchSize, hash, method, params...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("read items error: %w", 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 *ContractStorage) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
|
|
||||||
return listChains(name, target, s.actor.GetRPCInvoker(), s.hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContractStorageInvoker interface {
|
|
||||||
client.Invoker
|
|
||||||
GetRPCInvoker() neoinvoker.RPCInvoke
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewContractStorageReader(inv ContractStorageInvoker, contract util.Uint160) *ContractStorageReader {
|
|
||||||
return &ContractStorageReader{
|
|
||||||
hash: contract,
|
|
||||||
invoker: inv,
|
|
||||||
contractReaderInterface: client.NewReader(inv, contract),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ContractStorageReader) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
|
|
||||||
return listChains(name, target, s.invoker.GetRPCInvoker(), s.hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
switch param.Type() {
|
|
||||||
case stackitem.BufferT, stackitem.ByteArrayT, stackitem.IntegerT:
|
|
||||||
return param.TryBytes()
|
|
||||||
case stackitem.AnyT:
|
|
||||||
if param.Value() == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("chain/client: %s is not a byte array type", param.Type())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func prefixedChainName(name chain.Name, chainID chain.ID) []byte {
|
|
||||||
return []byte(strings.ToLower(fmt.Sprintf("%s:%s", name, chainID)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func policyKind(typ engine.TargetType) (policy.Kind, error) {
|
|
||||||
switch typ {
|
|
||||||
case engine.Namespace:
|
|
||||||
return policy.Namespace, nil
|
|
||||||
case engine.Container:
|
|
||||||
return policy.Container, nil
|
|
||||||
case engine.User:
|
|
||||||
return policy.Kind(engine.User), nil
|
|
||||||
case engine.Group:
|
|
||||||
return policy.Kind(engine.Group), nil
|
|
||||||
default:
|
|
||||||
return policy.Kind(0), ErrEngineTargetTypeUnsupported
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package testutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
resourcepkg "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Resource struct {
|
|
||||||
name string
|
|
||||||
properties map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) Name() string {
|
|
||||||
return r.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) Property(name string) string {
|
|
||||||
return r.properties[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewResource(name string, properties map[string]string) *Resource {
|
|
||||||
if properties == nil {
|
|
||||||
properties = make(map[string]string)
|
|
||||||
}
|
|
||||||
return &Resource{name: name, properties: properties}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
operation string
|
|
||||||
properties map[string]string
|
|
||||||
resource *Resource
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ resourcepkg.Request = (*Request)(nil)
|
|
||||||
|
|
||||||
func (r *Request) Operation() string {
|
|
||||||
return r.operation
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Request) Resource() resourcepkg.Resource {
|
|
||||||
return r.resource
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Request) Property(name string) string {
|
|
||||||
return r.properties[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequest(op string, r *Resource, properties map[string]string) *Request {
|
|
||||||
return &Request{
|
|
||||||
operation: op,
|
|
||||||
properties: properties,
|
|
||||||
resource: r,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package resource
|
package policyengine
|
||||||
|
|
||||||
// Request represents generic named resource (bucket, container etc.).
|
// Request represents generic named resource (bucket, container etc.).
|
||||||
// Name is resource depenent but should be globally unique for any given
|
// Name is resource depenent but should be globally unique for any given
|
49
resource_test.go
Normal file
49
resource_test.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package policyengine
|
||||||
|
|
||||||
|
type resource struct {
|
||||||
|
name string
|
||||||
|
properties map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resource) Name() string {
|
||||||
|
return r.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resource) Property(name string) string {
|
||||||
|
return r.properties[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
func newResource(name string, properties map[string]string) *resource {
|
||||||
|
if properties == nil {
|
||||||
|
properties = make(map[string]string)
|
||||||
|
}
|
||||||
|
return &resource{name: name, properties: properties}
|
||||||
|
}
|
||||||
|
|
||||||
|
type request struct {
|
||||||
|
operation string
|
||||||
|
properties map[string]string
|
||||||
|
resource *resource
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Request = (*request)(nil)
|
||||||
|
|
||||||
|
func (r *request) Operation() string {
|
||||||
|
return r.operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) Resource() Resource {
|
||||||
|
return r.resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) Property(name string) string {
|
||||||
|
return r.properties[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRequest(op string, r *resource, properties map[string]string) *request {
|
||||||
|
return &request{
|
||||||
|
operation: op,
|
||||||
|
properties: properties,
|
||||||
|
resource: r,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
package common
|
|
||||||
|
|
||||||
const (
|
|
||||||
PropertyKeyFrostFSIDGroupID = "frostfsid:groupID"
|
|
||||||
|
|
||||||
PropertyKeyFrostFSSourceIP = "frostfs:sourceIP"
|
|
||||||
|
|
||||||
PropertyKeyFormatFrostFSIDUserClaim = "frostfsid:userClaim/%s"
|
|
||||||
|
|
||||||
PropertyKeyFrostFSXHeader = "frostfs:xheader/%s"
|
|
||||||
)
|
|
|
@ -1,60 +0,0 @@
|
||||||
package native
|
|
||||||
|
|
||||||
const (
|
|
||||||
MethodGetObject = "GetObject"
|
|
||||||
MethodPutObject = "PutObject"
|
|
||||||
MethodHeadObject = "HeadObject"
|
|
||||||
MethodDeleteObject = "DeleteObject"
|
|
||||||
MethodSearchObject = "SearchObject"
|
|
||||||
MethodRangeObject = "RangeObject"
|
|
||||||
MethodHashObject = "HashObject"
|
|
||||||
MethodPatchObject = "PatchObject"
|
|
||||||
|
|
||||||
MethodPutContainer = "PutContainer"
|
|
||||||
MethodDeleteContainer = "DeleteContainer"
|
|
||||||
MethodGetContainer = "GetContainer"
|
|
||||||
MethodListContainers = "ListContainers"
|
|
||||||
MethodSetContainerEACL = "SetContainerEACL"
|
|
||||||
MethodGetContainerEACL = "GetContainerEACL"
|
|
||||||
|
|
||||||
ObjectPrefix = "native:object"
|
|
||||||
ContainerPrefix = "native:container"
|
|
||||||
|
|
||||||
ResourceFormatNamespaceObjects = "native:object/%s/*"
|
|
||||||
ResourceFormatNamespaceContainerObjects = "native:object/%s/%s/*"
|
|
||||||
ResourceFormatNamespaceContainerObject = "native:object/%s/%s/%s"
|
|
||||||
|
|
||||||
ResourceFormatRootObjects = "native:object//*"
|
|
||||||
ResourceFormatRootContainerObjects = "native:object//%s/*"
|
|
||||||
ResourceFormatRootContainerObject = "native:object//%s/%s"
|
|
||||||
|
|
||||||
ResourceFormatAllObjects = "native:object/*"
|
|
||||||
|
|
||||||
ResourceFormatNamespaceContainer = "native:container/%s/%s"
|
|
||||||
ResourceFormatNamespaceContainers = "native:container/%s/*"
|
|
||||||
|
|
||||||
ResourceFormatRootContainer = "native:container//%s"
|
|
||||||
ResourceFormatRootContainers = "native:container//*"
|
|
||||||
|
|
||||||
ResourceFormatAllContainers = "native:container/*"
|
|
||||||
|
|
||||||
PropertyKeyActorPublicKey = "$Actor:publicKey"
|
|
||||||
PropertyKeyActorRole = "$Actor:role"
|
|
||||||
|
|
||||||
PropertyKeyObjectVersion = "$Object:version"
|
|
||||||
PropertyKeyObjectID = "$Object:objectID"
|
|
||||||
PropertyKeyObjectContainerID = "$Object:containerID"
|
|
||||||
PropertyKeyObjectOwnerID = "$Object:ownerID"
|
|
||||||
PropertyKeyObjectCreationEpoch = "$Object:creationEpoch"
|
|
||||||
PropertyKeyObjectPayloadLength = "$Object:payloadLength"
|
|
||||||
PropertyKeyObjectPayloadHash = "$Object:payloadHash"
|
|
||||||
PropertyKeyObjectType = "$Object:objectType"
|
|
||||||
PropertyKeyObjectHomomorphicHash = "$Object:homomorphicHash"
|
|
||||||
|
|
||||||
PropertyKeyContainerOwnerID = "$Container:ownerID"
|
|
||||||
|
|
||||||
PropertyValueContainerRoleOwner = "owner"
|
|
||||||
PropertyValueContainerRoleIR = "ir"
|
|
||||||
PropertyValueContainerRoleContainer = "container"
|
|
||||||
PropertyValueContainerRoleOthers = "others"
|
|
||||||
)
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package s3
|
|
||||||
|
|
||||||
const (
|
|
||||||
PropertyKeyOwner = "Owner"
|
|
||||||
|
|
||||||
PropertyKeyDelimiter = "s3:delimiter"
|
|
||||||
PropertyKeyPrefix = "s3:prefix"
|
|
||||||
PropertyKeyVersionID = "s3:VersionId"
|
|
||||||
PropertyKeyMaxKeys = "s3:max-keys"
|
|
||||||
|
|
||||||
PropertyKeyFormatResourceTag = "aws:ResourceTag/%s"
|
|
||||||
PropertyKeyFormatRequestTag = "aws:RequestTag/%s"
|
|
||||||
|
|
||||||
PropertyKeyAccessBoxAttrMFA = "AccessBox-Attribute/IAM-MFA"
|
|
||||||
PropertyKeyFormatAccessBoxAttr = "AccessBox-Attribute/%s"
|
|
||||||
|
|
||||||
ResourceFormatS3All = "arn:aws:s3:::*"
|
|
||||||
ResourceFormatS3Bucket = "arn:aws:s3:::%s"
|
|
||||||
ResourceFormatS3BucketObjects = "arn:aws:s3:::%s/*"
|
|
||||||
ResourceFormatS3BucketObject = "arn:aws:s3:::%s/%s"
|
|
||||||
|
|
||||||
ResourceFormatIAMNamespaceUser = "arn:aws:iam::%s:user/%s"
|
|
||||||
ResourceFormatIAMNamespaceGroup = "arn:aws:iam::%s:group/%s"
|
|
||||||
)
|
|
Loading…
Reference in a new issue