forked from TrueCloudLab/policy-engine
[#4] Add IAM policy unmarshaler
This commit is contained in:
parent
5ebb2e694c
commit
88cf807951
5 changed files with 847 additions and 30 deletions
227
iam/converter.go
Normal file
227
iam/converter.go
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
package iam
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FrostFSPrincipal = "FrostFS"
|
||||||
|
|
||||||
|
RequestOwnerProperty = "Owner"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// String condition operators.
|
||||||
|
CondStringEquals string = "StringEquals"
|
||||||
|
CondStringNotEquals string = "StringNotEquals"
|
||||||
|
CondStringEqualsIgnoreCase string = "StringEqualsIgnoreCase"
|
||||||
|
CondStringNotEqualsIgnoreCase string = "StringNotEqualsIgnoreCase"
|
||||||
|
CondStringLike string = "StringLike"
|
||||||
|
CondStringNotLike string = "StringNotLike"
|
||||||
|
|
||||||
|
// Numeric condition operators.
|
||||||
|
CondNumericEquals string = "NumericEquals"
|
||||||
|
CondNumericNotEquals string = "NumericNotEquals"
|
||||||
|
CondNumericLessThan string = "NumericLessThan"
|
||||||
|
CondNumericLessThanEquals string = "NumericLessThanEquals"
|
||||||
|
CondNumericGreaterThan string = "NumericGreaterThan"
|
||||||
|
CondNumericGreaterThanEquals string = "NumericGreaterThanEquals"
|
||||||
|
|
||||||
|
// Date condition operators.
|
||||||
|
CondDateEquals string = "DateEquals"
|
||||||
|
CondDateNotEquals string = "DateNotEquals"
|
||||||
|
CondDateLessThan string = "DateLessThan"
|
||||||
|
CondDateLessThanEquals string = "DateLessThanEquals"
|
||||||
|
CondDateGreaterThan string = "DateGreaterThan"
|
||||||
|
CondDateGreaterThanEquals string = "DateGreaterThanEquals"
|
||||||
|
|
||||||
|
// Bolean condition operators.
|
||||||
|
CondBool string = "Bool"
|
||||||
|
|
||||||
|
// IP address condition operators.
|
||||||
|
CondIPAddress string = "IpAddress"
|
||||||
|
CondNotIPAddress string = "NotIpAddress"
|
||||||
|
|
||||||
|
// ARN condition operators.
|
||||||
|
CondArnEquals string = "ArnEquals"
|
||||||
|
CondArnLike string = "ArnLike"
|
||||||
|
CondArnNotEquals string = "ArnNotEquals"
|
||||||
|
CondArnNotLike string = "ArnNotLike"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p Policy) ToChain() (*policyengine.Chain, error) {
|
||||||
|
var chain policyengine.Chain
|
||||||
|
|
||||||
|
for _, statement := range p.Statement {
|
||||||
|
status := policyengine.AccessDenied
|
||||||
|
if statement.Effect == AllowEffect {
|
||||||
|
status = policyengine.Allow
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(statement.Principal) != 1 {
|
||||||
|
return nil, errors.New("currently supported exactly one principal type")
|
||||||
|
}
|
||||||
|
|
||||||
|
var principals []string
|
||||||
|
var op policyengine.ConditionType
|
||||||
|
if _, ok := statement.Principal[Wildcard]; ok {
|
||||||
|
principals = []string{Wildcard}
|
||||||
|
op = policyengine.CondStringLike
|
||||||
|
} else if frostfsPrincipals, ok := statement.Principal[FrostFSPrincipal]; ok {
|
||||||
|
principals = frostfsPrincipals
|
||||||
|
op = policyengine.CondStringEquals
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("currently supported only FrostFS or all (wildcard) principals")
|
||||||
|
}
|
||||||
|
|
||||||
|
var conditions []policyengine.Condition
|
||||||
|
for _, principal := range principals {
|
||||||
|
conditions = append(conditions, policyengine.Condition{
|
||||||
|
Op: op,
|
||||||
|
Object: policyengine.ObjectRequest,
|
||||||
|
Key: RequestOwnerProperty,
|
||||||
|
Value: principal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
conds, err := statement.Conditions.ToChainCondition()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conditions = append(conditions, conds...)
|
||||||
|
|
||||||
|
r := policyengine.Rule{
|
||||||
|
Status: status,
|
||||||
|
Action: statement.Action,
|
||||||
|
Resource: statement.Resource,
|
||||||
|
Any: true,
|
||||||
|
Condition: conditions,
|
||||||
|
}
|
||||||
|
chain.Rules = append(chain.Rules, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &chain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conditions = append(conditions, policyengine.Condition{
|
||||||
|
Op: condType,
|
||||||
|
Object: policyengine.ObjectRequest,
|
||||||
|
Key: key,
|
||||||
|
Value: converted,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conditions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type convertFunction func(string) (string, error)
|
||||||
|
|
||||||
|
func noConvertFunction(val string) (string, error) {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dateConvertFunction(val string) (string, error) {
|
||||||
|
if _, err := strconv.ParseInt(val, 10, 64); err == nil {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := time.Parse(time.RFC3339, val)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strconv.FormatInt(parsed.UTC().Unix(), 10), nil
|
||||||
|
}
|
270
iam/converter_test.go
Normal file
270
iam/converter_test.go
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
package iam
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConverters(t *testing.T) {
|
||||||
|
t.Run("valid policy", func(t *testing.T) {
|
||||||
|
p := Policy{
|
||||||
|
Version: "2012-10-17",
|
||||||
|
Statement: []Statement{{
|
||||||
|
Principal: map[string][]string{
|
||||||
|
FrostFSPrincipal: {"arn:aws:iam::111122223333:user/JohnDoe"},
|
||||||
|
},
|
||||||
|
Effect: AllowEffect,
|
||||||
|
Action: []string{"s3:PutObject"},
|
||||||
|
Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
|
||||||
|
Conditions: map[string]Condition{
|
||||||
|
CondStringEquals: {
|
||||||
|
"s3:RequestObjectTag/Department": {"Finance"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &policyengine.Chain{Rules: []policyengine.Rule{
|
||||||
|
{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Action: p.Statement[0].Action,
|
||||||
|
Resource: p.Statement[0].Resource,
|
||||||
|
Any: true,
|
||||||
|
Condition: []policyengine.Condition{
|
||||||
|
{
|
||||||
|
Op: policyengine.CondStringEquals,
|
||||||
|
Object: policyengine.ObjectRequest,
|
||||||
|
Key: RequestOwnerProperty,
|
||||||
|
Value: "arn:aws:iam::111122223333:user/JohnDoe",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Op: policyengine.CondStringEquals,
|
||||||
|
Object: policyengine.ObjectRequest,
|
||||||
|
Key: "s3:RequestObjectTag/Department",
|
||||||
|
Value: "Finance",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
chain, err := p.ToChain()
|
||||||
|
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[string][]string{
|
||||||
|
"AWS": {"arn:aws:iam::111122223333:user/JohnDoe"},
|
||||||
|
},
|
||||||
|
Effect: AllowEffect,
|
||||||
|
Action: []string{"s3:PutObject"},
|
||||||
|
Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := p.ToChain()
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid policy (missing principal)", func(t *testing.T) {
|
||||||
|
p := Policy{
|
||||||
|
Version: "2012-10-17",
|
||||||
|
Statement: []Statement{{
|
||||||
|
Principal: map[string][]string{},
|
||||||
|
Effect: AllowEffect,
|
||||||
|
Action: []string{"s3:PutObject"},
|
||||||
|
Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := p.ToChain()
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check policy conditions", func(t *testing.T) {
|
||||||
|
p := Policy{
|
||||||
|
Version: "2012-10-17",
|
||||||
|
Statement: []Statement{{
|
||||||
|
Principal: map[string][]string{"*": nil},
|
||||||
|
Effect: AllowEffect,
|
||||||
|
Action: []string{"s3:PutObject"},
|
||||||
|
Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
|
||||||
|
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: {"key17": {"val17"}},
|
||||||
|
CondArnNotEquals: {"key18": {"val18"}},
|
||||||
|
CondArnNotLike: {"key19": {"val19"}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &policyengine.Chain{Rules: []policyengine.Rule{
|
||||||
|
{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Action: p.Statement[0].Action,
|
||||||
|
Resource: p.Statement[0].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: "key17",
|
||||||
|
Value: "val17",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Op: policyengine.CondStringNotEquals,
|
||||||
|
Object: policyengine.ObjectRequest,
|
||||||
|
Key: "key18",
|
||||||
|
Value: "val18",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Op: policyengine.CondStringNotLike,
|
||||||
|
Object: policyengine.ObjectRequest,
|
||||||
|
Key: "key19",
|
||||||
|
Value: "val19",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
chain, err := p.ToChain()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for i, rule := range chain.Rules {
|
||||||
|
expectedRule := expected.Rules[i]
|
||||||
|
require.Equal(t, expectedRule.Action, rule.Action)
|
||||||
|
require.Equal(t, expectedRule.Any, rule.Any)
|
||||||
|
require.Equal(t, expectedRule.Resource, rule.Resource)
|
||||||
|
require.Equal(t, expectedRule.Status, rule.Status)
|
||||||
|
require.ElementsMatch(t, expectedRule.Condition, rule.Condition)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
177
iam/policy.go
Normal file
177
iam/policy.go
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
package iam
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Policy grammar https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_grammar.html
|
||||||
|
// Currently 'NotPrincipal', 'NotAction' and 'NotResource' are not supported (so cannot be unmarshalled).
|
||||||
|
Policy struct {
|
||||||
|
Version string `json:"Version,omitempty"`
|
||||||
|
ID string `json:"Id,omitempty"`
|
||||||
|
Statement Statements `json:"Statement"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Statements []Statement
|
||||||
|
|
||||||
|
Statement struct {
|
||||||
|
SID string `json:"Sid,omitempty"`
|
||||||
|
Principal Principal `json:"Principal,omitempty"`
|
||||||
|
Effect Effect `json:"Effect"`
|
||||||
|
Action Action `json:"Action"`
|
||||||
|
Resource Resource `json:"Resource"`
|
||||||
|
Conditions Conditions `json:"Condition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Principal map[string][]string
|
||||||
|
|
||||||
|
Effect string
|
||||||
|
|
||||||
|
Action []string
|
||||||
|
|
||||||
|
Resource []string
|
||||||
|
|
||||||
|
Conditions map[string]Condition
|
||||||
|
|
||||||
|
Condition map[string][]string
|
||||||
|
)
|
||||||
|
|
||||||
|
const Wildcard = "*"
|
||||||
|
|
||||||
|
const (
|
||||||
|
AllowEffect Effect = "Allow"
|
||||||
|
DenyEffect Effect = "Deny"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Statements) UnmarshalJSON(data []byte) error {
|
||||||
|
var list []Statement
|
||||||
|
if err := json.Unmarshal(data, &list); err == nil {
|
||||||
|
*s = list
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var elem Statement
|
||||||
|
if err := json.Unmarshal(data, &elem); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*s = []Statement{elem}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Principal) UnmarshalJSON(data []byte) error {
|
||||||
|
*p = make(Principal)
|
||||||
|
|
||||||
|
var str string
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &str); err == nil {
|
||||||
|
if str != Wildcard {
|
||||||
|
return errors.New("invalid IAM string principal")
|
||||||
|
}
|
||||||
|
(*p)[Wildcard] = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal(data, &m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range m {
|
||||||
|
element, ok := val.(string)
|
||||||
|
if ok {
|
||||||
|
(*p)[key] = []string{element}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
list, ok := val.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid principal format")
|
||||||
|
}
|
||||||
|
|
||||||
|
resList := make([]string, len(list))
|
||||||
|
for i := range list {
|
||||||
|
val, ok := list[i].(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid principal format")
|
||||||
|
}
|
||||||
|
resList[i] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
(*p)[key] = resList
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Action) UnmarshalJSON(data []byte) error {
|
||||||
|
var list []string
|
||||||
|
if err := json.Unmarshal(data, &list); err == nil {
|
||||||
|
*a = list
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var elem string
|
||||||
|
if err := json.Unmarshal(data, &elem); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*a = []string{elem}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) UnmarshalJSON(data []byte) error {
|
||||||
|
var list []string
|
||||||
|
if err := json.Unmarshal(data, &list); err == nil {
|
||||||
|
*r = list
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var elem string
|
||||||
|
if err := json.Unmarshal(data, &elem); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*r = []string{elem}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Condition) UnmarshalJSON(data []byte) error {
|
||||||
|
*c = make(Condition)
|
||||||
|
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal(data, &m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range m {
|
||||||
|
element, ok := val.(string)
|
||||||
|
if ok {
|
||||||
|
(*c)[key] = []string{element}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
list, ok := val.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid principal format")
|
||||||
|
}
|
||||||
|
|
||||||
|
resList := make([]string, len(list))
|
||||||
|
for i := range list {
|
||||||
|
val, ok := list[i].(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid principal format")
|
||||||
|
}
|
||||||
|
resList[i] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
(*c)[key] = resList
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
173
iam/policy_test.go
Normal file
173
iam/policy_test.go
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
package iam
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnmarshalIAMPolicy(t *testing.T) {
|
||||||
|
t.Run("simple fields", func(t *testing.T) {
|
||||||
|
policy := `{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Id": "PutObjPolicy",
|
||||||
|
"Statement": {
|
||||||
|
"Sid": "DenyObjectsThatAreNotSSEKMS",
|
||||||
|
"Principal": "*",
|
||||||
|
"Effect": "Deny",
|
||||||
|
"Action": "s3:PutObject",
|
||||||
|
"Resource": "arn:aws:s3:::DOC-EXAMPLE-BUCKET/*",
|
||||||
|
"Condition": {
|
||||||
|
"Null": {
|
||||||
|
"s3:x-amz-server-side-encryption-aws-kms-key-id": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
expected := Policy{
|
||||||
|
Version: "2012-10-17",
|
||||||
|
ID: "PutObjPolicy",
|
||||||
|
Statement: []Statement{{
|
||||||
|
SID: "DenyObjectsThatAreNotSSEKMS",
|
||||||
|
Principal: map[string][]string{
|
||||||
|
"*": nil,
|
||||||
|
},
|
||||||
|
Effect: DenyEffect,
|
||||||
|
Action: []string{"s3:PutObject"},
|
||||||
|
Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
|
||||||
|
Conditions: map[string]Condition{
|
||||||
|
"Null": {
|
||||||
|
"s3:x-amz-server-side-encryption-aws-kms-key-id": {"true"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var p Policy
|
||||||
|
err := json.Unmarshal([]byte(policy), &p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, p)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("complex fields", func(t *testing.T) {
|
||||||
|
policy := `{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [{
|
||||||
|
"Principal":{
|
||||||
|
"AWS":[
|
||||||
|
"arn:aws:iam::111122223333:user/JohnDoe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:PutObject"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"
|
||||||
|
],
|
||||||
|
"Condition": {
|
||||||
|
"StringEquals": {
|
||||||
|
"s3:RequestObjectTag/Department": ["Finance"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}`
|
||||||
|
|
||||||
|
expected := Policy{
|
||||||
|
Version: "2012-10-17",
|
||||||
|
Statement: []Statement{{
|
||||||
|
Principal: map[string][]string{
|
||||||
|
"AWS": {"arn:aws:iam::111122223333:user/JohnDoe"},
|
||||||
|
},
|
||||||
|
Effect: AllowEffect,
|
||||||
|
Action: []string{"s3:PutObject"},
|
||||||
|
Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
|
||||||
|
Conditions: map[string]Condition{
|
||||||
|
"StringEquals": {
|
||||||
|
"s3:RequestObjectTag/Department": {"Finance"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var p Policy
|
||||||
|
err := json.Unmarshal([]byte(policy), &p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, p)
|
||||||
|
|
||||||
|
raw, err := json.Marshal(expected)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.JSONEq(t, policy, string(raw))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check principal AWS", func(t *testing.T) {
|
||||||
|
policy := `{
|
||||||
|
"Statement": [{
|
||||||
|
"Principal":{
|
||||||
|
"AWS":"arn:aws:iam::111122223333:user/JohnDoe"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}`
|
||||||
|
|
||||||
|
expected := Policy{
|
||||||
|
Statement: []Statement{{
|
||||||
|
Principal: map[string][]string{
|
||||||
|
"AWS": {"arn:aws:iam::111122223333:user/JohnDoe"},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var p Policy
|
||||||
|
err := json.Unmarshal([]byte(policy), &p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, p)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("native example", func(t *testing.T) {
|
||||||
|
policy := `
|
||||||
|
{
|
||||||
|
"Version": "xyz",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"native:*",
|
||||||
|
"s3:PutObject",
|
||||||
|
"s3:GetObject"
|
||||||
|
],
|
||||||
|
"Resource": ["*"],
|
||||||
|
"Principal": {"FrostFS": ["did:frostfs:039e3ee771a223361fe7862f532e9511b57baaae3c3e2622682e99d0e660f7671"]},
|
||||||
|
"Condition": {"StringEquals": {"native::object::attribute": "iamuser-admin"}}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
var p Policy
|
||||||
|
err := json.Unmarshal([]byte(policy), &p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("condition array", func(t *testing.T) {
|
||||||
|
policy := `
|
||||||
|
{
|
||||||
|
"Statement": [{
|
||||||
|
"Condition": {"StringLike": {"ec2:InstanceType": ["t1.*", "t2.*", "m3.*"]}}
|
||||||
|
}]
|
||||||
|
}`
|
||||||
|
|
||||||
|
expected := Policy{
|
||||||
|
Statement: []Statement{{
|
||||||
|
Conditions: map[string]Condition{
|
||||||
|
"StringLike": {"ec2:InstanceType": {"t1.*", "t2.*", "m3.*"}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var p Policy
|
||||||
|
err := json.Unmarshal([]byte(policy), &p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, p)
|
||||||
|
})
|
||||||
|
}
|
30
policy.go
30
policy.go
|
@ -1,30 +0,0 @@
|
||||||
package policyengine
|
|
||||||
|
|
||||||
//{
|
|
||||||
// "Version": "xyz",
|
|
||||||
// "Policy": [
|
|
||||||
// {
|
|
||||||
// "Effect": "Allow",
|
|
||||||
// "Action": [
|
|
||||||
// "native:*",
|
|
||||||
// "s3:PutObject",
|
|
||||||
// "s3:GetObject"
|
|
||||||
// ],
|
|
||||||
// "Resource": ["*"],
|
|
||||||
// "Principal": ["did:frostfs:039e3ee771a223361fe7862f532e9511b57baaae3c3e2622682e99d0e660f7671"],
|
|
||||||
// "Condition": [ {"StringEquals": {"native::object::attribute", "iamuser-admin"}]
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
//}
|
|
||||||
|
|
||||||
// type Policy struct {
|
|
||||||
// Rules []Rule `json:"Policy"`
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type AWSRule struct {
|
|
||||||
// Effect string `json:"Effect"`
|
|
||||||
// Action []string `json:"Action"`
|
|
||||||
// Resource []string `json:"Resource"`
|
|
||||||
// Principal []string `json:"Principal"`
|
|
||||||
// Condition []Condition `json:"Condition"`
|
|
||||||
// }
|
|
Loading…
Reference in a new issue