opentofu/builtin/providers/aws/iam_policy_model.go
Martin Atkins 145bf42806 provider/aws: IAM policy document: normalize wildcard principals
There are three equivalent forms for expressing "everyone" (including
anonymous) in IAM policies:

- "Principals": "*"
- "Principals": {"AWS": "*"}
- "Principals": {"*": "*"}

The more-constrained syntax used by our aws_iam_policy_document data
source means that the user can only express the latter two of these
directly. However, when returning IAM policies from the API AWS likes to
normalize to the first form, causing unresolvable diffs.

This fixes #9335 by handling the "everyone" case as a special case,
serializing it in JSON as the "*" shorthand form.

This change does *not* address the normalization of hand-written policies
containing such elements. A similar change would need to be made in
the external package github.com/jen20/awspolicyequivalence in order to
avoid the issue for hand-written policies.
2016-11-01 08:46:34 -07:00

113 lines
3.1 KiB
Go

package aws
import (
"encoding/json"
"sort"
)
type IAMPolicyDoc struct {
Version string `json:",omitempty"`
Id string `json:",omitempty"`
Statements []*IAMPolicyStatement `json:"Statement"`
}
type IAMPolicyStatement struct {
Sid string
Effect string `json:",omitempty"`
Actions interface{} `json:"Action,omitempty"`
NotActions interface{} `json:"NotAction,omitempty"`
Resources interface{} `json:"Resource,omitempty"`
NotResources interface{} `json:"NotResource,omitempty"`
Principals IAMPolicyStatementPrincipalSet `json:"Principal,omitempty"`
NotPrincipals IAMPolicyStatementPrincipalSet `json:"NotPrincipal,omitempty"`
Conditions IAMPolicyStatementConditionSet `json:"Condition,omitempty"`
}
type IAMPolicyStatementPrincipal struct {
Type string
Identifiers interface{}
}
type IAMPolicyStatementCondition struct {
Test string
Variable string
Values interface{}
}
type IAMPolicyStatementPrincipalSet []IAMPolicyStatementPrincipal
type IAMPolicyStatementConditionSet []IAMPolicyStatementCondition
func (ps IAMPolicyStatementPrincipalSet) MarshalJSON() ([]byte, error) {
raw := map[string]interface{}{}
// As a special case, IAM considers the string value "*" to be
// equivalent to "AWS": "*", and normalizes policies as such.
// We'll follow their lead and do the same normalization here.
// IAM also considers {"*": "*"} to be equivalent to this.
if len(ps) == 1 {
p := ps[0]
if p.Type == "AWS" || p.Type == "*" {
if sv, ok := p.Identifiers.(string); ok && sv == "*" {
return []byte(`"*"`), nil
}
if av, ok := p.Identifiers.([]string); ok && len(av) == 1 && av[0] == "*" {
return []byte(`"*"`), nil
}
}
}
for _, p := range ps {
switch i := p.Identifiers.(type) {
case []string:
if _, ok := raw[p.Type]; !ok {
raw[p.Type] = make([]string, 0, len(i))
}
sort.Sort(sort.Reverse(sort.StringSlice(i)))
raw[p.Type] = append(raw[p.Type].([]string), i...)
case string:
raw[p.Type] = i
default:
panic("Unsupported data type for IAMPolicyStatementPrincipalSet")
}
}
return json.Marshal(&raw)
}
func (cs IAMPolicyStatementConditionSet) MarshalJSON() ([]byte, error) {
raw := map[string]map[string]interface{}{}
for _, c := range cs {
if _, ok := raw[c.Test]; !ok {
raw[c.Test] = map[string]interface{}{}
}
switch i := c.Values.(type) {
case []string:
if _, ok := raw[c.Test][c.Variable]; !ok {
raw[c.Test][c.Variable] = make([]string, 0, len(i))
}
sort.Sort(sort.Reverse(sort.StringSlice(i)))
raw[c.Test][c.Variable] = append(raw[c.Test][c.Variable].([]string), i...)
case string:
raw[c.Test][c.Variable] = i
default:
panic("Unsupported data type for IAMPolicyStatementConditionSet")
}
}
return json.Marshal(&raw)
}
func iamPolicyDecodeConfigStringList(lI []interface{}) interface{} {
if len(lI) == 1 {
return lI[0].(string)
}
ret := make([]string, len(lI))
for i, vI := range lI {
ret[i] = vI.(string)
}
sort.Sort(sort.Reverse(sort.StringSlice(ret)))
return ret
}