/
iam_helper.go
210 lines (182 loc) · 7.75 KB
/
iam_helper.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package builder
import (
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws/arn"
api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5"
cft "github.com/weaveworks/eksctl/pkg/cfn/template"
gfn "github.com/weaveworks/goformation/v4/cloudformation"
gfniam "github.com/weaveworks/goformation/v4/cloudformation/iam"
gfnt "github.com/weaveworks/goformation/v4/cloudformation/types"
"k8s.io/apimachinery/pkg/util/sets"
)
type cfnTemplate interface {
attachAllowPolicy(name string, refRole *gfnt.Value, statements []cft.MapOfInterfaces)
newResource(name string, resource gfn.Resource) *gfnt.Value
}
type managedPolicyForRole struct {
name string
}
type customPolicyForRole struct {
Name string
Statements []cft.MapOfInterfaces
}
func createWellKnownPolicies(wellKnownPolicies api.WellKnownPolicies) ([]managedPolicyForRole, []customPolicyForRole) {
var managedPolicies []managedPolicyForRole
var customPolicies []customPolicyForRole
if wellKnownPolicies.ImageBuilder {
managedPolicies = append(managedPolicies,
managedPolicyForRole{name: iamPolicyAmazonEC2ContainerRegistryPowerUser},
)
}
if wellKnownPolicies.AutoScaler {
customPolicies = append(customPolicies,
customPolicyForRole{Name: "PolicyAutoScaling", Statements: autoScalerStatements()},
)
}
if wellKnownPolicies.AWSLoadBalancerController {
customPolicies = append(customPolicies,
customPolicyForRole{Name: "PolicyAWSLoadBalancerController", Statements: loadBalancerControllerStatements()},
)
}
if wellKnownPolicies.ExternalDNS {
customPolicies = append(customPolicies,
[]customPolicyForRole{
{Name: "PolicyExternalDNSChangeSet", Statements: changeSetStatements()},
{Name: "PolicyExternalDNSHostedZones", Statements: externalDNSHostedZonesStatements()},
}...,
)
}
if wellKnownPolicies.CertManager {
customPolicies = append(customPolicies,
[]customPolicyForRole{
{Name: "PolicyCertManagerChangeSet", Statements: changeSetStatements()},
{Name: "PolicyCertManagerGetChange", Statements: certManagerGetChangeStatements()},
{Name: "PolicyCertManagerHostedZones", Statements: certManagerHostedZonesStatements()},
}...,
)
}
return managedPolicies, customPolicies
}
// createRole creates an IAM role with policies required for the worker nodes and addons
func createRole(cfnTemplate cfnTemplate, clusterIAMConfig *api.ClusterIAM, iamConfig *api.NodeGroupIAM, managed, enableSSM, forceAddCNIPolicy bool) error {
managedPolicyARNs, err := makeManagedPolicies(clusterIAMConfig, iamConfig, managed, enableSSM, forceAddCNIPolicy)
if err != nil {
return err
}
role := gfniam.Role{
Path: gfnt.NewString("/"),
AssumeRolePolicyDocument: cft.MakeAssumeRolePolicyDocumentForServices(MakeServiceRef("EC2")),
ManagedPolicyArns: managedPolicyARNs,
}
if iamConfig.InstanceRoleName != "" {
role.RoleName = gfnt.NewString(iamConfig.InstanceRoleName)
}
if iamConfig.InstanceRolePermissionsBoundary != "" {
role.PermissionsBoundary = gfnt.NewString(iamConfig.InstanceRolePermissionsBoundary)
}
refIR := cfnTemplate.newResource(cfnIAMInstanceRoleName, &role)
if api.IsEnabled(iamConfig.WithAddonPolicies.AutoScaler) {
cfnTemplate.attachAllowPolicy("PolicyAutoScaling", refIR, autoScalerStatements())
}
if api.IsEnabled(iamConfig.WithAddonPolicies.CertManager) {
cfnTemplate.attachAllowPolicy("PolicyCertManagerChangeSet", refIR, changeSetStatements())
cfnTemplate.attachAllowPolicy("PolicyCertManagerHostedZones", refIR, certManagerHostedZonesStatements())
cfnTemplate.attachAllowPolicy("PolicyCertManagerGetChange", refIR, certManagerGetChangeStatements())
}
if api.IsEnabled(iamConfig.WithAddonPolicies.ExternalDNS) {
cfnTemplate.attachAllowPolicy("PolicyExternalDNSChangeSet", refIR, changeSetStatements())
cfnTemplate.attachAllowPolicy("PolicyExternalDNSHostedZones", refIR, externalDNSHostedZonesStatements())
}
if api.IsEnabled(iamConfig.WithAddonPolicies.AppMesh) {
cfnTemplate.attachAllowPolicy("PolicyAppMesh", refIR, appMeshStatements("appmesh:*"))
}
if api.IsEnabled(iamConfig.WithAddonPolicies.AppMeshPreview) {
cfnTemplate.attachAllowPolicy("PolicyAppMeshPreview", refIR, appMeshStatements("appmesh-preview:*"))
}
if api.IsEnabled(iamConfig.WithAddonPolicies.EBS) {
cfnTemplate.attachAllowPolicy("PolicyEBS", refIR, ebsStatements())
}
if api.IsEnabled(iamConfig.WithAddonPolicies.FSX) {
cfnTemplate.attachAllowPolicy("PolicyFSX", refIR, fsxStatements())
cfnTemplate.attachAllowPolicy("PolicyServiceLinkRole", refIR, serviceLinkRoleStatements())
}
if api.IsEnabled(iamConfig.WithAddonPolicies.EFS) {
cfnTemplate.attachAllowPolicy("PolicyEFS", refIR, efsStatements())
cfnTemplate.attachAllowPolicy("PolicyEFSEC2", refIR, efsEc2Statements())
}
if api.IsEnabled(iamConfig.WithAddonPolicies.AWSLoadBalancerController) {
cfnTemplate.attachAllowPolicy("PolicyAWSLoadBalancerController", refIR, loadBalancerControllerStatements())
}
if api.IsEnabled(iamConfig.WithAddonPolicies.XRay) {
cfnTemplate.attachAllowPolicy("PolicyXRay", refIR, xRayStatements())
}
return nil
}
func makeManagedPolicies(iamCluster *api.ClusterIAM, iamConfig *api.NodeGroupIAM, managed, enableSSM, forceAddCNIPolicy bool) (*gfnt.Value, error) {
managedPolicyNames := sets.NewString()
if len(iamConfig.AttachPolicyARNs) == 0 {
managedPolicyNames.Insert(iamDefaultNodePolicies...)
if !api.IsEnabled(iamCluster.WithOIDC) || forceAddCNIPolicy {
managedPolicyNames.Insert(iamPolicyAmazonEKSCNIPolicy)
}
if managed {
// The Managed Nodegroup API requires this managed policy to be present, even though
// AmazonEC2ContainerRegistryPowerUser (attached if imageBuilder is enabled) contains a superset of the
// actions allowed by this managed policy
managedPolicyNames.Insert(iamPolicyAmazonEC2ContainerRegistryReadOnly)
}
}
if enableSSM {
managedPolicyNames.Insert(iamPolicyAmazonSSMManagedInstanceCore)
}
if api.IsEnabled(iamConfig.WithAddonPolicies.ImageBuilder) {
managedPolicyNames.Insert(iamPolicyAmazonEC2ContainerRegistryPowerUser)
} else if !managed {
// attach this policy even if `AttachPolicyARNs` is specified to preserve existing behaviour for unmanaged
// nodegroups
managedPolicyNames.Insert(iamPolicyAmazonEC2ContainerRegistryReadOnly)
}
if api.IsEnabled(iamConfig.WithAddonPolicies.CloudWatch) {
managedPolicyNames.Insert(iamPolicyCloudWatchAgentServerPolicy)
}
for _, policyARN := range iamConfig.AttachPolicyARNs {
parsedARN, err := arn.Parse(policyARN)
if err != nil {
return nil, err
}
start := strings.IndexRune(parsedARN.Resource, '/')
if start == -1 || start+1 == len(parsedARN.Resource) {
return nil, fmt.Errorf("failed to find ARN resource name: %s", parsedARN.Resource)
}
resourceName := parsedARN.Resource[start+1:]
managedPolicyNames.Delete(resourceName)
}
return gfnt.NewSlice(append(
makeStringSlice(iamConfig.AttachPolicyARNs...),
makePolicyARNs(managedPolicyNames.List()...)...,
)...), nil
}
// NormalizeARN returns the ARN with just the last element in the resource path preserved. If the
// input does not contain at least one forward-slash then the input is returned unmodified.
//
// When providing an existing instanceRoleARN that contains a path other than "/", nodes may
// fail to join the cluster as the AWS IAM Authenticator does not recognize such ARNs declared in
// the aws-auth ConfigMap.
//
// See: https://docs.aws.amazon.com/eks/latest/userguide/troubleshooting.html#troubleshoot-container-runtime-network
func NormalizeARN(arn string) string {
parts := strings.Split(arn, "/")
if len(parts) <= 1 {
return arn
}
return fmt.Sprintf("%s/%s", parts[0], parts[len(parts)-1])
}
// AbstractRoleNameFromARN returns the role name from the ARN
func AbstractRoleNameFromARN(arn string) string {
parts := strings.Split(arn, "/")
if len(parts) <= 1 {
return arn
}
return parts[len(parts)-1]
}