Skip to content

Commit 3bb1cce

Browse files
committed
aws/capi: IAM: add BYO support & user tags
Adds support for users to bring their own IAM roles. Adds support to tag IAM resources with user tags.
1 parent 3bc9ed7 commit 3bb1cce

File tree

2 files changed

+81
-33
lines changed

2 files changed

+81
-33
lines changed

pkg/infrastructure/aws/clusterapi/aws.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ type Provider struct{}
1717
// Name gives the name of the provider, AWS.
1818
func (*Provider) Name() string { return awstypes.Name }
1919

20+
// PreProvision creates the IAM roles used by all nodes in the cluster.
2021
func (*Provider) PreProvision(ctx context.Context, in clusterapi.PreProvisionInput) error {
21-
if err := putIAMRoles(ctx, in.InfraID, in.InstallConfig); err != nil {
22+
if err := createIAMRoles(ctx, in.InfraID, in.InstallConfig); err != nil {
2223
return fmt.Errorf("failed to create IAM roles: %w", err)
2324
}
2425
return nil

pkg/infrastructure/aws/clusterapi/iam.go

Lines changed: 79 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@ import (
1616
"github.com/openshift/installer/pkg/asset/installconfig"
1717
)
1818

19+
const (
20+
master = "master"
21+
worker = "worker"
22+
)
23+
1924
var (
2025
policies = map[string]*iamv1.PolicyDocument{
21-
"master": {
26+
master: {
2227
Version: "2012-10-17",
2328
Statement: []iamv1.StatementEntry{
2429
{
@@ -69,7 +74,7 @@ var (
6974
},
7075
},
7176
},
72-
"worker": {
77+
worker: {
7378
Version: "2012-10-17",
7479
Statement: []iamv1.StatementEntry{
7580
{
@@ -85,8 +90,8 @@ var (
8590
}
8691
)
8792

88-
// putIAMRoles creates the roles used by control-plane and compute nodes.
89-
func putIAMRoles(ctx context.Context, clusterID string, ic *installconfig.InstallConfig) error {
93+
// createIAMRoles creates the roles used by control-plane and compute nodes.
94+
func createIAMRoles(ctx context.Context, infraID string, ic *installconfig.InstallConfig) error {
9095
logrus.Infoln("Creating IAM roles for control-plane and compute nodes")
9196
// Create the IAM Role with the aws sdk.
9297
// https://docs.aws.amazon.com/sdk-for-go/api/service/iam/#IAM.CreateRole
@@ -97,9 +102,18 @@ func putIAMRoles(ctx context.Context, clusterID string, ic *installconfig.Instal
97102
svc := iam.New(session)
98103

99104
// Create the IAM Roles for master and workers.
100-
clusterOwnedIAMTag := &iam.Tag{
101-
Key: aws.String(fmt.Sprintf("kubernetes.io/cluster/%s", clusterID)),
102-
Value: aws.String("owned"),
105+
tags := []*iam.Tag{
106+
{
107+
Key: aws.String(fmt.Sprintf("kubernetes.io/cluster/%s", infraID)),
108+
Value: aws.String("owned"),
109+
},
110+
}
111+
112+
for k, v := range ic.Config.AWS.UserTags {
113+
tags = append(tags, &iam.Tag{
114+
Key: aws.String(k),
115+
Value: aws.String(v),
116+
})
103117
}
104118

105119
assumePolicy := &iamv1.PolicyDocument{
@@ -123,43 +137,27 @@ func putIAMRoles(ctx context.Context, clusterID string, ic *installconfig.Instal
123137
return fmt.Errorf("failed to marshal assume policy: %w", err)
124138
}
125139

126-
for _, role := range []string{"master", "worker"} {
127-
roleName := aws.String(fmt.Sprintf("%s-%s-role", clusterID, role))
128-
if _, err := svc.GetRoleWithContext(ctx, &iam.GetRoleInput{RoleName: roleName}); err != nil {
129-
var awsErr awserr.Error
130-
if errors.As(err, &awsErr) && awsErr.Code() != iam.ErrCodeNoSuchEntityException {
131-
return fmt.Errorf("failed to get %s role: %w", role, err)
132-
}
133-
// If the role does not exist, create it.
134-
createRoleInput := &iam.CreateRoleInput{
135-
RoleName: roleName,
136-
AssumeRolePolicyDocument: aws.String(string(assumePolicyBytes)),
137-
Tags: []*iam.Tag{clusterOwnedIAMTag},
138-
}
139-
if _, err := svc.CreateRoleWithContext(ctx, createRoleInput); err != nil {
140-
return fmt.Errorf("failed to create %s role: %w", role, err)
141-
}
142-
143-
if err := svc.WaitUntilRoleExistsWithContext(ctx, &iam.GetRoleInput{RoleName: roleName}); err != nil {
144-
return fmt.Errorf("failed to wait for %s role to exist: %w", role, err)
145-
}
140+
for _, role := range []string{master, worker} {
141+
roleName, err := getOrCreateIAMRole(ctx, role, infraID, string(assumePolicyBytes), *ic, tags, svc)
142+
if err != nil {
143+
return fmt.Errorf("failed to create IAM roles: %w", err)
146144
}
147145

148146
// Put the policy inline.
149-
policyName := aws.String(fmt.Sprintf("%s-%s-policy", clusterID, role))
147+
policyName := aws.String(fmt.Sprintf("%s-%s-policy", infraID, role))
150148
b, err := json.Marshal(policies[role])
151149
if err != nil {
152150
return fmt.Errorf("failed to marshal %s policy: %w", role, err)
153151
}
154152
if _, err := svc.PutRolePolicyWithContext(ctx, &iam.PutRolePolicyInput{
155153
PolicyDocument: aws.String(string(b)),
156154
PolicyName: policyName,
157-
RoleName: roleName,
155+
RoleName: aws.String(roleName),
158156
}); err != nil {
159157
return fmt.Errorf("failed to create inline policy for role %s: %w", role, err)
160158
}
161159

162-
profileName := aws.String(fmt.Sprintf("%s-%s-profile", clusterID, role))
160+
profileName := aws.String(fmt.Sprintf("%s-%s-profile", infraID, role))
163161
if _, err := svc.GetInstanceProfileWithContext(ctx, &iam.GetInstanceProfileInput{InstanceProfileName: profileName}); err != nil {
164162
var awsErr awserr.Error
165163
if errors.As(err, &awsErr) && awsErr.Code() != iam.ErrCodeNoSuchEntityException {
@@ -168,7 +166,7 @@ func putIAMRoles(ctx context.Context, clusterID string, ic *installconfig.Instal
168166
// If the profile does not exist, create it.
169167
if _, err := svc.CreateInstanceProfileWithContext(ctx, &iam.CreateInstanceProfileInput{
170168
InstanceProfileName: profileName,
171-
Tags: []*iam.Tag{clusterOwnedIAMTag},
169+
Tags: tags,
172170
}); err != nil {
173171
return fmt.Errorf("failed to create %s instance profile: %w", role, err)
174172
}
@@ -179,7 +177,7 @@ func putIAMRoles(ctx context.Context, clusterID string, ic *installconfig.Instal
179177
// Finally, attach the role to the profile.
180178
if _, err := svc.AddRoleToInstanceProfileWithContext(ctx, &iam.AddRoleToInstanceProfileInput{
181179
InstanceProfileName: profileName,
182-
RoleName: roleName,
180+
RoleName: aws.String(roleName),
183181
}); err != nil {
184182
return fmt.Errorf("failed to add %s role to instance profile: %w", role, err)
185183
}
@@ -189,6 +187,55 @@ func putIAMRoles(ctx context.Context, clusterID string, ic *installconfig.Instal
189187
return nil
190188
}
191189

190+
// getOrCreateRole returns the name of the IAM role to be used,
191+
// creating it when not specified by the user in the install config.
192+
func getOrCreateIAMRole(ctx context.Context, nodeRole, infraID, assumePolicy string, ic installconfig.InstallConfig, tags []*iam.Tag, svc *iam.IAM) (string, error) {
193+
roleName := aws.String(fmt.Sprintf("%s-%s-role", infraID, nodeRole))
194+
195+
var defaultRole string
196+
if dmp := ic.Config.AWS.DefaultMachinePlatform; dmp != nil && len(dmp.IAMRole) > 0 {
197+
defaultRole = dmp.IAMRole
198+
}
199+
200+
masterRole := defaultRole
201+
if cp := ic.Config.ControlPlane; cp != nil && cp.Platform.AWS != nil && len(cp.Platform.AWS.IAMRole) > 0 {
202+
masterRole = cp.Platform.AWS.IAMRole
203+
}
204+
205+
workerRole := defaultRole
206+
if w := ic.Config.Compute; len(w) > 0 && w[0].Platform.AWS != nil && len(w[0].Platform.AWS.IAMRole) > 0 {
207+
workerRole = w[0].Platform.AWS.IAMRole
208+
}
209+
210+
switch {
211+
case nodeRole == master && len(masterRole) > 0:
212+
return masterRole, nil
213+
case nodeRole == worker && len(workerRole) > 0:
214+
return workerRole, nil
215+
}
216+
217+
if _, err := svc.GetRoleWithContext(ctx, &iam.GetRoleInput{RoleName: roleName}); err != nil {
218+
var awsErr awserr.Error
219+
if errors.As(err, &awsErr) && awsErr.Code() != iam.ErrCodeNoSuchEntityException {
220+
return "", fmt.Errorf("failed to get %s role: %w", nodeRole, err)
221+
}
222+
// If the role does not exist, create it.
223+
createRoleInput := &iam.CreateRoleInput{
224+
RoleName: roleName,
225+
AssumeRolePolicyDocument: aws.String(assumePolicy),
226+
Tags: tags,
227+
}
228+
if _, err := svc.CreateRoleWithContext(ctx, createRoleInput); err != nil {
229+
return "", fmt.Errorf("failed to create %s role: %w", nodeRole, err)
230+
}
231+
232+
if err := svc.WaitUntilRoleExistsWithContext(ctx, &iam.GetRoleInput{RoleName: roleName}); err != nil {
233+
return "", fmt.Errorf("failed to wait for %s role to exist: %w", nodeRole, err)
234+
}
235+
}
236+
return *roleName, nil
237+
}
238+
192239
func getPartitionService(region string) string {
193240
partitionDNSSuffix := "amazonaws.com"
194241
if ps, found := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), region); found {

0 commit comments

Comments
 (0)