[#XX] Add IAM policy unmarshaler

This commit is contained in:
Denis Kirillov 2023-10-19 16:15:21 +03:00
parent 3970569602
commit 0933aa7ce6
2 changed files with 286 additions and 26 deletions

161
policy.go
View file

@ -1,30 +1,139 @@
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"}]
// }
// ]
//}
import (
"encoding/json"
"errors"
)
// type Policy struct {
// Rules []Rule `json:"Policy"`
// }
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"`
}
// type AWSRule struct {
// Effect string `json:"Effect"`
// Action []string `json:"Action"`
// Resource []string `json:"Resource"`
// Principal []string `json:"Principal"`
// Condition []Condition `json:"Condition"`
// }
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
}

151
policy_test.go Normal file
View file

@ -0,0 +1,151 @@
package policyengine
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 := IAMPolicy{
Version: "2012-10-17",
ID: "PutObjPolicy",
Statement: []IAMStatement{{
SID: "DenyObjectsThatAreNotSSEKMS",
Principal: map[string][]string{
"*": nil,
},
Effect: IAMDenyEffect,
Action: []string{"s3:PutObject"},
Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
Condition: map[string]map[string]string{
"Null": {
"s3:x-amz-server-side-encryption-aws-kms-key-id": "true",
},
},
}},
}
var p IAMPolicy
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 := IAMPolicy{
Version: "2012-10-17",
Statement: []IAMStatement{{
Principal: map[string][]string{
"AWS": {"arn:aws:iam::111122223333:user/JohnDoe"},
},
Effect: IAMAllowEffect,
Action: []string{"s3:PutObject"},
Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"},
Condition: map[string]map[string]string{
"StringEquals": {
"s3:RequestObjectTag/Department": "Finance",
},
},
}},
}
var p IAMPolicy
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 := IAMPolicy{
Statement: []IAMStatement{{
Principal: map[string][]string{
"AWS": {"arn:aws:iam::111122223333:user/JohnDoe"},
},
}},
}
var p IAMPolicy
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 IAMPolicy
err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
})
}