mirror of https://github.com/kubernetes/kops.git
JSON formatting of IAM: Workaround for optional fields
AWS IAM is very strict and doesn't support `Resource: []` for example. We implement a custom MarshalJSON method to work around that.
This commit is contained in:
parent
d8895c57ec
commit
6fa8be2716
|
|
@ -338,7 +338,7 @@ func (b *IAMModelBuilder) buildAWSIAMRolePolicy(role iam.Subject) (fi.Resource,
|
|||
Statement: []*iam.Statement{
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: &iam.Principal{
|
||||
Principal: iam.Principal{
|
||||
Federated: "arn:aws:iam::" + b.AWSAccountID + ":oidc-provider/" + oidcProvider,
|
||||
},
|
||||
Action: stringorslice.String("sts:AssumeRoleWithWebIdentity"),
|
||||
|
|
|
|||
|
|
@ -77,10 +77,101 @@ type Condition map[string]interface{}
|
|||
// http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Statement
|
||||
type Statement struct {
|
||||
Effect StatementEffect
|
||||
Principal *Principal `json:",omitempty"`
|
||||
Action stringorslice.StringOrSlice `json:",omitempty"`
|
||||
Resource stringorslice.StringOrSlice `json:",omitempty"`
|
||||
Condition Condition `json:",omitempty"`
|
||||
Principal Principal
|
||||
Action stringorslice.StringOrSlice
|
||||
Resource stringorslice.StringOrSlice
|
||||
Condition Condition
|
||||
}
|
||||
|
||||
type jsonWriter struct {
|
||||
w io.Writer
|
||||
err error
|
||||
}
|
||||
|
||||
func (j *jsonWriter) Error() error {
|
||||
return j.err
|
||||
}
|
||||
|
||||
func (j *jsonWriter) WriteLiteral(b []byte) {
|
||||
if j.err != nil {
|
||||
return
|
||||
}
|
||||
_, err := j.w.Write(b)
|
||||
if err != nil {
|
||||
j.err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (j *jsonWriter) StartObject() {
|
||||
j.WriteLiteral([]byte("{"))
|
||||
}
|
||||
|
||||
func (j *jsonWriter) EndObject() {
|
||||
j.WriteLiteral([]byte("}"))
|
||||
}
|
||||
|
||||
func (j *jsonWriter) Comma() {
|
||||
j.WriteLiteral([]byte(","))
|
||||
}
|
||||
|
||||
func (j *jsonWriter) Field(s string) {
|
||||
if j.err != nil {
|
||||
return
|
||||
}
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
j.err = err
|
||||
return
|
||||
}
|
||||
j.WriteLiteral(b)
|
||||
j.WriteLiteral([]byte(": "))
|
||||
}
|
||||
|
||||
func (j *jsonWriter) Marshal(v interface{}) {
|
||||
if j.err != nil {
|
||||
return
|
||||
}
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
j.err = err
|
||||
return
|
||||
}
|
||||
j.WriteLiteral(b)
|
||||
}
|
||||
|
||||
// MarshalJSON formats the IAM statement for the AWS IAM restrictions.
|
||||
// For example, `Resource: []` is not allowed, but golang would force us to use pointers.
|
||||
func (s *Statement) MarshalJSON() ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
jw := &jsonWriter{w: &b}
|
||||
jw.StartObject()
|
||||
jw.Field("Effect")
|
||||
jw.Marshal(s.Effect)
|
||||
|
||||
if !s.Principal.IsEmpty() {
|
||||
jw.Comma()
|
||||
jw.Field("Principal")
|
||||
jw.Marshal(s.Principal)
|
||||
}
|
||||
if !s.Action.IsEmpty() {
|
||||
jw.Comma()
|
||||
jw.Field("Action")
|
||||
jw.Marshal(s.Action)
|
||||
}
|
||||
if !s.Resource.IsEmpty() {
|
||||
jw.Comma()
|
||||
jw.Field("Resource")
|
||||
jw.Marshal(s.Resource)
|
||||
}
|
||||
if len(s.Condition) != 0 {
|
||||
jw.Comma()
|
||||
jw.Field("Condition")
|
||||
jw.Marshal(s.Condition)
|
||||
}
|
||||
jw.EndObject()
|
||||
|
||||
return b.Bytes(), jw.Error()
|
||||
}
|
||||
|
||||
type Principal struct {
|
||||
|
|
@ -88,6 +179,10 @@ type Principal struct {
|
|||
Service string `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (p *Principal) IsEmpty() bool {
|
||||
return *p == Principal{}
|
||||
}
|
||||
|
||||
// Equal compares two IAM Statements and returns a bool
|
||||
// TODO: Extend to support Condition Keys
|
||||
func (l *Statement) Equal(r *Statement) bool {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,26 @@ func TestRoundTrip(t *testing.T) {
|
|||
},
|
||||
JSON: "{\"Effect\":\"Deny\",\"Action\":[\"ec2:DescribeRegions\",\"ec2:DescribeInstances\"],\"Resource\":[\"a\",\"b\"]}",
|
||||
},
|
||||
{
|
||||
IAM: &Statement{
|
||||
Effect: StatementEffectDeny,
|
||||
Principal: Principal{Federated: "federated"},
|
||||
Condition: map[string]interface{}{
|
||||
"foo": 1,
|
||||
},
|
||||
},
|
||||
JSON: "{\"Effect\":\"Deny\",\"Principal\":{\"Federated\":\"federated\"},\"Condition\":{\"foo\":1}}",
|
||||
},
|
||||
{
|
||||
IAM: &Statement{
|
||||
Effect: StatementEffectDeny,
|
||||
Principal: Principal{Service: "service"},
|
||||
Condition: map[string]interface{}{
|
||||
"bar": "baz",
|
||||
},
|
||||
},
|
||||
JSON: "{\"Effect\":\"Deny\",\"Principal\":{\"Service\":\"service\"},\"Condition\":{\"bar\":\"baz\"}}",
|
||||
},
|
||||
}
|
||||
for _, g := range grid {
|
||||
actualJSON, err := json.Marshal(g.IAM)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ type StringOrSlice struct {
|
|||
forceEncodeAsArray bool
|
||||
}
|
||||
|
||||
func (s *StringOrSlice) IsEmpty() bool {
|
||||
return len(s.values) == 0
|
||||
}
|
||||
|
||||
// Slice will build a value that marshals to a JSON array
|
||||
func Slice(v []string) StringOrSlice {
|
||||
return StringOrSlice{values: v, forceEncodeAsArray: true}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
"Federated": "arn:aws:iam::123456789012:oidc-provider/api.minimal.example.com"
|
||||
},
|
||||
"Action": "sts:AssumeRoleWithWebIdentity",
|
||||
"Resource": [],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"api.minimal.example.com:sub": "system:serviceaccount:kube-system:dns-controller"
|
||||
|
|
|
|||
Loading…
Reference in New Issue