Skip to content

Commit a2354fb

Browse files
cPu1cPu1
cPu1
authored andcommitted
Fix reusing instanceRoleARN for nodegroups authorized with access entries
This changelist changes the design of creating access entries for self-managed nodegroups that use a pre-existing instanceRoleARN by creating the access entry resource outside of the CloudFormation stack by making a separate call to the AWS API. When deleting such a nodegroup, it's the user's responsibility to also delete the corresponding access entry when no more nodegroups are associated with it. This is because eksctl cannot tell if an access entry resource is still in use by non-eksctl created self-managed nodegroups. Self-managed nodegroups not using a pre-existing instanceRoleARN will continue to have the access entry resource in the CloudFormation stack, making delete nodegroup an atomic operation for most use cases. Fixes eksctl-io#7502
1 parent ce836e8 commit a2354fb

19 files changed

+1064
-214
lines changed

.mockery.yaml

+12
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,15 @@ packages:
3636
config:
3737
dir: "{{.InterfaceDir}}/mocks"
3838
outpkg: mocks
39+
40+
github.com/weaveworks/eksctl/pkg/cfn/manager:
41+
interfaces:
42+
NodeGroupStackManager:
43+
config:
44+
dir: "{{.InterfaceDir}}/mocks"
45+
outpkg: mocks
46+
47+
NodeGroupResourceSet:
48+
config:
49+
dir: "{{.InterfaceDir}}/mocks"
50+
outpkg: mocks

pkg/actions/nodegroup/create.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,8 @@ func (m *Manager) nodeCreationTasks(ctx context.Context, isOwnedCluster, skipEgr
254254
Parallel: true,
255255
}
256256
disableAccessEntryCreation := !m.accessEntry.IsEnabled() || updateAuthConfigMap != nil
257-
nodeGroupTasks := m.stackManager.NewUnmanagedNodeGroupTask(ctx, cfg.NodeGroups, !awsNodeUsesIRSA, skipEgressRules, disableAccessEntryCreation, vpcImporter)
258-
if nodeGroupTasks.Len() > 0 {
257+
if nodeGroupTasks := m.stackManager.NewUnmanagedNodeGroupTask(ctx, cfg.NodeGroups, !awsNodeUsesIRSA, skipEgressRules,
258+
disableAccessEntryCreation, vpcImporter); nodeGroupTasks.Len() > 0 {
259259
allNodeGroupTasks.Append(nodeGroupTasks)
260260
}
261261
managedTasks := m.stackManager.NewManagedNodeGroupTask(ctx, cfg.ManagedNodeGroups, !awsNodeUsesIRSA, vpcImporter)

pkg/actions/nodegroup/nodegroup.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ package nodegroup
33
import (
44
"time"
55

6-
"github.com/weaveworks/eksctl/pkg/accessentry"
7-
86
"github.com/aws/aws-sdk-go/aws/request"
97
"k8s.io/client-go/kubernetes"
108

9+
"github.com/weaveworks/eksctl/pkg/accessentry"
1110
api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5"
1211
"github.com/weaveworks/eksctl/pkg/cfn/builder"
1312
"github.com/weaveworks/eksctl/pkg/cfn/manager"

pkg/apis/eksctl.io/v1alpha5/access_entry.go

+25-3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,28 @@ type AccessScope struct {
4242
Namespaces []string `json:"namespaces,omitempty"`
4343
}
4444

45+
// AccessEntryType represents the type of access entry.
46+
type AccessEntryType string
47+
48+
const (
49+
// AccessEntryTypeLinux specifies the EC2 Linux access entry type.
50+
AccessEntryTypeLinux AccessEntryType = "EC2_LINUX"
51+
// AccessEntryTypeWindows specifies the Windows access entry type.
52+
AccessEntryTypeWindows AccessEntryType = "EC2_WINDOWS"
53+
// AccessEntryTypeFargateLinux specifies the Fargate Linux access entry type.
54+
AccessEntryTypeFargateLinux AccessEntryType = "FARGATE_LINUX"
55+
// AccessEntryTypeStandard specifies a standard access entry type.
56+
AccessEntryTypeStandard AccessEntryType = "STANDARD"
57+
)
58+
59+
// GetAccessEntryType returns the access entry type for the specified AMI family.
60+
func GetAccessEntryType(ng *NodeGroup) AccessEntryType {
61+
if IsWindowsImage(ng.GetAMIFamily()) {
62+
return AccessEntryTypeWindows
63+
}
64+
return AccessEntryTypeLinux
65+
}
66+
4567
type ARN arn.ARN
4668

4769
// ARN provides custom unmarshalling for an AWS ARN.
@@ -103,9 +125,9 @@ func validateAccessEntries(accessEntries []AccessEntry) error {
103125
return fmt.Errorf("%s.principalARN must be set to a valid AWS ARN", path)
104126
}
105127

106-
switch ae.Type {
107-
case "", "STANDARD":
108-
case "EC2_LINUX", "EC2_WINDOWS", "FARGATE_LINUX":
128+
switch AccessEntryType(ae.Type) {
129+
case "", AccessEntryTypeStandard:
130+
case AccessEntryTypeLinux, AccessEntryTypeWindows, AccessEntryTypeFargateLinux:
109131
if len(ae.KubernetesGroups) > 0 || ae.KubernetesUsername != "" {
110132
return fmt.Errorf("cannot specify %s.kubernetesGroups nor %s.kubernetesUsername when type is set to %s", path, path, ae.Type)
111133
}

pkg/cfn/builder/iam.go

+15-14
Original file line numberDiff line numberDiff line change
@@ -124,30 +124,31 @@ func (n *NodeGroupResourceSet) WithNamedIAM() bool {
124124
}
125125

126126
func (n *NodeGroupResourceSet) addResourcesForIAM(ctx context.Context) error {
127-
if n.spec.IAM.InstanceProfileARN != "" {
127+
nodeGroupIAM := n.options.NodeGroup.IAM
128+
if nodeGroupIAM.InstanceProfileARN != "" {
128129
n.rs.withIAM = false
129130
n.rs.withNamedIAM = false
130131

131132
// if instance profile is given, as well as the role, we simply use both and export the role
132133
// (which is needed in order to authorise the nodegroup)
133-
n.instanceProfileARN = gfnt.NewString(n.spec.IAM.InstanceProfileARN)
134-
if n.spec.IAM.InstanceRoleARN != "" {
135-
n.rs.defineOutputWithoutCollector(outputs.NodeGroupInstanceProfileARN, n.spec.IAM.InstanceProfileARN, true)
136-
n.rs.defineOutputWithoutCollector(outputs.NodeGroupInstanceRoleARN, n.spec.IAM.InstanceRoleARN, true)
134+
n.instanceProfileARN = gfnt.NewString(nodeGroupIAM.InstanceProfileARN)
135+
if nodeGroupIAM.InstanceRoleARN != "" {
136+
n.rs.defineOutputWithoutCollector(outputs.NodeGroupInstanceProfileARN, nodeGroupIAM.InstanceProfileARN, true)
137+
n.rs.defineOutputWithoutCollector(outputs.NodeGroupInstanceRoleARN, nodeGroupIAM.InstanceRoleARN, true)
137138
return nil
138139
}
139140
// if instance role is not given, export profile and use the getter to call importer function
140-
n.rs.defineOutput(outputs.NodeGroupInstanceProfileARN, n.spec.IAM.InstanceProfileARN, true, func(v string) error {
141-
return iam.ImportInstanceRoleFromProfileARN(ctx, n.iamAPI, n.spec, v)
141+
n.rs.defineOutput(outputs.NodeGroupInstanceProfileARN, nodeGroupIAM.InstanceProfileARN, true, func(v string) error {
142+
return iam.ImportInstanceRoleFromProfileARN(ctx, n.iamAPI, n.options.NodeGroup, v)
142143
})
143144

144145
return nil
145146
}
146147

147148
n.rs.withIAM = true
148149

149-
if n.spec.IAM.InstanceRoleARN != "" {
150-
roleARN := NormalizeARN(n.spec.IAM.InstanceRoleARN)
150+
if nodeGroupIAM.InstanceRoleARN != "" {
151+
roleARN := NormalizeARN(nodeGroupIAM.InstanceRoleARN)
151152

152153
// if role is set, but profile isn't - create profile
153154
n.newResource(cfnIAMInstanceProfileName, &gfniam.InstanceProfile{
@@ -156,7 +157,7 @@ func (n *NodeGroupResourceSet) addResourcesForIAM(ctx context.Context) error {
156157
})
157158
n.instanceProfileARN = gfnt.MakeFnGetAttString(cfnIAMInstanceProfileName, "Arn")
158159
n.rs.defineOutputFromAtt(outputs.NodeGroupInstanceProfileARN, cfnIAMInstanceProfileName, "Arn", true, func(v string) error {
159-
n.spec.IAM.InstanceProfileARN = v
160+
nodeGroupIAM.InstanceProfileARN = v
160161
return nil
161162
})
162163
n.rs.defineOutputWithoutCollector(outputs.NodeGroupInstanceRoleARN, roleARN, true)
@@ -165,12 +166,12 @@ func (n *NodeGroupResourceSet) addResourcesForIAM(ctx context.Context) error {
165166

166167
// if neither role nor profile is given - create both
167168

168-
if n.spec.IAM.InstanceRoleName != "" {
169+
if nodeGroupIAM.InstanceRoleName != "" {
169170
// setting role name requires additional capabilities
170171
n.rs.withNamedIAM = true
171172
}
172173

173-
if err := createRole(n.rs, n.clusterSpec.IAM, n.spec.IAM, false, n.forceAddCNIPolicy); err != nil {
174+
if err := createRole(n.rs, n.options.ClusterConfig.IAM, nodeGroupIAM, false, n.options.ForceAddCNIPolicy); err != nil {
174175
return err
175176
}
176177

@@ -181,11 +182,11 @@ func (n *NodeGroupResourceSet) addResourcesForIAM(ctx context.Context) error {
181182
n.instanceProfileARN = gfnt.MakeFnGetAttString(cfnIAMInstanceProfileName, "Arn")
182183

183184
n.rs.defineOutputFromAtt(outputs.NodeGroupInstanceProfileARN, cfnIAMInstanceProfileName, "Arn", true, func(v string) error {
184-
n.spec.IAM.InstanceProfileARN = v
185+
nodeGroupIAM.InstanceProfileARN = v
185186
return nil
186187
})
187188
n.rs.defineOutputFromAtt(outputs.NodeGroupInstanceRoleARN, cfnIAMInstanceRoleName, "Arn", true, func(v string) error {
188-
n.spec.IAM.InstanceRoleARN = v
189+
nodeGroupIAM.InstanceRoleARN = v
189190
return nil
190191
})
191192
return nil

0 commit comments

Comments
 (0)