Skip to content

Commit 67c342a

Browse files
Starting aws registration by spoke by assuming IAM role on startup and adding annotations to ManagedCluster CR
Signed-off-by: Erica Jin <[email protected]>
1 parent 2710e0e commit 67c342a

File tree

12 files changed

+195
-35
lines changed

12 files changed

+195
-35
lines changed

manifests/klusterlet/managed/klusterlet-registration-serviceaccount.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,9 @@ metadata:
99
"{{ $key }}": "{{ $value }}"
1010
{{ end }}
1111
{{ end }}
12+
{{ if and .ManagedClusterRoleArn (eq .RegistrationDriver.AuthType "awsirsa") }}
13+
annotations:
14+
eks.amazonaws.com/role-arn: {{ .ManagedClusterRoleArn }}
15+
{{ end }}
1216
imagePullSecrets:
1317
- name: open-cluster-management-image-pull-credentials

manifests/klusterlet/managed/klusterlet-work-serviceaccount.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,9 @@ metadata:
99
"{{ $key }}": "{{ $value }}"
1010
{{ end }}
1111
{{ end }}
12+
{{ if and .ManagedClusterRoleArn (eq .RegistrationDriver.AuthType "awsirsa") }}
13+
annotations:
14+
eks.amazonaws.com/role-arn: {{ .ManagedClusterRoleArn }}
15+
{{ end }}
1216
imagePullSecrets:
1317
- name: open-cluster-management-image-pull-credentials

manifests/klusterlet/management/klusterlet-agent-deployment.yaml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,15 @@ spec:
109109
{{if .AppliedManifestWorkEvictionGracePeriod}}
110110
- "--appliedmanifestwork-eviction-grace-period={{ .AppliedManifestWorkEvictionGracePeriod }}"
111111
{{end}}
112-
{{if .RegistrationDriver.AuthType}}
112+
{{if and .RegistrationDriver .RegistrationDriver.AuthType}}
113113
- "--registration-auth={{ .RegistrationDriver.AuthType }}"
114-
{{end}}
115114
{{if eq .RegistrationDriver.AuthType "awsirsa"}}
116115
- "--hub-cluster-arn={{ .RegistrationDriver.AwsIrsa.HubClusterArn }}"
116+
- "--managed-cluster-arn={{ .RegistrationDriver.AwsIrsa.ManagedClusterArn }}"
117+
{{if .ManagedClusterRoleSuffix}}
118+
- "--managed-cluster-role-suffix={{ .ManagedClusterRoleSuffix }}"
119+
{{end}}
120+
{{end}}
117121
{{end}}
118122
env:
119123
- name: POD_NAME
@@ -144,6 +148,10 @@ spec:
144148
mountPath: "/spoke/hub-kubeconfig"
145149
- name: tmpdir
146150
mountPath: /tmp
151+
{{if and .RegistrationDriver .RegistrationDriver.AuthType (eq .RegistrationDriver.AuthType "awsirsa")}}
152+
- name: dot-aws
153+
mountPath: /.aws
154+
{{end}}
147155
{{if eq .InstallMode "SingletonHosted"}}
148156
- name: spoke-kubeconfig-secret
149157
mountPath: "/spoke/config"
@@ -195,6 +203,10 @@ spec:
195203
medium: Memory
196204
- name: tmpdir
197205
emptyDir: { }
206+
{{if and .RegistrationDriver .RegistrationDriver.AuthType (eq .RegistrationDriver.AuthType "awsirsa")}}
207+
- name: dot-aws
208+
emptyDir: { }
209+
{{end}}
198210
{{if eq .InstallMode "SingletonHosted"}}
199211
- name: spoke-kubeconfig-secret
200212
secret:

manifests/klusterlet/management/klusterlet-registration-deployment.yaml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,15 @@ spec:
9797
{{if gt .RegistrationKubeAPIBurst 0}}
9898
- "--kube-api-burst={{ .RegistrationKubeAPIBurst }}"
9999
{{end}}
100-
{{if .RegistrationDriver.AuthType}}
100+
{{if and .RegistrationDriver .RegistrationDriver.AuthType}}
101101
- "--registration-auth={{ .RegistrationDriver.AuthType }}"
102-
{{end}}
103102
{{if eq .RegistrationDriver.AuthType "awsirsa"}}
104103
- "--hub-cluster-arn={{ .RegistrationDriver.AwsIrsa.HubClusterArn }}"
104+
- "--managed-cluster-arn={{ .RegistrationDriver.AwsIrsa.ManagedClusterArn }}"
105+
{{if .ManagedClusterRoleSuffix}}
106+
- "--managed-cluster-role-suffix={{ .ManagedClusterRoleSuffix }}"
107+
{{end}}
108+
{{end}}
105109
{{end}}
106110
env:
107111
- name: POD_NAME
@@ -132,6 +136,10 @@ spec:
132136
mountPath: "/spoke/hub-kubeconfig"
133137
- name: tmpdir
134138
mountPath: /tmp
139+
{{if and .RegistrationDriver .RegistrationDriver.AuthType (eq .RegistrationDriver.AuthType "awsirsa")}}
140+
- name: dot-aws
141+
mountPath: /.aws
142+
{{end}}
135143
{{if eq .InstallMode "Hosted"}}
136144
- name: spoke-kubeconfig-secret
137145
mountPath: "/spoke/config"
@@ -183,6 +191,10 @@ spec:
183191
medium: Memory
184192
- name: tmpdir
185193
emptyDir: { }
194+
{{if and .RegistrationDriver .RegistrationDriver.AuthType (eq .RegistrationDriver.AuthType "awsirsa")}}
195+
- name: dot-aws
196+
emptyDir: { }
197+
{{end}}
186198
{{if eq .InstallMode "Hosted"}}
187199
- name: spoke-kubeconfig-secret
188200
secret:

manifests/klusterlet/management/klusterlet-work-deployment.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ spec:
107107
readOnly: true
108108
- name: tmpdir
109109
mountPath: /tmp
110+
{{if and .RegistrationDriver .RegistrationDriver.AuthType (eq .RegistrationDriver.AuthType "awsirsa")}}
111+
- name: dot-aws
112+
mountPath: /.aws
113+
{{end}}
110114
{{if eq .InstallMode "Hosted"}}
111115
- name: spoke-kubeconfig-secret
112116
mountPath: "/spoke/config"
@@ -147,6 +151,10 @@ spec:
147151
secretName: {{ .HubKubeConfigSecret }}
148152
- name: tmpdir
149153
emptyDir: { }
154+
{{if and .RegistrationDriver .RegistrationDriver.AuthType (eq .RegistrationDriver.AuthType "awsirsa")}}
155+
- name: dot-aws
156+
emptyDir: { }
157+
{{end}}
150158
{{if eq .InstallMode "Hosted"}}
151159
- name: spoke-kubeconfig-secret
152160
secret:

pkg/common/helpers/aws.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package helpers
2+
3+
import (
4+
"regexp"
5+
)
6+
7+
func IsEksArnWellFormed(eksArn string) bool {
8+
pattern := "^arn:aws:eks:([a-zA-Z0-9-]+):(\\d{12}):cluster/([a-zA-Z0-9-]+)$"
9+
matched, err := regexp.MatchString(pattern, eksArn)
10+
if err != nil {
11+
return false
12+
}
13+
return matched
14+
}

pkg/operator/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package klusterletcontroller
22

33
import (
44
"context"
5+
"crypto/md5"
6+
"encoding/hex"
7+
er "errors"
58
"fmt"
69
"strings"
710
"time"
@@ -114,7 +117,8 @@ func NewKlusterletController(
114117
}
115118

116119
type AwsIrsa struct {
117-
HubClusterArn string
120+
HubClusterArn string
121+
ManagedClusterArn string
118122
}
119123

120124
type RegistrationDriver struct {
@@ -187,6 +191,10 @@ type klusterletConfig struct {
187191
// Labels of the agents are synced from klusterlet CR.
188192
Labels map[string]string
189193
RegistrationDriver RegistrationDriver
194+
195+
ManagedClusterArn string
196+
ManagedClusterRoleArn string
197+
ManagedClusterRoleSuffix string
190198
}
191199

192200
// If multiplehubs feature gate is enabled, using the bootstrapkubeconfigs from klusterlet CR.
@@ -329,12 +337,32 @@ func (n *klusterletController) sync(ctx context.Context, controllerContext facto
329337
//Configuring Registration driver depending on registration auth
330338
if &klusterlet.Spec.RegistrationConfiguration.RegistrationDriver != nil &&
331339
klusterlet.Spec.RegistrationConfiguration.RegistrationDriver.AuthType == AwsIrsaAuthType {
340+
341+
hubClusterArn := klusterlet.Spec.RegistrationConfiguration.RegistrationDriver.AwsIrsa.HubClusterArn
342+
managedClusterArn := klusterlet.Spec.RegistrationConfiguration.RegistrationDriver.AwsIrsa.ManagedClusterArn
343+
344+
if !commonhelpers.IsEksArnWellFormed(hubClusterArn) {
345+
errorMsg := fmt.Sprintf("HubClusterArn %s is not well formed", hubClusterArn)
346+
klog.Errorf(errorMsg)
347+
return er.New(errorMsg)
348+
}
349+
350+
if !commonhelpers.IsEksArnWellFormed(managedClusterArn) {
351+
errorMsg := fmt.Sprintf("ManagedClusterArn %s is not well formed", managedClusterArn)
352+
klog.Errorf(errorMsg)
353+
return er.New(errorMsg)
354+
}
355+
332356
config.RegistrationDriver = RegistrationDriver{
333357
AuthType: klusterlet.Spec.RegistrationConfiguration.RegistrationDriver.AuthType,
334358
AwsIrsa: &AwsIrsa{
335-
HubClusterArn: klusterlet.Spec.RegistrationConfiguration.RegistrationDriver.AwsIrsa.HubClusterArn,
359+
HubClusterArn: hubClusterArn,
360+
ManagedClusterArn: managedClusterArn,
336361
},
337362
}
363+
managedClusterRoleArn, managedClusterRoleSuffix := n.generateManagedRoleArnAndSuffix(klusterlet)
364+
config.ManagedClusterRoleArn = managedClusterRoleArn
365+
config.ManagedClusterRoleSuffix = managedClusterRoleSuffix
338366
} else {
339367
config.RegistrationDriver = RegistrationDriver{
340368
AuthType: klusterlet.Spec.RegistrationConfiguration.RegistrationDriver.AuthType,
@@ -433,6 +461,23 @@ func (n *klusterletController) sync(ctx context.Context, controllerContext facto
433461
return utilerrors.NewAggregate(errs)
434462
}
435463

464+
func (n *klusterletController) generateManagedRoleArnAndSuffix(klusterlet *operatorapiv1.Klusterlet) (string, string) {
465+
hubClusterArn := klusterlet.Spec.RegistrationConfiguration.RegistrationDriver.AwsIrsa.HubClusterArn
466+
managedClusterArn := klusterlet.Spec.RegistrationConfiguration.RegistrationDriver.AwsIrsa.ManagedClusterArn
467+
468+
hubClusterStringParts := strings.Split(hubClusterArn, ":")
469+
470+
managedClusterStringParts := strings.Split(managedClusterArn, ":")
471+
hubClusterName := strings.Split(hubClusterStringParts[5], "/")[1]
472+
hubClusterAccountId := hubClusterStringParts[4]
473+
managedClusterName := strings.Split(managedClusterStringParts[5], "/")[1]
474+
managedClusterAccountId := managedClusterStringParts[4]
475+
md5HashUniqueIdentifier := generateMd5HashUniqueIdentifier(hubClusterAccountId, hubClusterName, managedClusterAccountId, managedClusterName)
476+
//arn:aws:iam::<managed-cluster-account-id>:role/ocm-managed-cluster-<md5-hash-unique-identifier>
477+
managedClusterRoleArn := "arn:aws:iam::" + managedClusterAccountId + ":role/ocm-managed-cluster-" + md5HashUniqueIdentifier
478+
return managedClusterRoleArn, md5HashUniqueIdentifier
479+
}
480+
436481
// TODO also read CABundle from ExternalServerURLs and set into registration deployment
437482
func getServersFromKlusterlet(klusterlet *operatorapiv1.Klusterlet) string {
438483
if klusterlet.Spec.ExternalServerURLs == nil {
@@ -536,3 +581,8 @@ func serviceAccountName(suffix string, klusterlet *operatorapiv1.Klusterlet) str
536581
}
537582
return fmt.Sprintf("%s-%s", klusterlet.Name, suffix)
538583
}
584+
585+
func generateMd5HashUniqueIdentifier(hubClusterAccountId string, hubClusterName string, managedClusterAccountId string, managedClusterName string) string {
586+
hash := md5.Sum([]byte(hubClusterAccountId + "#" + hubClusterName + "#" + managedClusterAccountId + "#" + managedClusterName))
587+
return hex.EncodeToString(hash[:])
588+
}

pkg/operator/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller_test.go

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,9 @@ func assertKlusterletDeployment(t *testing.T, actions []clienttesting.Action, ve
388388
}
389389

390390
args := deployment.Spec.Template.Spec.Containers[0].Args
391+
volumeMounts := deployment.Spec.Template.Spec.Containers[0].VolumeMounts
392+
volumes := deployment.Spec.Template.Spec.Volumes
393+
391394
expectedArgs := []string{
392395
"/registration-operator",
393396
"agent",
@@ -405,14 +408,39 @@ func assertKlusterletDeployment(t *testing.T, actions []clienttesting.Action, ve
405408
expectedArgs = append(expectedArgs, "--disable-leader-election")
406409
}
407410

408-
expectedArgs = append(expectedArgs, "--status-sync-interval=60s", "--kube-api-qps=20", "--kube-api-burst=60",
409-
"--registration-auth=awsirsa", "--hub-cluster-arn=arneks:us-west-2:123456789012:cluster/hub-cluster1")
411+
expectedArgs = append(expectedArgs, "--status-sync-interval=60s", "--kube-api-qps=20", "--kube-api-burst=60")
412+
413+
expectedArgs = append(expectedArgs, "--registration-auth=awsirsa",
414+
"--hub-cluster-arn=arn:aws:eks:us-west-2:123456789012:cluster/hub-cluster1",
415+
"--managed-cluster-arn=arn:aws:eks:us-west-2:123456789012:cluster/managed-cluster1",
416+
"--managed-cluster-role-suffix=7f8141296c75f2871e3d030f85c35692")
410417

411418
if !equality.Semantic.DeepEqual(args, expectedArgs) {
412419
t.Errorf("Expect args %v, but got %v", expectedArgs, args)
413420
return
414421
}
415422

423+
assert.True(t, isDotAwsMounted(volumeMounts))
424+
assert.True(t, isDotAwsVolumePresent(volumes))
425+
426+
}
427+
428+
func isDotAwsVolumePresent(volumes []corev1.Volume) bool {
429+
for _, volume := range volumes {
430+
if volume.Name == "dot-aws" {
431+
return true
432+
}
433+
}
434+
return false
435+
}
436+
437+
func isDotAwsMounted(mounts []corev1.VolumeMount) bool {
438+
for _, mount := range mounts {
439+
if mount.Name == "dot-aws" && mount.MountPath == "/.aws" {
440+
return true
441+
}
442+
}
443+
return false
416444
}
417445

418446
func assertRegistrationDeployment(t *testing.T, actions []clienttesting.Action, verb, serverURL, clusterName string, replica int32, awsAuth bool) {
@@ -444,7 +472,9 @@ func assertRegistrationDeployment(t *testing.T, actions []clienttesting.Action,
444472

445473
expectedArgs = append(expectedArgs, "--kube-api-qps=10", "--kube-api-burst=60")
446474
if awsAuth {
447-
expectedArgs = append(expectedArgs, "--registration-auth=awsirsa", "--hub-cluster-arn=arneks:us-west-2:123456789012:cluster/hub-cluster1")
475+
expectedArgs = append(expectedArgs, "--registration-auth=awsirsa", "--hub-cluster-arn=arn:aws:eks:us-west-2:123456789012:cluster/hub-cluster1")
476+
expectedArgs = append(expectedArgs, "--managed-cluster-arn=arn:aws:eks:us-west-2:123456789012:cluster/managed-cluster1",
477+
"--managed-cluster-role-suffix=7f8141296c75f2871e3d030f85c35692")
448478
}
449479
if !equality.Semantic.DeepEqual(args, expectedArgs) {
450480
t.Errorf("Expect args %v, but got %v", expectedArgs, args)
@@ -988,18 +1018,50 @@ func TestGetServersFromKlusterlet(t *testing.T) {
9881018
}
9891019
}
9901020

1021+
func TestAWSIrsaAuthInSingletonModeWithInvalidClusterArns(t *testing.T) {
1022+
klusterlet := newKlusterlet("klusterlet", "testns", "cluster1")
1023+
awsIrsaRegistrationDriver := operatorapiv1.RegistrationDriver{
1024+
AuthType: AwsIrsaAuthType,
1025+
AwsIrsa: &operatorapiv1.AwsIrsa{
1026+
HubClusterArn: "arn:aws:bks:us-west-2:123456789012:cluster/hub-cluster1",
1027+
ManagedClusterArn: "arn:aws:eks:us-west-2:123456789012:cluster/managed-cluster1",
1028+
},
1029+
}
1030+
klusterlet.Spec.RegistrationConfiguration.RegistrationDriver = awsIrsaRegistrationDriver
1031+
klusterlet.Spec.DeployOption.Mode = operatorapiv1.InstallModeSingleton
1032+
hubSecret := newSecret(helpers.HubKubeConfig, "testns")
1033+
hubSecret.Data["kubeconfig"] = []byte("dummykubeconfig")
1034+
hubSecret.Data["cluster-name"] = []byte("cluster1")
1035+
objects := []runtime.Object{
1036+
newNamespace("testns"),
1037+
newSecret(helpers.BootstrapHubKubeConfig, "testns"),
1038+
hubSecret,
1039+
}
1040+
1041+
syncContext := testingcommon.NewFakeSyncContext(t, "klusterlet")
1042+
controller := newTestController(t, klusterlet, syncContext.Recorder(), nil, false,
1043+
objects...)
1044+
1045+
err := controller.controller.sync(context.TODO(), syncContext)
1046+
if err != nil {
1047+
assert.Equal(t, err.Error(), "HubClusterArn arn:aws:bks:us-west-2:123456789012:cluster/hub-cluster1 is not well formed")
1048+
}
1049+
1050+
}
1051+
9911052
func TestAWSIrsaAuthInSingletonMode(t *testing.T) {
9921053
klusterlet := newKlusterlet("klusterlet", "testns", "cluster1")
9931054
awsIrsaRegistrationDriver := operatorapiv1.RegistrationDriver{
9941055
AuthType: AwsIrsaAuthType,
9951056
AwsIrsa: &operatorapiv1.AwsIrsa{
996-
HubClusterArn: "arneks:us-west-2:123456789012:cluster/hub-cluster1",
1057+
HubClusterArn: "arn:aws:eks:us-west-2:123456789012:cluster/hub-cluster1",
1058+
ManagedClusterArn: "arn:aws:eks:us-west-2:123456789012:cluster/managed-cluster1",
9971059
},
9981060
}
9991061
klusterlet.Spec.RegistrationConfiguration.RegistrationDriver = awsIrsaRegistrationDriver
10001062
klusterlet.Spec.DeployOption.Mode = operatorapiv1.InstallModeSingleton
10011063
hubSecret := newSecret(helpers.HubKubeConfig, "testns")
1002-
hubSecret.Data["kubeconfig"] = []byte("dummuykubeconnfig")
1064+
hubSecret.Data["kubeconfig"] = []byte("dummykubeconfig")
10031065
hubSecret.Data["cluster-name"] = []byte("cluster1")
10041066
objects := []runtime.Object{
10051067
newNamespace("testns"),
@@ -1024,7 +1086,8 @@ func TestAWSIrsaAuthInNonSingletonMode(t *testing.T) {
10241086
awsIrsaRegistrationDriver := operatorapiv1.RegistrationDriver{
10251087
AuthType: AwsIrsaAuthType,
10261088
AwsIrsa: &operatorapiv1.AwsIrsa{
1027-
HubClusterArn: "arneks:us-west-2:123456789012:cluster/hub-cluster1",
1089+
HubClusterArn: "arn:aws:eks:us-west-2:123456789012:cluster/hub-cluster1",
1090+
ManagedClusterArn: "arn:aws:eks:us-west-2:123456789012:cluster/managed-cluster1",
10281091
},
10291092
}
10301093
klusterlet.Spec.RegistrationConfiguration.RegistrationDriver = awsIrsaRegistrationDriver

pkg/registration/helpers/helpers.go

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ package helpers
22

33
import (
44
"embed"
5-
"net/url"
6-
"regexp"
7-
85
"github.com/openshift/library-go/pkg/assets"
96
"github.com/openshift/library-go/pkg/operator/resource/resourceapply"
107
certificatesv1 "k8s.io/api/certificates/v1"
@@ -13,6 +10,7 @@ import (
1310
"k8s.io/client-go/discovery/cached/memory"
1411
"k8s.io/client-go/kubernetes"
1512
"k8s.io/client-go/restmapper"
13+
"net/url"
1614

1715
clusterv1 "open-cluster-management.io/api/cluster/v1"
1816
)
@@ -177,14 +175,3 @@ func IsCSRSupported(nativeClient kubernetes.Interface) (bool, bool, error) {
177175
}
178176
return v1CSRSupported, v1beta1CSRSupported, nil
179177
}
180-
181-
// IsEksArnWellFormed checks if the EKS cluster ARN is well-formed
182-
// Example of a well-formed ARN: arn:aws:eks:us-west-2:123456789012:cluster/my-cluster
183-
func IsEksArnWellFormed(eksArn string) bool {
184-
pattern := "^arn:aws:eks:([a-zA-Z0-9-]+):(\\d{12}):cluster/([a-zA-Z0-9-]+)$"
185-
matched, err := regexp.MatchString(pattern, eksArn)
186-
if err != nil {
187-
return false
188-
}
189-
return matched
190-
}

0 commit comments

Comments
 (0)