Skip to content

Instantly share code, notes, and snippets.

@andrewmeissner
Last active November 24, 2020 18:35
Show Gist options
  • Save andrewmeissner/1edc9ed9f8a3c4c8fccf3f75d93c6606 to your computer and use it in GitHub Desktop.
Save andrewmeissner/1edc9ed9f8a3c4c8fccf3f75d93c6606 to your computer and use it in GitHub Desktop.
AWS Policy Documents can have string/[]string Resource attributes
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"strings"
)
var (
raw = `{"Version":"2020-17-12","Statement":[{"Effect":"deny","Action":["s3:Get*"],"Resource":["myArn","myOtherArn"]}]}`
// raw = `{"Version":"2020-09-12","Statement":[{"Effect":"alLow","Action":["s3:Get*"],"Resource":"myArn"}]}`
)
type PolicyDocument struct {
Version string
Statement []StatementEntry
}
type Effect string
const (
EffectAllow Effect = "Allow"
EffectDeny Effect = "Deny"
)
func (e *Effect) UnmarshalJSON(bs []byte) error {
var s string
if err := json.Unmarshal(bs, &s); err != nil {
return err
}
effect := Effect(strings.Title(strings.ToLower(s)))
switch effect {
case EffectAllow, EffectDeny:
*e = effect
return nil
default:
return errors.New("invalid effect")
}
}
type StatementEntry struct {
Effect Effect
Action []string
Resource []string
}
func (s *StatementEntry) UnmarshalJSON(bs []byte) error {
var data map[string]interface{}
if err := json.Unmarshal(bs, &data); err != nil {
return err
}
if action, err := consolidate(data, "Action"); err == nil {
s.Action = action
} else {
return err
}
if resource, err := consolidate(data, "Resource"); err == nil {
s.Resource = resource
} else {
return err
}
var e Effect
jsonKey := `"` + data["Effect"].(string) + `"`
if err := json.Unmarshal([]byte(jsonKey), &e); err != nil {
return err
}
s.Effect = e
return nil
}
func (s StatementEntry) MarshalJSON() ([]byte, error) {
buffer := new(bytes.Buffer)
if _, err := buffer.WriteString(`{`); err != nil {
return nil, err
}
if _, err := buffer.WriteString(fmt.Sprintf(`"Effect":"%s",`, s.Effect)); err != nil {
return nil, err
}
if err := marshalSlice(buffer, "Action", s.Action); err != nil {
return nil, err
}
if _, err := buffer.WriteString(`,`); err != nil {
return nil, err
}
if err := marshalSlice(buffer, "Resource", s.Resource); err != nil {
return nil, err
}
if _, err := buffer.WriteString("}"); err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
func consolidate(objs map[string]interface{}, key string) ([]string, error) {
iItem, ok1 := objs[key].(interface{})
if !ok1 {
return nil, errors.New("failed to cast to map")
}
switch v := iItem.(type) {
case string:
item, ok2 := iItem.(string)
if !ok2 {
return nil, errors.New("failed to cast single item to string")
}
return strings.Split(item, ","), nil
case []interface{}:
iItems, ok3 := iItem.([]interface{})
if !ok3 {
return nil, errors.New("failed to cast to interface slice")
}
items := make([]string, len(iItems))
for i := 0; i < len(iItems); i++ {
item, ok4 := iItems[i].(string)
if !ok4 {
return nil, errors.New("failed to cast item in slice to a string")
}
items[i] = item
}
return items, nil
default:
return nil, fmt.Errorf("failed on type assertion: %v", v)
}
}
func marshalSlice(buf *bytes.Buffer, key string, items []string) error {
bs, err := json.Marshal(items)
if err != nil {
return err
}
if _, err := buf.WriteString(fmt.Sprintf(`"%s":`, key)); err != nil {
return err
}
if _, err := buf.Write(bs); err != nil {
return err
}
return nil
}
func main() {
var pd PolicyDocument
if err := json.Unmarshal([]byte(raw), &pd); err != nil {
panic(err)
}
fmt.Printf("%+v\n", pd)
bs, err := json.Marshal(pd)
if err != nil {
panic(err)
}
fmt.Println(string(bs))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment