Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unmarshal AWS IAM Document But Conditional string or []string in Doc

I'm working through the IAM policy examples for the AWS Go SDK and trying to do the opposite of the Create Policy example -- basically, get all of the IAM policy in the account, get the default policy versions, then unmarshal that json document into a struct so it is easily parsed.

I got this far but I'm stuck with how go handles a conditional struct type. In the AWS policy doc version response, the json data for StatementEntry can be string or []string depending on the doc.

What would be a best practice? Add another struct and use retry logic in the error handling?

package main

import (
    "encoding/json"
    "fmt"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/iam"
    "log"
    "net/url"
)

type PolicyDocument struct {
    Version   string
    Statement []StatementEntry
}

type StatementEntry struct {
    Effect   string
    Action   []string
    Resource []string
}

func main() {
    sess, _ := session.NewSession(&aws.Config{
        Region: aws.String("us-west-2")},
    )

    svc := iam.New(sess)

    fmt.Printf("%s - %s\n", arn, *result.Policy.Description)

    results, _ := svc.ListPolicies(&iam.ListPoliciesInput{})

    for _, policy := range results.Policies {

        arn := policy.Arn
        version := policy.DefaultVersionId

        pv, _ := svc.GetPolicyVersion(&iam.GetPolicyVersionInput{
            PolicyArn: arn,
            VersionId: version,
        })

        decodedValue, err := url.QueryUnescape(aws.StringValue(pv.PolicyVersion.Document))
        if err != nil {
            log.Fatal(err)
            return
        }
        //fmt.Println(decodedValue)

        data := []byte(decodedValue)

        var doc PolicyDocument
        err1 := json.Unmarshal(data, &doc)

        if err1 != nil {
            log.Fatal(err1)
        }

        fmt.Printf("\n----\n%v\n---\n", doc)
    }
}

Example PolicyDocuments are this:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ssm:PutParameter",
                "ssm:DeleteParameter",
                "ssm:DescribeInstancePatchStates",
                "elasticloadbalancing:RegisterTargets",
                "elasticloadbalancing:DescribeTargetHealth",
                "elasticloadbalancing:DescribeTargetGroups",
                "elasticloadbalancing:DeregisterTargets",
                "ssm:GetParameter"
            ],
            "Resource": "*"
        }
    ]
}

And this (for Resource []string in the StatementEntry):

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:ListBucket"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::SageMaker"
            ]
        },
        {
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::SageMaker/*"
            ]
        }
    ]
}
like image 772
tommy_o Avatar asked Sep 02 '25 02:09

tommy_o


1 Answers

You can achieve this by using a custom type for the slice with an Unmarshal method that first unmarshals to an empty interface and then determines if it is a slice or a single string:

package main

import (
    "fmt"
    "encoding/json"
    "errors"
)

type container struct {
    Field customSlice
}

type customSlice []string

func (c *customSlice) UnmarshalJSON(data []byte) error {
    var tmp interface{}
    err := json.Unmarshal(data, &tmp)
    if err != nil {
        return err
    }
    slice, ok := tmp.([]interface{})
    if ok {
        for _, item := range slice {
            *c = append(*c, item.(string))
        }
        return nil
    }
    theString, ok := tmp.(string)
    if ok {
        *c = append(*c, theString)
        return nil
    }
    return errors.New("Field neither slice or string")
}

func main() {
    jsonInputSlice := `{"Field":["a"]}`
    jsonInputString := `{"Field":"a"}`
    var containerSlice container
    var containerString container
    err := json.Unmarshal([]byte(jsonInputSlice), &containerSlice)
    if err != nil {
        panic(err)
    }
    fmt.Println(containerSlice)
    err = json.Unmarshal([]byte(jsonInputString), &containerString)
    if err != nil {
        panic(err)
    }
    fmt.Println(containerString)

}

https://play.golang.org/p/mAhJBNhE1yc

like image 197
Iain Duncan Avatar answered Sep 04 '25 17:09

Iain Duncan