forked from TrueCloudLab/policy-engine
495 lines
13 KiB
Go
495 lines
13 KiB
Go
package iam
|
|
|
|
import (
|
|
"errors"
|
|
"strconv"
|
|
"testing"
|
|
|
|
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"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) {
|
|
namespace := "root"
|
|
userName := "JohnDoe"
|
|
user := namespace + "/" + userName
|
|
principal := "arn:aws:iam::" + namespace + ":user/" + userName
|
|
resource := "DOC-EXAMPLE-BUCKET/*"
|
|
action := "PutObject"
|
|
|
|
mockResolver := newMockUserResolver(t, []string{user})
|
|
|
|
t.Run("valid 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},
|
|
Conditions: map[string]Condition{
|
|
CondStringEquals: {
|
|
"s3:RequestObjectTag/Department": {"Finance"},
|
|
},
|
|
},
|
|
}},
|
|
}
|
|
|
|
expected := &policyengine.Chain{Rules: []policyengine.Rule{
|
|
{
|
|
Status: policyengine.Allow,
|
|
Actions: policyengine.Actions{Names: []string{action}},
|
|
Resources: policyengine.Resources{Names: []string{resource}},
|
|
Any: true,
|
|
Condition: []policyengine.Condition{
|
|
{
|
|
Op: policyengine.CondStringEquals,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: RequestOwnerProperty,
|
|
Value: mockResolver.users[user].Address(),
|
|
},
|
|
{
|
|
Op: policyengine.CondStringEquals,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "s3:RequestObjectTag/Department",
|
|
Value: "Finance",
|
|
},
|
|
},
|
|
},
|
|
}}
|
|
|
|
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: []string{"native:" + 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.Equal(t, expected, chain)
|
|
})
|
|
|
|
t.Run("valid inverted policy", func(t *testing.T) {
|
|
p := Policy{
|
|
Version: "2012-10-17",
|
|
Statement: []Statement{{
|
|
NotPrincipal: map[PrincipalType][]string{
|
|
AWSPrincipalType: {principal},
|
|
},
|
|
Effect: DenyEffect,
|
|
NotAction: []string{"s3:PutObject"},
|
|
NotResource: []string{"arn:aws:s3:::" + resource},
|
|
}},
|
|
}
|
|
|
|
expected := &policyengine.Chain{Rules: []policyengine.Rule{
|
|
{
|
|
Status: policyengine.AccessDenied,
|
|
Actions: policyengine.Actions{Inverted: true, Names: []string{action}},
|
|
Resources: policyengine.Resources{Inverted: true, Names: []string{resource}},
|
|
Any: true,
|
|
Condition: []policyengine.Condition{
|
|
{
|
|
Op: policyengine.CondStringNotEquals,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: RequestOwnerProperty,
|
|
Value: mockResolver.users[user].Address(),
|
|
},
|
|
},
|
|
},
|
|
}}
|
|
|
|
chain, err := p.ToChain(S3ChainType, mockResolver)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected, chain)
|
|
})
|
|
|
|
t.Run("valid policy map get action", func(t *testing.T) {
|
|
p := Policy{
|
|
Version: "2012-10-17",
|
|
Statement: []Statement{{
|
|
Principal: map[PrincipalType][]string{
|
|
AWSPrincipalType: {principal},
|
|
},
|
|
Effect: DenyEffect,
|
|
NotAction: []string{"s3:GetObject"},
|
|
NotResource: []string{"arn:aws:s3:::" + resource},
|
|
}},
|
|
}
|
|
|
|
expected := &policyengine.Chain{Rules: []policyengine.Rule{
|
|
{
|
|
Status: policyengine.AccessDenied,
|
|
Actions: policyengine.Actions{Inverted: true, Names: actionToOpMap["GetObject"]},
|
|
Resources: policyengine.Resources{Inverted: true, 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.Equal(t, expected, chain)
|
|
})
|
|
|
|
t.Run("invalid policy (unsupported principal type)", func(t *testing.T) {
|
|
p := Policy{
|
|
Version: "2012-10-17",
|
|
Statement: []Statement{{
|
|
Principal: map[PrincipalType][]string{
|
|
"dummy": {principal},
|
|
},
|
|
Effect: AllowEffect,
|
|
Action: []string{"s3:PutObject"},
|
|
Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
|
|
}},
|
|
}
|
|
|
|
_, err := p.ToChain(S3ChainType, mockResolver)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("invalid policy (missing resource)", func(t *testing.T) {
|
|
p := Policy{
|
|
Version: "2012-10-17",
|
|
Statement: []Statement{{
|
|
Principal: map[PrincipalType][]string{
|
|
AWSPrincipalType: {principal},
|
|
},
|
|
Effect: AllowEffect,
|
|
Action: []string{"s3:PutObject"},
|
|
}},
|
|
}
|
|
|
|
_, err := p.ToChain(S3ChainType, mockResolver)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("invalid policy (not applicable native actions)", func(t *testing.T) {
|
|
p := Policy{
|
|
Version: "2012-10-17",
|
|
Statement: []Statement{{
|
|
Principal: map[PrincipalType][]string{
|
|
AWSPrincipalType: {principal},
|
|
},
|
|
Effect: AllowEffect,
|
|
Action: []string{"s3:AbortMultipartUpload"},
|
|
Resource: []string{"arn:aws:s3:::" + resource},
|
|
}},
|
|
}
|
|
|
|
_, err := p.ToChain(NativeChainType, mockResolver)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("check policy conditions", func(t *testing.T) {
|
|
p := Policy{
|
|
Version: "2012-10-17",
|
|
Statement: []Statement{{
|
|
Principal: map[PrincipalType][]string{Wildcard: nil},
|
|
Effect: AllowEffect,
|
|
Action: []string{"s3:PutObject"},
|
|
Resource: []string{"arn:aws:s3:::" + resource},
|
|
Conditions: Conditions{
|
|
CondStringEquals: {"key1": {"val0", "val1"}},
|
|
CondStringNotEquals: {"key2": {"val2"}},
|
|
CondStringEqualsIgnoreCase: {"key3": {"val3"}},
|
|
CondStringNotEqualsIgnoreCase: {"key4": {"val4"}},
|
|
CondStringLike: {"key5": {"val5"}},
|
|
CondStringNotLike: {"key6": {"val6"}},
|
|
CondDateEquals: {"key7": {"2006-01-02T15:04:05+07:00"}},
|
|
CondDateNotEquals: {"key8": {"2006-01-02T15:04:05Z"}},
|
|
CondDateLessThan: {"key9": {"2006-01-02T15:04:05+06:00"}},
|
|
CondDateLessThanEquals: {"key10": {"2006-01-02T15:04:05+03:00"}},
|
|
CondDateGreaterThan: {"key11": {"2006-01-02T15:04:05-01:00"}},
|
|
CondDateGreaterThanEquals: {"key12": {"2006-01-02T15:04:05-03:00"}},
|
|
CondBool: {"key13": {"True"}},
|
|
CondIPAddress: {"key14": {"val14"}},
|
|
CondNotIPAddress: {"key15": {"val15"}},
|
|
CondArnEquals: {"key16": {"val16"}},
|
|
CondArnLike: {CondKeyAWSPrincipalARN: {principal}},
|
|
CondArnNotEquals: {"key18": {"val18"}},
|
|
CondArnNotLike: {"key19": {"val19"}},
|
|
},
|
|
}},
|
|
}
|
|
|
|
expected := &policyengine.Chain{Rules: []policyengine.Rule{
|
|
{
|
|
Status: policyengine.Allow,
|
|
Actions: policyengine.Actions{Names: []string{action}},
|
|
Resources: policyengine.Resources{Names: []string{resource}},
|
|
Any: true,
|
|
Condition: []policyengine.Condition{
|
|
{
|
|
Op: policyengine.CondStringLike,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: RequestOwnerProperty,
|
|
Value: "*",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringEquals,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key1",
|
|
Value: "val0",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringEquals,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key1",
|
|
Value: "val1",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringNotEquals,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key2",
|
|
Value: "val2",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringEqualsIgnoreCase,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key3",
|
|
Value: "val3",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringNotEqualsIgnoreCase,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key4",
|
|
Value: "val4",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringLike,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key5",
|
|
Value: "val5",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringNotLike,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key6",
|
|
Value: "val6",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringEquals,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key7",
|
|
Value: "1136189045",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringNotEquals,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key8",
|
|
Value: "1136214245",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringLessThan,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key9",
|
|
Value: "1136192645",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringLessThanEquals,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key10",
|
|
Value: "1136203445",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringGreaterThan,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key11",
|
|
Value: "1136217845",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringGreaterThanEquals,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key12",
|
|
Value: "1136225045",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringEqualsIgnoreCase,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key13",
|
|
Value: "True",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringLike,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key14",
|
|
Value: "val14",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringNotLike,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key15",
|
|
Value: "val15",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringEquals,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key16",
|
|
Value: "val16",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringLike,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: RequestOwnerProperty,
|
|
Value: mockResolver.users[user].Address(),
|
|
},
|
|
{
|
|
Op: policyengine.CondStringNotEquals,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key18",
|
|
Value: "val18",
|
|
},
|
|
{
|
|
Op: policyengine.CondStringNotLike,
|
|
Object: policyengine.ObjectRequest,
|
|
Key: "key19",
|
|
Value: "val19",
|
|
},
|
|
},
|
|
},
|
|
}}
|
|
|
|
chain, err := p.ToChain(S3ChainType, mockResolver)
|
|
require.NoError(t, err)
|
|
|
|
for i, rule := range chain.Rules {
|
|
expectedRule := expected.Rules[i]
|
|
require.Equal(t, expectedRule.Actions, rule.Actions)
|
|
require.Equal(t, expectedRule.Any, rule.Any)
|
|
require.Equal(t, expectedRule.Resources, rule.Resources)
|
|
require.Equal(t, expectedRule.Status, rule.Status)
|
|
require.ElementsMatch(t, expectedRule.Condition, rule.Condition)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestParsePrincipalARN(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)
|
|
})
|
|
}
|
|
}
|