forked from TrueCloudLab/policy-engine
[#XX] iam: Add converter to native policy
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
63ecf63a08
commit
0566a2b058
4 changed files with 333 additions and 36 deletions
11
go.mod
11
go.mod
|
@ -2,10 +2,19 @@ module git.frostfs.info/TrueCloudLab/policy-engine
|
||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require github.com/stretchr/testify v1.8.1
|
require (
|
||||||
|
github.com/nspcc-dev/neo-go v0.103.1
|
||||||
|
github.com/stretchr/testify v1.8.4
|
||||||
|
)
|
||||||
|
|
||||||
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/hashicorp/golang-lru v0.6.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
|
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
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
26
go.sum
26
go.sum
|
@ -1,17 +1,25 @@
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
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/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/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 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
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=
|
||||||
|
|
162
iam/converter.go
162
iam/converter.go
|
@ -1,16 +1,27 @@
|
||||||
package iam
|
package iam
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RequestOwnerProperty = "Owner"
|
RequestOwnerProperty = "Owner"
|
||||||
|
|
||||||
|
ResourceFullPathProperty = "FullPath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CondKeyAWSPrincipalARN = "aws:PrincipalArn"
|
||||||
|
CondKeyS3Delimiter = "s3:delimiter"
|
||||||
|
CondKeyS3Prefix = "s3:prefix"
|
||||||
|
CondKeyS3VersionID = "s3:VersionId"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -52,7 +63,30 @@ const (
|
||||||
CondArnNotLike string = "ArnNotLike"
|
CondArnNotLike string = "ArnNotLike"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p Policy) ToChain() (*policyengine.Chain, error) {
|
const (
|
||||||
|
arnIAMPrefix = "arn:aws:iam::"
|
||||||
|
s3ResourcePrefix = "arn:aws:s3:::"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalidPrincipalFormat occurs when principal has unknown/unsupported format.
|
||||||
|
var ErrInvalidPrincipalFormat = errors.New("invalid principal format")
|
||||||
|
|
||||||
|
type ChainType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
S3ChainType ChainType = "s3"
|
||||||
|
NativeChainType ChainType = "native"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserResolver interface {
|
||||||
|
GetUserKey(account, user string) (*keys.PublicKey, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Policy) ToChain(typ ChainType, resolver UserResolver) (*policyengine.Chain, error) {
|
||||||
|
if !isValidChainType(typ) {
|
||||||
|
return nil, fmt.Errorf("unknown chain type '%s'", typ)
|
||||||
|
}
|
||||||
|
|
||||||
if err := p.Validate(GeneralPolicyType); err != nil {
|
if err := p.Validate(GeneralPolicyType); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -72,8 +106,15 @@ func (p Policy) ToChain() (*policyengine.Chain, error) {
|
||||||
principals = []string{Wildcard}
|
principals = []string{Wildcard}
|
||||||
op = policyengine.CondStringLike
|
op = policyengine.CondStringLike
|
||||||
} else {
|
} else {
|
||||||
for _, principal := range statementPrincipal {
|
for principalType, principal := range statementPrincipal {
|
||||||
principals = append(principals, principal...)
|
if principalType != AWSPrincipalType {
|
||||||
|
return nil, fmt.Errorf("unsupported principal type '%s'", principalType)
|
||||||
|
}
|
||||||
|
parsedPrincipal, err := formPrincipal(principal, resolver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse principal '%s': %w", typ, err)
|
||||||
|
}
|
||||||
|
principals = append(principals, parsedPrincipal...)
|
||||||
}
|
}
|
||||||
|
|
||||||
op = policyengine.CondStringEquals
|
op = policyengine.CondStringEquals
|
||||||
|
@ -92,7 +133,7 @@ func (p Policy) ToChain() (*policyengine.Chain, error) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
conds, err := statement.Conditions.ToChainCondition()
|
conds, err := statement.Conditions.ToChainCondition(resolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -102,7 +143,9 @@ func (p Policy) ToChain() (*policyengine.Chain, error) {
|
||||||
ruleAction := policyengine.Actions{Inverted: actionInverted, Names: action}
|
ruleAction := policyengine.Actions{Inverted: actionInverted, Names: action}
|
||||||
|
|
||||||
resource, resourceInverted := statement.resource()
|
resource, resourceInverted := statement.resource()
|
||||||
ruleResource := policyengine.Resources{Inverted: resourceInverted, Names: resource}
|
names, extraConditions := formResourceNamesAndConditions(typ, resource)
|
||||||
|
ruleResource := policyengine.Resources{Inverted: resourceInverted, Names: names}
|
||||||
|
conditions = append(conditions, extraConditions...)
|
||||||
|
|
||||||
r := policyengine.Rule{
|
r := policyengine.Rule{
|
||||||
Status: status,
|
Status: status,
|
||||||
|
@ -118,7 +161,7 @@ func (p Policy) ToChain() (*policyengine.Chain, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:funlen
|
//nolint:funlen
|
||||||
func (c Conditions) ToChainCondition() ([]policyengine.Condition, error) {
|
func (c Conditions) ToChainCondition(resolver UserResolver) ([]policyengine.Condition, error) {
|
||||||
var conditions []policyengine.Condition
|
var conditions []policyengine.Condition
|
||||||
|
|
||||||
var convertValue convertFunction
|
var convertValue convertFunction
|
||||||
|
@ -203,6 +246,13 @@ func (c Conditions) ToChainCondition() ([]policyengine.Condition, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key == CondKeyAWSPrincipalARN {
|
||||||
|
if converted, err = formPrincipalOwner(converted, resolver); err != nil {
|
||||||
|
return nil, fmt.Errorf("handle %s: %w", CondKeyAWSPrincipalARN, err)
|
||||||
|
}
|
||||||
|
key = RequestOwnerProperty
|
||||||
|
}
|
||||||
|
|
||||||
conditions = append(conditions, policyengine.Condition{
|
conditions = append(conditions, policyengine.Condition{
|
||||||
Op: condType,
|
Op: condType,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: policyengine.ObjectRequest,
|
||||||
|
@ -234,3 +284,103 @@ func dateConvertFunction(val string) (string, error) {
|
||||||
|
|
||||||
return strconv.FormatInt(parsed.UTC().Unix(), 10), nil
|
return strconv.FormatInt(parsed.UTC().Unix(), 10), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isValidChainType(chainType ChainType) bool {
|
||||||
|
return chainType == S3ChainType || chainType == NativeChainType
|
||||||
|
}
|
||||||
|
|
||||||
|
func formPrincipal(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 parsePrincipalAsIAMUser(principal string) (string, string, 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 formResourceNamesAndConditions(chainType ChainType, names []string) ([]string, []policyengine.Condition) {
|
||||||
|
switch chainType {
|
||||||
|
case S3ChainType:
|
||||||
|
return formS3ResourceNamesAndConditions(names)
|
||||||
|
case NativeChainType:
|
||||||
|
return formNativeResourceNamesAndConditions(names)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("unknown chain type") // this must not ever happen
|
||||||
|
}
|
||||||
|
|
||||||
|
func formNativeResourceNamesAndConditions(names []string) ([]string, []policyengine.Condition) {
|
||||||
|
res := make([]string, len(names))
|
||||||
|
resCond := make([]policyengine.Condition, len(names))
|
||||||
|
|
||||||
|
for i := range names {
|
||||||
|
// we user ::: instead of :: because of node
|
||||||
|
// https://git.frostfs.info/TrueCloudLab/frostfs-node/src/commit/78cfb6aea86c01df34a534020dc63cefbad61da0/pkg/services/object/acl/ape_request.go#L42
|
||||||
|
res[i] = "native:::object/*"
|
||||||
|
|
||||||
|
resCond[i] = policyengine.Condition{
|
||||||
|
Op: policyengine.CondStringLike,
|
||||||
|
Object: policyengine.ObjectResource,
|
||||||
|
Key: ResourceFullPathProperty,
|
||||||
|
Value: strings.TrimPrefix(names[i], s3ResourcePrefix),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, resCond
|
||||||
|
}
|
||||||
|
|
||||||
|
func formS3ResourceNamesAndConditions(names []string) ([]string, []policyengine.Condition) {
|
||||||
|
res := make([]string, len(names))
|
||||||
|
for i := range names {
|
||||||
|
res[i] = strings.TrimPrefix(names[i], s3ResourcePrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,23 +1,58 @@
|
||||||
package iam
|
package iam
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type mockUserResolver struct {
|
||||||
|
users map[string]*keys.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockUserResolver(t *testing.T, accountUsers []string) *mockUserResolver {
|
||||||
|
m := make(map[string]*keys.PublicKey, len(accountUsers))
|
||||||
|
for _, user := range accountUsers {
|
||||||
|
key, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
m[user] = key.PublicKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &mockUserResolver{users: m}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockUserResolver) GetUserKey(account, user string) (*keys.PublicKey, error) {
|
||||||
|
key, ok := m.users[account+"/"+user]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestConverters(t *testing.T) {
|
func TestConverters(t *testing.T) {
|
||||||
|
namespace := "root"
|
||||||
|
userName := "JohnDoe"
|
||||||
|
user := namespace + "/" + userName
|
||||||
|
principal := "arn:aws:iam::" + namespace + ":user/" + userName
|
||||||
|
resource := "DOC-EXAMPLE-BUCKET/*"
|
||||||
|
|
||||||
|
mockResolver := newMockUserResolver(t, []string{user})
|
||||||
|
|
||||||
t.Run("valid policy", func(t *testing.T) {
|
t.Run("valid policy", func(t *testing.T) {
|
||||||
p := Policy{
|
p := Policy{
|
||||||
Version: "2012-10-17",
|
Version: "2012-10-17",
|
||||||
Statement: []Statement{{
|
Statement: []Statement{{
|
||||||
Principal: map[PrincipalType][]string{
|
Principal: map[PrincipalType][]string{
|
||||||
AWSPrincipalType: {"arn:aws:iam::111122223333:user/JohnDoe"},
|
AWSPrincipalType: {principal},
|
||||||
},
|
},
|
||||||
Effect: AllowEffect,
|
Effect: AllowEffect,
|
||||||
Action: []string{"s3:PutObject"},
|
Action: []string{"s3:PutObject"},
|
||||||
Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
|
Resource: []string{"arn:aws:s3:::" + resource},
|
||||||
Conditions: map[string]Condition{
|
Conditions: map[string]Condition{
|
||||||
CondStringEquals: {
|
CondStringEquals: {
|
||||||
"s3:RequestObjectTag/Department": {"Finance"},
|
"s3:RequestObjectTag/Department": {"Finance"},
|
||||||
|
@ -30,14 +65,14 @@ func TestConverters(t *testing.T) {
|
||||||
{
|
{
|
||||||
Status: policyengine.Allow,
|
Status: policyengine.Allow,
|
||||||
Actions: policyengine.Actions{Names: p.Statement[0].Action},
|
Actions: policyengine.Actions{Names: p.Statement[0].Action},
|
||||||
Resources: policyengine.Resources{Names: p.Statement[0].Resource},
|
Resources: policyengine.Resources{Names: []string{resource}},
|
||||||
Any: true,
|
Any: true,
|
||||||
Condition: []policyengine.Condition{
|
Condition: []policyengine.Condition{
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringEquals,
|
Op: policyengine.CondStringEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: policyengine.ObjectRequest,
|
||||||
Key: RequestOwnerProperty,
|
Key: RequestOwnerProperty,
|
||||||
Value: "arn:aws:iam::111122223333:user/JohnDoe",
|
Value: mockResolver.users[user].Address(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringEquals,
|
Op: policyengine.CondStringEquals,
|
||||||
|
@ -49,7 +84,48 @@ func TestConverters(t *testing.T) {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
chain, err := p.ToChain()
|
chain, err := p.ToChain(S3ChainType, mockResolver)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, chain)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid native policy", func(t *testing.T) {
|
||||||
|
p := Policy{
|
||||||
|
Version: "2012-10-17",
|
||||||
|
Statement: []Statement{{
|
||||||
|
Principal: map[PrincipalType][]string{
|
||||||
|
AWSPrincipalType: {principal},
|
||||||
|
},
|
||||||
|
Effect: AllowEffect,
|
||||||
|
Action: []string{"s3:PutObject"},
|
||||||
|
Resource: []string{"arn:aws:s3:::" + resource},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &policyengine.Chain{Rules: []policyengine.Rule{
|
||||||
|
{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Actions: policyengine.Actions{Names: p.Statement[0].Action},
|
||||||
|
Resources: policyengine.Resources{Names: []string{"native:::object/*"}},
|
||||||
|
Any: true,
|
||||||
|
Condition: []policyengine.Condition{
|
||||||
|
{
|
||||||
|
Op: policyengine.CondStringEquals,
|
||||||
|
Object: policyengine.ObjectRequest,
|
||||||
|
Key: RequestOwnerProperty,
|
||||||
|
Value: mockResolver.users[user].Address(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Op: policyengine.CondStringLike,
|
||||||
|
Object: policyengine.ObjectResource,
|
||||||
|
Key: ResourceFullPathProperty,
|
||||||
|
Value: resource,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
chain, err := p.ToChain(NativeChainType, mockResolver)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expected, chain)
|
require.Equal(t, expected, chain)
|
||||||
})
|
})
|
||||||
|
@ -59,11 +135,11 @@ func TestConverters(t *testing.T) {
|
||||||
Version: "2012-10-17",
|
Version: "2012-10-17",
|
||||||
Statement: []Statement{{
|
Statement: []Statement{{
|
||||||
NotPrincipal: map[PrincipalType][]string{
|
NotPrincipal: map[PrincipalType][]string{
|
||||||
AWSPrincipalType: {"arn:aws:iam::111122223333:user/JohnDoe"},
|
AWSPrincipalType: {principal},
|
||||||
},
|
},
|
||||||
Effect: DenyEffect,
|
Effect: DenyEffect,
|
||||||
NotAction: []string{"s3:PutObject"},
|
NotAction: []string{"s3:PutObject"},
|
||||||
NotResource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
|
NotResource: []string{"arn:aws:s3:::" + resource},
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,20 +147,20 @@ func TestConverters(t *testing.T) {
|
||||||
{
|
{
|
||||||
Status: policyengine.AccessDenied,
|
Status: policyengine.AccessDenied,
|
||||||
Actions: policyengine.Actions{Inverted: true, Names: p.Statement[0].NotAction},
|
Actions: policyengine.Actions{Inverted: true, Names: p.Statement[0].NotAction},
|
||||||
Resources: policyengine.Resources{Inverted: true, Names: p.Statement[0].NotResource},
|
Resources: policyengine.Resources{Inverted: true, Names: []string{resource}},
|
||||||
Any: true,
|
Any: true,
|
||||||
Condition: []policyengine.Condition{
|
Condition: []policyengine.Condition{
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringNotEquals,
|
Op: policyengine.CondStringNotEquals,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: policyengine.ObjectRequest,
|
||||||
Key: RequestOwnerProperty,
|
Key: RequestOwnerProperty,
|
||||||
Value: "arn:aws:iam::111122223333:user/JohnDoe",
|
Value: mockResolver.users[user].Address(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
chain, err := p.ToChain()
|
chain, err := p.ToChain(S3ChainType, mockResolver)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expected, chain)
|
require.Equal(t, expected, chain)
|
||||||
})
|
})
|
||||||
|
@ -94,7 +170,7 @@ func TestConverters(t *testing.T) {
|
||||||
Version: "2012-10-17",
|
Version: "2012-10-17",
|
||||||
Statement: []Statement{{
|
Statement: []Statement{{
|
||||||
Principal: map[PrincipalType][]string{
|
Principal: map[PrincipalType][]string{
|
||||||
"dummy": {"arn:aws:iam::111122223333:user/JohnDoe"},
|
"dummy": {principal},
|
||||||
},
|
},
|
||||||
Effect: AllowEffect,
|
Effect: AllowEffect,
|
||||||
Action: []string{"s3:PutObject"},
|
Action: []string{"s3:PutObject"},
|
||||||
|
@ -102,7 +178,7 @@ func TestConverters(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := p.ToChain()
|
_, err := p.ToChain(S3ChainType, mockResolver)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -111,14 +187,14 @@ func TestConverters(t *testing.T) {
|
||||||
Version: "2012-10-17",
|
Version: "2012-10-17",
|
||||||
Statement: []Statement{{
|
Statement: []Statement{{
|
||||||
Principal: map[PrincipalType][]string{
|
Principal: map[PrincipalType][]string{
|
||||||
AWSPrincipalType: {"arn:aws:iam::111122223333:user/JohnDoe"},
|
AWSPrincipalType: {principal},
|
||||||
},
|
},
|
||||||
Effect: AllowEffect,
|
Effect: AllowEffect,
|
||||||
Action: []string{"s3:PutObject"},
|
Action: []string{"s3:PutObject"},
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := p.ToChain()
|
_, err := p.ToChain(S3ChainType, mockResolver)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -129,7 +205,7 @@ func TestConverters(t *testing.T) {
|
||||||
Principal: map[PrincipalType][]string{Wildcard: nil},
|
Principal: map[PrincipalType][]string{Wildcard: nil},
|
||||||
Effect: AllowEffect,
|
Effect: AllowEffect,
|
||||||
Action: []string{"s3:PutObject"},
|
Action: []string{"s3:PutObject"},
|
||||||
Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
|
Resource: []string{"arn:aws:s3:::" + resource},
|
||||||
Conditions: Conditions{
|
Conditions: Conditions{
|
||||||
CondStringEquals: {"key1": {"val0", "val1"}},
|
CondStringEquals: {"key1": {"val0", "val1"}},
|
||||||
CondStringNotEquals: {"key2": {"val2"}},
|
CondStringNotEquals: {"key2": {"val2"}},
|
||||||
|
@ -147,7 +223,7 @@ func TestConverters(t *testing.T) {
|
||||||
CondIPAddress: {"key14": {"val14"}},
|
CondIPAddress: {"key14": {"val14"}},
|
||||||
CondNotIPAddress: {"key15": {"val15"}},
|
CondNotIPAddress: {"key15": {"val15"}},
|
||||||
CondArnEquals: {"key16": {"val16"}},
|
CondArnEquals: {"key16": {"val16"}},
|
||||||
CondArnLike: {"key17": {"val17"}},
|
CondArnLike: {CondKeyAWSPrincipalARN: {principal}},
|
||||||
CondArnNotEquals: {"key18": {"val18"}},
|
CondArnNotEquals: {"key18": {"val18"}},
|
||||||
CondArnNotLike: {"key19": {"val19"}},
|
CondArnNotLike: {"key19": {"val19"}},
|
||||||
},
|
},
|
||||||
|
@ -158,7 +234,7 @@ func TestConverters(t *testing.T) {
|
||||||
{
|
{
|
||||||
Status: policyengine.Allow,
|
Status: policyengine.Allow,
|
||||||
Actions: policyengine.Actions{Names: p.Statement[0].Action},
|
Actions: policyengine.Actions{Names: p.Statement[0].Action},
|
||||||
Resources: policyengine.Resources{Names: p.Statement[0].Resource},
|
Resources: policyengine.Resources{Names: []string{resource}},
|
||||||
Any: true,
|
Any: true,
|
||||||
Condition: []policyengine.Condition{
|
Condition: []policyengine.Condition{
|
||||||
{
|
{
|
||||||
|
@ -272,8 +348,8 @@ func TestConverters(t *testing.T) {
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringLike,
|
Op: policyengine.CondStringLike,
|
||||||
Object: policyengine.ObjectRequest,
|
Object: policyengine.ObjectRequest,
|
||||||
Key: "key17",
|
Key: RequestOwnerProperty,
|
||||||
Value: "val17",
|
Value: mockResolver.users[user].Address(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringNotEquals,
|
Op: policyengine.CondStringNotEquals,
|
||||||
|
@ -291,7 +367,7 @@ func TestConverters(t *testing.T) {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
chain, err := p.ToChain()
|
chain, err := p.ToChain(S3ChainType, mockResolver)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for i, rule := range chain.Rules {
|
for i, rule := range chain.Rules {
|
||||||
|
@ -304,3 +380,57 @@ func TestConverters(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestName(t *testing.T) {
|
||||||
|
for i, tc := range []struct {
|
||||||
|
principal string
|
||||||
|
account string
|
||||||
|
user string
|
||||||
|
error bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
principal: "arn:aws:iam::root:user/user",
|
||||||
|
account: "root",
|
||||||
|
user: "user",
|
||||||
|
error: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
principal: "arn:aws:iam::root:user/path/user/user2",
|
||||||
|
account: "root",
|
||||||
|
user: "user2",
|
||||||
|
error: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
principal: "arn:aws:iam::root:user/",
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
principal: "root:user/name",
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
principal: "arn:aws:iam::root:user",
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
principal: "arn:aws:iam::root:name",
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
principal: "arn:aws:iam::root:user/path/user/",
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
|
account, user, err := parsePrincipalAsIAMUser(tc.principal)
|
||||||
|
if tc.error {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.account, account)
|
||||||
|
require.Equal(t, tc.user, user)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue