Skip to content

Commit 3bc9ed7

Browse files
committed
pkg/infrastructure/aws: PreProvision IAM Roles
Creates the IAM roles that will be used by the control-plane & compute nodes.
1 parent a9da892 commit 3bc9ed7

File tree

3 files changed

+209
-4
lines changed

3 files changed

+209
-4
lines changed

pkg/asset/manifests/clusterapi/cluster.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,6 @@ func (c *Cluster) Generate(dependencies asset.Parents) error {
9696
var out *capiutils.GenerateClusterAssetsOutput
9797
switch platform := installConfig.Config.Platform.Name(); platform {
9898
case awstypes.Name:
99-
// Move this somewhere else.
100-
// if err := aws.PutIAMRoles(clusterID.InfraID, installConfig); err != nil {
101-
// return errors.Wrap(err, "failed to create IAM roles")
102-
// }
10399
var err error
104100
out, err = aws.GenerateClusterAssets(installConfig, clusterID)
105101
if err != nil {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
package clusterapi
22

33
import (
4+
"context"
5+
"fmt"
6+
47
"github.com/openshift/installer/pkg/infrastructure/clusterapi"
58
awstypes "github.com/openshift/installer/pkg/types/aws"
69
)
710

811
var _ clusterapi.Provider = (*Provider)(nil)
12+
var _ clusterapi.PreProvider = (*Provider)(nil)
913

1014
// Provider implements AWS CAPI installation.
1115
type Provider struct{}
1216

1317
// Name gives the name of the provider, AWS.
1418
func (*Provider) Name() string { return awstypes.Name }
19+
20+
func (*Provider) PreProvision(ctx context.Context, in clusterapi.PreProvisionInput) error {
21+
if err := putIAMRoles(ctx, in.InfraID, in.InstallConfig); err != nil {
22+
return fmt.Errorf("failed to create IAM roles: %w", err)
23+
}
24+
return nil
25+
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package clusterapi
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
9+
"github.com/aws/aws-sdk-go/aws"
10+
"github.com/aws/aws-sdk-go/aws/awserr"
11+
"github.com/aws/aws-sdk-go/aws/endpoints"
12+
"github.com/aws/aws-sdk-go/service/iam"
13+
"github.com/sirupsen/logrus"
14+
iamv1 "sigs.k8s.io/cluster-api-provider-aws/v2/iam/api/v1beta1"
15+
16+
"github.com/openshift/installer/pkg/asset/installconfig"
17+
)
18+
19+
var (
20+
policies = map[string]*iamv1.PolicyDocument{
21+
"master": {
22+
Version: "2012-10-17",
23+
Statement: []iamv1.StatementEntry{
24+
{
25+
Effect: "Allow",
26+
Action: []string{
27+
"ec2:AttachVolume",
28+
"ec2:AuthorizeSecurityGroupIngress",
29+
"ec2:CreateSecurityGroup",
30+
"ec2:CreateTags",
31+
"ec2:CreateVolume",
32+
"ec2:DeleteSecurityGroup",
33+
"ec2:DeleteVolume",
34+
"ec2:Describe*",
35+
"ec2:DetachVolume",
36+
"ec2:ModifyInstanceAttribute",
37+
"ec2:ModifyVolume",
38+
"ec2:RevokeSecurityGroupIngress",
39+
"elasticloadbalancing:AddTags",
40+
"elasticloadbalancing:AttachLoadBalancerToSubnets",
41+
"elasticloadbalancing:ApplySecurityGroupsToLoadBalancer",
42+
"elasticloadbalancing:CreateListener",
43+
"elasticloadbalancing:CreateLoadBalancer",
44+
"elasticloadbalancing:CreateLoadBalancerPolicy",
45+
"elasticloadbalancing:CreateLoadBalancerListeners",
46+
"elasticloadbalancing:CreateTargetGroup",
47+
"elasticloadbalancing:ConfigureHealthCheck",
48+
"elasticloadbalancing:DeleteListener",
49+
"elasticloadbalancing:DeleteLoadBalancer",
50+
"elasticloadbalancing:DeleteLoadBalancerListeners",
51+
"elasticloadbalancing:DeleteTargetGroup",
52+
"elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
53+
"elasticloadbalancing:DeregisterTargets",
54+
"elasticloadbalancing:Describe*",
55+
"elasticloadbalancing:DetachLoadBalancerFromSubnets",
56+
"elasticloadbalancing:ModifyListener",
57+
"elasticloadbalancing:ModifyLoadBalancerAttributes",
58+
"elasticloadbalancing:ModifyTargetGroup",
59+
"elasticloadbalancing:ModifyTargetGroupAttributes",
60+
"elasticloadbalancing:RegisterInstancesWithLoadBalancer",
61+
"elasticloadbalancing:RegisterTargets",
62+
"elasticloadbalancing:SetLoadBalancerPoliciesForBackendServer",
63+
"elasticloadbalancing:SetLoadBalancerPoliciesOfListener",
64+
"kms:DescribeKey",
65+
},
66+
Resource: iamv1.Resources{
67+
"*",
68+
},
69+
},
70+
},
71+
},
72+
"worker": {
73+
Version: "2012-10-17",
74+
Statement: []iamv1.StatementEntry{
75+
{
76+
Effect: "Allow",
77+
Action: iamv1.Actions{
78+
"ec2:DescribeInstances",
79+
"ec2:DescribeRegions",
80+
},
81+
Resource: iamv1.Resources{"*"},
82+
},
83+
},
84+
},
85+
}
86+
)
87+
88+
// putIAMRoles creates the roles used by control-plane and compute nodes.
89+
func putIAMRoles(ctx context.Context, clusterID string, ic *installconfig.InstallConfig) error {
90+
logrus.Infoln("Creating IAM roles for control-plane and compute nodes")
91+
// Create the IAM Role with the aws sdk.
92+
// https://docs.aws.amazon.com/sdk-for-go/api/service/iam/#IAM.CreateRole
93+
session, err := ic.AWS.Session(ctx)
94+
if err != nil {
95+
return fmt.Errorf("failed to load AWS session: %w", err)
96+
}
97+
svc := iam.New(session)
98+
99+
// 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"),
103+
}
104+
105+
assumePolicy := &iamv1.PolicyDocument{
106+
Version: "2012-10-17",
107+
Statement: iamv1.Statements{
108+
{
109+
Effect: "Allow",
110+
Principal: iamv1.Principals{
111+
iamv1.PrincipalService: []string{
112+
getPartitionService(ic.AWS.Region),
113+
},
114+
},
115+
Action: iamv1.Actions{
116+
"sts:AssumeRole",
117+
},
118+
},
119+
},
120+
}
121+
assumePolicyBytes, err := json.Marshal(assumePolicy)
122+
if err != nil {
123+
return fmt.Errorf("failed to marshal assume policy: %w", err)
124+
}
125+
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+
}
146+
}
147+
148+
// Put the policy inline.
149+
policyName := aws.String(fmt.Sprintf("%s-%s-policy", clusterID, role))
150+
b, err := json.Marshal(policies[role])
151+
if err != nil {
152+
return fmt.Errorf("failed to marshal %s policy: %w", role, err)
153+
}
154+
if _, err := svc.PutRolePolicyWithContext(ctx, &iam.PutRolePolicyInput{
155+
PolicyDocument: aws.String(string(b)),
156+
PolicyName: policyName,
157+
RoleName: roleName,
158+
}); err != nil {
159+
return fmt.Errorf("failed to create inline policy for role %s: %w", role, err)
160+
}
161+
162+
profileName := aws.String(fmt.Sprintf("%s-%s-profile", clusterID, role))
163+
if _, err := svc.GetInstanceProfileWithContext(ctx, &iam.GetInstanceProfileInput{InstanceProfileName: profileName}); err != nil {
164+
var awsErr awserr.Error
165+
if errors.As(err, &awsErr) && awsErr.Code() != iam.ErrCodeNoSuchEntityException {
166+
return fmt.Errorf("failed to get %s instance profile: %w", role, err)
167+
}
168+
// If the profile does not exist, create it.
169+
if _, err := svc.CreateInstanceProfileWithContext(ctx, &iam.CreateInstanceProfileInput{
170+
InstanceProfileName: profileName,
171+
Tags: []*iam.Tag{clusterOwnedIAMTag},
172+
}); err != nil {
173+
return fmt.Errorf("failed to create %s instance profile: %w", role, err)
174+
}
175+
if err := svc.WaitUntilInstanceProfileExistsWithContext(ctx, &iam.GetInstanceProfileInput{InstanceProfileName: profileName}); err != nil {
176+
return fmt.Errorf("failed to wait for %s instance profile to exist: %w", role, err)
177+
}
178+
179+
// Finally, attach the role to the profile.
180+
if _, err := svc.AddRoleToInstanceProfileWithContext(ctx, &iam.AddRoleToInstanceProfileInput{
181+
InstanceProfileName: profileName,
182+
RoleName: roleName,
183+
}); err != nil {
184+
return fmt.Errorf("failed to add %s role to instance profile: %w", role, err)
185+
}
186+
}
187+
}
188+
189+
return nil
190+
}
191+
192+
func getPartitionService(region string) string {
193+
partitionDNSSuffix := "amazonaws.com"
194+
if ps, found := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), region); found {
195+
partitionDNSSuffix = ps.DNSSuffix()
196+
}
197+
return fmt.Sprintf("ec2.%s", partitionDNSSuffix)
198+
}

0 commit comments

Comments
 (0)