Last active
November 24, 2020 18:35
-
-
Save andrewmeissner/1edc9ed9f8a3c4c8fccf3f75d93c6606 to your computer and use it in GitHub Desktop.
AWS Policy Documents can have string/[]string Resource attributes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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