Compare commits

..

2 commits

Author SHA1 Message Date
0933aa7ce6 [#XX] Add IAM policy unmarshaler 2023-10-19 16:15:21 +03:00
3970569602 [#xx] Initial implementation
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-10-17 17:33:23 +03:00
6 changed files with 211 additions and 749 deletions

View file

@ -1,7 +1,8 @@
package policyengine
import (
"encoding/json"
"bytes"
"encoding/gob"
"fmt"
"strings"
)
@ -17,16 +18,24 @@ type Chain struct {
Rules []Rule
}
func init() {
// FIXME #1 (@fyrchik): Introduce more optimal serialization format.
gob.Register(Chain{})
}
func (c *Chain) Bytes() []byte {
data, err := json.Marshal(c)
if err != nil {
b := bytes.NewBuffer(nil)
e := gob.NewEncoder(b)
if err := e.Encode(c); err != nil {
panic(err)
}
return data
return b.Bytes()
}
func (c *Chain) DecodeBytes(b []byte) error {
return json.Unmarshal(b, c)
r := bytes.NewReader(b)
d := gob.NewDecoder(r)
return d.Decode(c)
}
type Rule struct {
@ -56,42 +65,60 @@ const (
ObjectActor
)
type ConditionType byte
// TODO @fyrchik: replace string with int-like type.
type ConditionType string
// TODO @fyrchik: reduce the number of conditions.
// Everything from here should be expressable, but we do not need them all.
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html
const (
// String condition operators.
CondStringEquals ConditionType = iota
CondStringNotEquals
CondStringEqualsIgnoreCase
CondStringNotEqualsIgnoreCase
CondStringLike
CondStringNotLike
CondStringLessThan
CondStringLessThanEquals
CondStringGreaterThan
CondStringGreaterThanEquals
CondStringEquals ConditionType = "StringEquals"
CondStringNotEquals ConditionType = "StringNotEquals"
CondStringEqualsIgnoreCase ConditionType = "StringEqualsIgnoreCase"
CondStringNotEqualsIgnoreCase ConditionType = "StringNotEqualsIgnoreCase"
CondStringLike ConditionType = "StringLike"
CondStringNotLike ConditionType = "StringNotLike"
// Numeric condition operators.
CondNumericEquals
CondNumericNotEquals
CondNumericLessThan
CondNumericLessThanEquals
CondNumericGreaterThan
CondNumericGreaterThanEquals
CondNumericEquals ConditionType = "NumericEquals"
CondNumericNotEquals ConditionType = "NumericNotEquals"
CondNumericLessThan ConditionType = "NumericLessThan"
CondNumericLessThanEquals ConditionType = "NumericLessThanEquals"
CondNumericGreaterThan ConditionType = "NumericGreaterThan"
CondNumericGreaterThanEquals ConditionType = "NumericGreaterThanEquals"
// Date condition operators.
CondDateEquals ConditionType = "DateEquals"
CondDateNotEquals ConditionType = "DateNotEquals"
CondDateLessThan ConditionType = "DateLessThan"
CondDateLessThanEquals ConditionType = "DateLessThanEquals"
CondDateGreaterThan ConditionType = "DateGreaterThan"
CondDateGreaterThanEquals ConditionType = "DateGreaterThanEquals"
// Bolean condition operators.
CondBool ConditionType = "Bool"
// IP address condition operators.
CondIPAddress ConditionType = "IpAddress"
CondNotIPAddress ConditionType = "NotIpAddress"
// ARN condition operators.
CondArnEquals ConditionType = "ArnEquals"
CondArnLike ConditionType = "ArnLike"
CondArnNotEquals ConditionType = "ArnNotEquals"
CondArnNotLike ConditionType = "ArnNotLike"
)
func (c *Condition) Match(req Request) bool {
func (c *Condition) Match(obj Request) bool {
var val string
switch c.Object {
case ObjectResource:
val = req.Resource().Property(c.Key)
val = obj.Resource().Property(c.Key)
case ObjectRequest:
val = req.Property(c.Key)
val = obj.Property(c.Key)
default:
panic(fmt.Sprintf("unknown condition type: %d", c.Object))
return false
}
switch c.Op {
@ -109,14 +136,6 @@ func (c *Condition) Match(req Request) bool {
return globMatch(val, c.Value)
case CondStringNotLike:
return !globMatch(val, c.Value)
case CondStringLessThan:
return val < c.Value
case CondStringLessThanEquals:
return val <= c.Value
case CondStringGreaterThan:
return val > c.Value
case CondStringGreaterThanEquals:
return val >= c.Value
}
}
@ -132,7 +151,7 @@ func (r *Rule) Match(req Request) (status Status, matched bool) {
return NoRuleFound, false
}
for i := range r.Action {
if globMatch(req.Operation(), r.Action[i]) {
if globMatch(r.Action[i], req.Operation()) {
return r.matchCondition(req)
}
}

View file

@ -1,227 +0,0 @@
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
}

View file

@ -1,270 +0,0 @@
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)
}
})
}

View file

@ -1,177 +0,0 @@
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
}

139
policy.go Normal file
View file

@ -0,0 +1,139 @@
package policyengine
import (
"encoding/json"
"errors"
)
type (
// IAMPolicy grammar https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_grammar.html
IAMPolicy struct {
Version string `json:"Version,omitempty"`
ID string `json:"Id,omitempty"`
Statement IAMStatements `json:"Statement"`
}
IAMStatements []IAMStatement
IAMStatement struct {
SID string `json:"Sid,omitempty"`
Principal IAMPrincipal `json:"Principal,omitempty"`
Effect IAMEffect `json:"Effect"`
Action IAMAction `json:"Action"`
Resource IAMResource `json:"Resource"`
Condition IAMCondition `json:"Condition,omitempty"`
}
IAMPrincipal map[string][]string
IAMEffect string
IAMAction []string
IAMResource []string
IAMCondition map[string]map[string]string
)
const IAMWildcard = "*"
const (
IAMAllowEffect IAMEffect = "Allow"
IAMDenyEffect IAMEffect = "Deny"
)
func (s *IAMStatements) UnmarshalJSON(data []byte) error {
var list []IAMStatement
if err := json.Unmarshal(data, &list); err == nil {
*s = list
return nil
}
var elem IAMStatement
if err := json.Unmarshal(data, &elem); err != nil {
return err
}
*s = []IAMStatement{elem}
return nil
}
func (p *IAMPrincipal) UnmarshalJSON(data []byte) error {
*p = make(IAMPrincipal)
var str string
if err := json.Unmarshal(data, &str); err == nil {
if str != IAMWildcard {
return errors.New("invalid IAM string principal")
}
(*p)[IAMWildcard] = 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 *IAMAction) 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 *IAMResource) 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
}

View file

@ -1,4 +1,4 @@
package iam
package policyengine
import (
"encoding/json"
@ -26,26 +26,26 @@ func TestUnmarshalIAMPolicy(t *testing.T) {
}
}`
expected := Policy{
expected := IAMPolicy{
Version: "2012-10-17",
ID: "PutObjPolicy",
Statement: []Statement{{
Statement: []IAMStatement{{
SID: "DenyObjectsThatAreNotSSEKMS",
Principal: map[string][]string{
"*": nil,
},
Effect: DenyEffect,
Effect: IAMDenyEffect,
Action: []string{"s3:PutObject"},
Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
Conditions: map[string]Condition{
Condition: map[string]map[string]string{
"Null": {
"s3:x-amz-server-side-encryption-aws-kms-key-id": {"true"},
"s3:x-amz-server-side-encryption-aws-kms-key-id": "true",
},
},
}},
}
var p Policy
var p IAMPolicy
err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
require.Equal(t, expected, p)
@ -69,30 +69,30 @@ func TestUnmarshalIAMPolicy(t *testing.T) {
],
"Condition": {
"StringEquals": {
"s3:RequestObjectTag/Department": ["Finance"]
"s3:RequestObjectTag/Department": "Finance"
}
}
}]
}`
expected := Policy{
expected := IAMPolicy{
Version: "2012-10-17",
Statement: []Statement{{
Statement: []IAMStatement{{
Principal: map[string][]string{
"AWS": {"arn:aws:iam::111122223333:user/JohnDoe"},
},
Effect: AllowEffect,
Effect: IAMAllowEffect,
Action: []string{"s3:PutObject"},
Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
Conditions: map[string]Condition{
Condition: map[string]map[string]string{
"StringEquals": {
"s3:RequestObjectTag/Department": {"Finance"},
"s3:RequestObjectTag/Department": "Finance",
},
},
}},
}
var p Policy
var p IAMPolicy
err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
require.Equal(t, expected, p)
@ -111,15 +111,15 @@ func TestUnmarshalIAMPolicy(t *testing.T) {
}]
}`
expected := Policy{
Statement: []Statement{{
expected := IAMPolicy{
Statement: []IAMStatement{{
Principal: map[string][]string{
"AWS": {"arn:aws:iam::111122223333:user/JohnDoe"},
},
}},
}
var p Policy
var p IAMPolicy
err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
require.Equal(t, expected, p)
@ -144,30 +144,8 @@ func TestUnmarshalIAMPolicy(t *testing.T) {
]
}`
var p Policy
var p IAMPolicy
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)
})
}