-
Notifications
You must be signed in to change notification settings - Fork 79
/
iam.go
150 lines (137 loc) · 4.47 KB
/
iam.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Package iam implements various IAM components.
package iam
import (
"encoding/json"
"fmt"
"net/url"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/iam/iamiface"
"go.uber.org/zap"
)
// PolicyDocument is the IAM policy document.
type PolicyDocument struct {
Version string
Statement []StatementEntry
}
// StatementEntry is the entry in IAM policy document "Statement" field.
type StatementEntry struct {
Effect string `json:"Effect,omitempty"`
Action []string `json:"Action,omitempty"`
Resource string `json:"Resource,omitempty"`
Principal *PrincipalEntry `json:"Principal,omitempty"`
}
type AssumeRolePolicyDocument struct {
Version string `json:"Version"`
Statement []*AssumeRolePolicyDocumentStatement `json:"Statement"`
}
type AssumeRolePolicyDocumentSingle struct {
Version string `json:"Version"`
Statement []*AssumeRolePolicyDocumentStatementSingle `json:"Statement"`
}
type AssumeRolePolicyDocumentStatement struct {
Effect string `json:"Effect"`
Principal *PrincipalEntry `json:"Principal,omitempty"`
}
// PrincipalEntry represents the policy document Principal.
type PrincipalEntry struct {
Service []string `json:"Service,omitempty"`
}
type AssumeRolePolicyDocumentStatementSingle struct {
Effect string `json:"Effect"`
Principal *PrincipalEntrySingle `json:"Principal,omitempty"`
}
// PrincipalEntrySingle represents the policy document Principal.
type PrincipalEntrySingle struct {
Service string `json:"Service,omitempty"`
}
// Validate validates IAM role.
func Validate(
lg *zap.Logger,
iamAPI iamiface.IAMAPI,
roleName string,
requiredSPs []string,
requiredPolicyARNs []string,
) error {
lg.Info("validating role service principals",
zap.String("role-name", roleName),
)
out, err := iamAPI.GetRole(&iam.GetRoleInput{
RoleName: aws.String(roleName),
})
if err != nil {
lg.Warn("failed to GetRole", zap.Error(err))
return err
}
txt := aws.StringValue(out.Role.AssumeRolePolicyDocument)
txt, err = url.QueryUnescape(txt)
if err != nil {
return fmt.Errorf("failed to escape AssumeRolePolicyDocument:\n%s\n\n(%v)", txt, err)
}
doc, docSingle := new(AssumeRolePolicyDocument), new(AssumeRolePolicyDocumentSingle)
if err = json.Unmarshal([]byte(txt), doc); err != nil {
doc = nil
lg.Warn("retrying unmarshal", zap.String("body", txt), zap.Error(err))
if err = json.Unmarshal([]byte(txt), docSingle); err != nil {
return fmt.Errorf("failed to unmarshal AssumeRolePolicyDocument/Single:\n%s\n\n(%v)", txt, err)
}
}
trustedEntities := make(map[string]struct{})
switch {
case doc != nil && len(doc.Statement) > 0:
lg.Info("checking trusted entity using AssumeRolePolicyDocument",
zap.String("body", txt),
zap.String("parsed-doc", fmt.Sprintf("%+v", *doc)),
)
for _, v1 := range doc.Statement {
for _, v2 := range v1.Principal.Service {
lg.Info("found trusted entity", zap.String("entity", v2))
trustedEntities[v2] = struct{}{}
}
}
case docSingle != nil && len(docSingle.Statement) > 0:
lg.Info("checking trusted entity using AssumeRolePolicyDocumentSingle",
zap.String("body", txt),
zap.String("parsed-doc", fmt.Sprintf("%+v", *docSingle)),
)
for _, v1 := range docSingle.Statement {
lg.Info("found trusted entity", zap.String("entity", v1.Principal.Service))
trustedEntities[v1.Principal.Service] = struct{}{}
}
default:
return fmt.Errorf("statement not found %s", txt)
}
reqEnts := make(map[string]struct{})
for _, v := range requiredSPs {
reqEnts[v] = struct{}{}
}
for k := range reqEnts {
if _, ok := trustedEntities[k]; !ok {
return fmt.Errorf("Principal.Service missing %q", k)
}
}
lg.Info("validating role policies", zap.String("role-name", roleName))
lout, err := iamAPI.ListAttachedRolePolicies(&iam.ListAttachedRolePoliciesInput{
RoleName: aws.String(roleName),
})
if err != nil {
lg.Warn("failed to ListAttachedRolePolicies", zap.Error(err))
return err
}
attached := make(map[string]struct{})
for _, p := range lout.AttachedPolicies {
arn := aws.StringValue(p.PolicyArn)
lg.Info("found attached policy ARN", zap.String("policy-arn", arn))
attached[arn] = struct{}{}
}
reqPols := make(map[string]struct{})
for _, v := range requiredPolicyARNs {
reqPols[v] = struct{}{}
}
for k := range reqPols {
if _, ok := attached[k]; !ok {
return fmt.Errorf("PolicyARNs missing %q", k)
}
}
return nil
}