forked from TrueCloudLab/policy-engine
[#17] iam: Add converter to native/s3 policy
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
5db67021e1
commit
c9d4d15db6
9 changed files with 1683 additions and 346 deletions
16
go.mod
16
go.mod
|
@ -2,10 +2,24 @@ module git.frostfs.info/TrueCloudLab/policy-engine
|
|||
|
||||
go 1.20
|
||||
|
||||
require github.com/stretchr/testify v1.8.1
|
||||
require (
|
||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20231114081800-3787477133f3
|
||||
github.com/nspcc-dev/neo-go v0.103.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
)
|
||||
|
||||
require (
|
||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230802075510-964c3edb3f44 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/hashicorp/golang-lru v0.6.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/nspcc-dev/rfc6979 v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
43
go.sum
43
go.sum
|
@ -1,17 +1,40 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230802075510-964c3edb3f44 h1:v6JqBD/VzZx3QSxbaXnUwnnJ1KEYheU4LzLGr3IhsAE=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230802075510-964c3edb3f44/go.mod h1:pKJJRLOChW4zDQsAt1e8k/snWKljJtpkiPfxV53ngjI=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20231114081800-3787477133f3 h1:Qa35bB58plMb14LIsYzu2ibeYfsY2taFnbZytv9Ao3M=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20231114081800-3787477133f3/go.mod h1:t1akKcUH7iBrFHX8rSXScYMP17k2kYQXMbZooiL5Juw=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/neo-go v0.103.1 h1:BfRBceHUu8jSc1KQy7CzmQ/pa+xzAmgcyteGf0/IGgM=
|
||||
github.com/nspcc-dev/neo-go v0.103.1/go.mod h1:MD7MPiyshUwrE5n1/LzxeandbItaa/iLW/bJb6gNs/U=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
338
iam/converter.go
338
iam/converter.go
|
@ -1,17 +1,18 @@
|
|||
package iam
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
)
|
||||
|
||||
const (
|
||||
RequestOwnerProperty = "Owner"
|
||||
)
|
||||
const condKeyAWSPrincipalARN = "aws:PrincipalArn"
|
||||
|
||||
const (
|
||||
// String condition operators.
|
||||
|
@ -52,168 +53,157 @@ const (
|
|||
CondArnNotLike string = "ArnNotLike"
|
||||
)
|
||||
|
||||
func (p Policy) ToChain() (*chain.Chain, error) {
|
||||
if err := p.Validate(GeneralPolicyType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
const (
|
||||
arnIAMPrefix = "arn:aws:iam::"
|
||||
s3ResourcePrefix = "arn:aws:s3:::"
|
||||
s3ActionPrefix = "s3:"
|
||||
)
|
||||
|
||||
var ch chain.Chain
|
||||
var (
|
||||
// ErrInvalidPrincipalFormat occurs when principal has unknown/unsupported format.
|
||||
ErrInvalidPrincipalFormat = errors.New("invalid principal format")
|
||||
|
||||
for _, statement := range p.Statement {
|
||||
status := chain.AccessDenied
|
||||
if statement.Effect == AllowEffect {
|
||||
status = chain.Allow
|
||||
}
|
||||
// ErrInvalidResourceFormat occurs when resource has unknown/unsupported format.
|
||||
ErrInvalidResourceFormat = errors.New("invalid resource format")
|
||||
)
|
||||
|
||||
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 _, principal := range statementPrincipal {
|
||||
principals = append(principals, principal...)
|
||||
}
|
||||
type UserResolver interface {
|
||||
GetUserKey(account, user string) (*keys.PublicKey, error)
|
||||
}
|
||||
|
||||
op = chain.CondStringEquals
|
||||
if inverted {
|
||||
op = chain.CondStringNotEquals
|
||||
}
|
||||
}
|
||||
type BucketResolver interface {
|
||||
GetBucketCID(bucket string) (cid.ID, error)
|
||||
}
|
||||
|
||||
var conditions []chain.Condition
|
||||
for _, principal := range principals {
|
||||
conditions = append(conditions, chain.Condition{
|
||||
Op: op,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: RequestOwnerProperty,
|
||||
Value: principal,
|
||||
})
|
||||
}
|
||||
type Resolver interface {
|
||||
UserResolver
|
||||
BucketResolver
|
||||
}
|
||||
|
||||
conds, err := statement.Conditions.ToChainCondition()
|
||||
type formPrincipalConditionFunc func(string) chain.Condition
|
||||
|
||||
type transformConditionFunc func(gr GroupedConditions) (GroupedConditions, error)
|
||||
|
||||
func convertToChainConditions(c Conditions, transformer transformConditionFunc) ([]GroupedConditions, error) {
|
||||
conditions, err := convertToChainCondition(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conditions = append(conditions, conds...)
|
||||
|
||||
action, actionInverted := statement.action()
|
||||
ruleAction := chain.Actions{Inverted: actionInverted, Names: action}
|
||||
|
||||
resource, resourceInverted := statement.resource()
|
||||
ruleResource := chain.Resources{Inverted: resourceInverted, Names: resource}
|
||||
|
||||
r := chain.Rule{
|
||||
Status: status,
|
||||
Actions: ruleAction,
|
||||
Resources: ruleResource,
|
||||
Any: true,
|
||||
Condition: conditions,
|
||||
for i := range conditions {
|
||||
if conditions[i], err = transformer(conditions[i]); err != nil {
|
||||
return nil, fmt.Errorf("transform condition: %w", err)
|
||||
}
|
||||
ch.Rules = append(ch.Rules, r)
|
||||
}
|
||||
|
||||
return &ch, nil
|
||||
return conditions, nil
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func (c Conditions) ToChainCondition() ([]chain.Condition, error) {
|
||||
var conditions []chain.Condition
|
||||
type GroupedConditions struct {
|
||||
Conditions []chain.Condition
|
||||
Any bool
|
||||
}
|
||||
|
||||
var convertValue convertFunction
|
||||
func convertToChainCondition(c Conditions) ([]GroupedConditions, error) {
|
||||
var grouped []GroupedConditions
|
||||
|
||||
for op, KVs := range c {
|
||||
var condType chain.ConditionType
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(op, "String"):
|
||||
convertValue = noConvertFunction
|
||||
switch op {
|
||||
case CondStringEquals:
|
||||
condType = chain.CondStringEquals
|
||||
case CondStringNotEquals:
|
||||
condType = chain.CondStringNotEquals
|
||||
case CondStringEqualsIgnoreCase:
|
||||
condType = chain.CondStringEqualsIgnoreCase
|
||||
case CondStringNotEqualsIgnoreCase:
|
||||
condType = chain.CondStringNotEqualsIgnoreCase
|
||||
case CondStringLike:
|
||||
condType = chain.CondStringLike
|
||||
case CondStringNotLike:
|
||||
condType = chain.CondStringNotLike
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||
}
|
||||
case strings.HasPrefix(op, "Arn"):
|
||||
convertValue = noConvertFunction
|
||||
switch op {
|
||||
case CondArnEquals:
|
||||
condType = chain.CondStringEquals
|
||||
case CondArnNotEquals:
|
||||
condType = chain.CondStringNotEquals
|
||||
case CondArnLike:
|
||||
condType = chain.CondStringLike
|
||||
case CondArnNotLike:
|
||||
condType = chain.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 = chain.CondStringEquals
|
||||
case CondDateNotEquals:
|
||||
condType = chain.CondStringNotEquals
|
||||
case CondDateLessThan:
|
||||
condType = chain.CondStringLessThan
|
||||
case CondDateLessThanEquals:
|
||||
condType = chain.CondStringLessThanEquals
|
||||
case CondDateGreaterThan:
|
||||
condType = chain.CondStringGreaterThan
|
||||
case CondDateGreaterThanEquals:
|
||||
condType = chain.CondStringGreaterThanEquals
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||
}
|
||||
case op == CondBool:
|
||||
convertValue = noConvertFunction
|
||||
condType = chain.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 = chain.CondStringLike
|
||||
case op == CondNotIPAddress:
|
||||
convertValue = noConvertFunction
|
||||
condType = chain.CondStringNotLike
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||
condType, convertValue, err := getConditionTypeAndConverter(op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, values := range KVs {
|
||||
for _, val := range values {
|
||||
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
|
||||
}
|
||||
|
||||
conditions = append(conditions, chain.Condition{
|
||||
group.Conditions[i] = chain.Condition{
|
||||
Op: condType,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: key,
|
||||
Value: converted,
|
||||
})
|
||||
}
|
||||
}
|
||||
grouped = append(grouped, group)
|
||||
}
|
||||
}
|
||||
|
||||
return conditions, nil
|
||||
return grouped, nil
|
||||
}
|
||||
|
||||
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"):
|
||||
// TODO
|
||||
return 0, nil, fmt.Errorf("currently nummeric conditions unsupported: '%s'", 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:
|
||||
// todo consider using converters
|
||||
// "203.0.113.0/24" -> "203.0.113.*",
|
||||
// "2001:DB8:1234:5678::/64" -> "2001:DB8:1234:5678:*"
|
||||
// or having specific condition type for IP
|
||||
return chain.CondStringLike, noConvertFunction, nil
|
||||
case op == CondNotIPAddress:
|
||||
return chain.CondStringNotLike, noConvertFunction, nil
|
||||
default:
|
||||
return 0, nil, fmt.Errorf("unsupported condition operator: '%s'", op)
|
||||
}
|
||||
}
|
||||
|
||||
type convertFunction func(string) (string, error)
|
||||
|
@ -234,3 +224,89 @@ func dateConvertFunction(val string) (string, error) {
|
|||
|
||||
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 parseResourceAsS3ARN(resource string) (bucket string, object string, err error) {
|
||||
if !strings.HasPrefix(resource, s3ResourcePrefix) {
|
||||
return "", "", ErrInvalidResourceFormat
|
||||
}
|
||||
|
||||
// iam arn format arn:aws:s3:::<bucket-name>/<object-name>
|
||||
s3Resource := strings.TrimPrefix(resource, s3ResourcePrefix)
|
||||
sepIndex := strings.Index(s3Resource, "/")
|
||||
if sepIndex < 0 {
|
||||
return s3Resource, Wildcard, nil
|
||||
}
|
||||
|
||||
bucket = s3Resource[:sepIndex]
|
||||
object = s3Resource[sepIndex+1:]
|
||||
if len(object) == 0 {
|
||||
return bucket, Wildcard, nil
|
||||
}
|
||||
|
||||
if bucket == Wildcard && object != Wildcard {
|
||||
return "", "", ErrInvalidResourceFormat
|
||||
}
|
||||
|
||||
return bucket, object, nil
|
||||
}
|
||||
|
||||
func 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
|
||||
}
|
||||
|
|
236
iam/converter_native.go
Normal file
236
iam/converter_native.go
Normal file
|
@ -0,0 +1,236 @@
|
|||
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"
|
||||
|
||||
// ErrActionsNotApplicable occurs when failed to convert any actions.
|
||||
var ErrActionsNotApplicable = errors.New("actions not applicable")
|
||||
|
||||
var actionToOpMap = map[string][]string{
|
||||
supportedS3ActionDeleteObject: {native.MethodDeleteObject},
|
||||
supportedS3ActionGetObject: {native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
|
||||
supportedS3ActionHeadObject: {native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
|
||||
supportedS3ActionPutObject: {native.MethodPutObject},
|
||||
supportedS3ActionListBucket: {native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
|
||||
}
|
||||
|
||||
const (
|
||||
supportedS3ActionDeleteObject = "DeleteObject"
|
||||
supportedS3ActionGetObject = "GetObject"
|
||||
supportedS3ActionHeadObject = "HeadObject"
|
||||
supportedS3ActionPutObject = "PutObject"
|
||||
supportedS3ActionListBucket = "ListBucket"
|
||||
)
|
||||
|
||||
func ConvertToNativeChain(p Policy, resolver Resolver) (*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)
|
||||
|
||||
action, actionInverted := statement.action()
|
||||
ruleAction := chain.Actions{Inverted: actionInverted, Names: formNativeActionNames(action)}
|
||||
if len(ruleAction.Names) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
resource, resourceInverted := statement.resource()
|
||||
groupedResources, err := formNativeResourceNamesAndConditions(resource, resolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groupedConditions, err := convertToNativeChainCondition(statement.Conditions, resolver)
|
||||
if err != nil {
|
||||
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 {
|
||||
ruleConditions := append([]chain.Condition{principalCondFn(principal)}, 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 getNativePrincipalsAndConditionFunc(statement Statement, resolver UserResolver) ([]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,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: native.PropertyKeyActorPublicKey,
|
||||
Value: principal,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func convertToNativeChainCondition(c Conditions, resolver UserResolver) ([]GroupedConditions, error) {
|
||||
return convertToChainConditions(c, func(gr GroupedConditions) (GroupedConditions, error) {
|
||||
for i := range gr.Conditions {
|
||||
if gr.Conditions[i].Key == condKeyAWSPrincipalARN {
|
||||
gr.Conditions[i].Key = native.PropertyKeyActorPublicKey
|
||||
val, err := formPrincipalKey(gr.Conditions[i].Value, resolver)
|
||||
if err != nil {
|
||||
return GroupedConditions{}, err
|
||||
}
|
||||
gr.Conditions[i].Value = val
|
||||
}
|
||||
}
|
||||
|
||||
return gr, nil
|
||||
})
|
||||
}
|
||||
|
||||
type GroupedResources struct {
|
||||
Names []string
|
||||
Conditions []chain.Condition
|
||||
}
|
||||
|
||||
func formNativeResourceNamesAndConditions(names []string, resolver BucketResolver) ([]GroupedResources, error) {
|
||||
res := make([]GroupedResources, 0, len(names))
|
||||
|
||||
var combined []string
|
||||
|
||||
for i := range names {
|
||||
bkt, obj, err := parseResourceAsS3ARN(names[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bkt == Wildcard {
|
||||
res = res[:0]
|
||||
return append(res, GroupedResources{Names: []string{native.ResourceFormatAllObjects}}), nil
|
||||
}
|
||||
|
||||
cnrID, err := resolver.GetBucketCID(bkt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resource := fmt.Sprintf(native.ResourceFormatRootContainerObjects, cnrID.EncodeToString())
|
||||
|
||||
if obj == Wildcard {
|
||||
combined = append(combined, resource)
|
||||
continue
|
||||
}
|
||||
|
||||
res = append(res, GroupedResources{
|
||||
Names: []string{resource},
|
||||
Conditions: []chain.Condition{
|
||||
{
|
||||
Op: chain.CondStringLike,
|
||||
Object: chain.ObjectResource,
|
||||
Key: PropertyKeyFilePath,
|
||||
Value: obj,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if len(combined) != 0 {
|
||||
res = append(res, GroupedResources{Names: combined})
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func formNativePrincipal(principal []string, resolver UserResolver) ([]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 UserResolver) (string, error) {
|
||||
account, user, err := parsePrincipalAsIAMUser(principal)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
key, err := resolver.GetUserKey(account, user)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolve user: %w", err)
|
||||
}
|
||||
|
||||
return string(key.Bytes()), nil
|
||||
}
|
||||
|
||||
func formNativeActionNames(names []string) []string {
|
||||
res := make([]string, 0, len(names))
|
||||
|
||||
for i := range names {
|
||||
trimmed := strings.TrimPrefix(names[i], s3ActionPrefix)
|
||||
if trimmed == Wildcard {
|
||||
return []string{Wildcard}
|
||||
}
|
||||
res = append(res, actionToOpMap[trimmed]...)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
149
iam/converter_s3.go
Normal file
149
iam/converter_s3.go
Normal file
|
@ -0,0 +1,149 @@
|
|||
package iam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/schema/s3"
|
||||
)
|
||||
|
||||
func ConvertToS3Chain(p Policy, resolver UserResolver) (*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)
|
||||
|
||||
action, actionInverted := statement.action()
|
||||
ruleAction := chain.Actions{Inverted: actionInverted, Names: formS3ActionNames(action)}
|
||||
|
||||
resource, resourceInverted := statement.resource()
|
||||
ruleResource := chain.Resources{Inverted: resourceInverted, Names: formS3ResourceNamesAndConditions(resource)}
|
||||
|
||||
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 {
|
||||
r := chain.Rule{
|
||||
Status: status,
|
||||
Actions: ruleAction,
|
||||
Resources: ruleResource,
|
||||
Condition: append([]chain.Condition{principalCondFn(principal)}, conditions...),
|
||||
}
|
||||
engineChain.Rules = append(engineChain.Rules, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &engineChain, nil
|
||||
}
|
||||
|
||||
func getS3PrincipalsAndConditionFunc(statement Statement, resolver UserResolver) ([]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,
|
||||
Object: chain.ObjectRequest,
|
||||
Key: s3.PropertyKeyOwner,
|
||||
Value: principal,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func convertToS3ChainCondition(c Conditions, resolver UserResolver) ([]GroupedConditions, error) {
|
||||
return convertToChainConditions(c, func(gr GroupedConditions) (GroupedConditions, error) {
|
||||
for i := range gr.Conditions {
|
||||
if gr.Conditions[i].Key == condKeyAWSPrincipalARN {
|
||||
gr.Conditions[i].Key = s3.PropertyKeyOwner
|
||||
val, err := formPrincipalOwner(gr.Conditions[i].Value, resolver)
|
||||
if err != nil {
|
||||
return GroupedConditions{}, err
|
||||
}
|
||||
gr.Conditions[i].Value = val
|
||||
}
|
||||
}
|
||||
|
||||
return gr, nil
|
||||
})
|
||||
}
|
||||
|
||||
func formS3Principal(principal []string, resolver UserResolver) ([]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 UserResolver) (string, error) {
|
||||
account, user, err := parsePrincipalAsIAMUser(principal)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
key, err := resolver.GetUserKey(account, user)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolve user: %w", err)
|
||||
}
|
||||
|
||||
return key.Address(), nil
|
||||
}
|
||||
|
||||
func formS3ResourceNamesAndConditions(names []string) []string {
|
||||
res := make([]string, len(names))
|
||||
for i := range names {
|
||||
res[i] = strings.TrimPrefix(names[i], s3ResourcePrefix)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func formS3ActionNames(names []string) []string {
|
||||
res := make([]string, len(names))
|
||||
for i := range names {
|
||||
res[i] = strings.TrimPrefix(names[i], s3ActionPrefix)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -222,6 +222,10 @@ func (p Policy) Validate(typ PolicyType) error {
|
|||
}
|
||||
|
||||
func (p Policy) validate() error {
|
||||
if len(p.Statement) == 0 {
|
||||
return errors.New("'Statement' is missing")
|
||||
}
|
||||
|
||||
for _, statement := range p.Statement {
|
||||
if !statement.Effect.IsValid() {
|
||||
return fmt.Errorf("unknown effect: '%s'", statement.Effect)
|
||||
|
|
|
@ -320,6 +320,12 @@ func TestValidatePolicies(t *testing.T) {
|
|||
typ: GeneralPolicyType,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
name: "missing statement block",
|
||||
policy: Policy{},
|
||||
typ: GeneralPolicyType,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
name: "identity based valid",
|
||||
policy: Policy{
|
||||
|
|
9
schema/s3/consts.go
Normal file
9
schema/s3/consts.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package s3
|
||||
|
||||
const (
|
||||
PropertyKeyOwner = "Owner"
|
||||
|
||||
PropertyKeyDelimiter = "s3:delimiter"
|
||||
PropertyKeyPrefix = "s3:prefix"
|
||||
PropertyKeyVersionID = "s3:VersionId"
|
||||
)
|
Loading…
Reference in a new issue