Skip to content

Commit 8bc341b

Browse files
suvaanshkumarEricaJ6
authored andcommitted
Add aws irsa support
Signed-off-by: Erica Jin <[email protected]>
1 parent 865ae06 commit 8bc341b

File tree

14 files changed

+831
-27
lines changed

14 files changed

+831
-27
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ kubectl edit klusterlet klusterlet
169169

170170
### Integration tests
171171

172-
The integration tests are written in the [test/integration](test/integration) directory. They start a kubenretes
172+
The integration tests are written in the [test/integration](test/integration) directory. They start a kubernetes
173173
api server locally with [controller-runtime](https://book.kubebuilder.io/reference/envtest), and run the tests against
174174
the local api server.
175175

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ spec:
109109
{{if .AppliedManifestWorkEvictionGracePeriod}}
110110
- "--appliedmanifestwork-eviction-grace-period={{ .AppliedManifestWorkEvictionGracePeriod }}"
111111
{{end}}
112+
{{if .RegistrationDriver.AuthType}}
113+
- "--registration-auth={{ .RegistrationDriver.AuthType }}"
114+
{{end}}
115+
{{if eq .RegistrationDriver.AuthType "awsirsa"}}
116+
- "--hub-cluster-arn={{ .RegistrationDriver.AwsIrsa.HubClusterArn }}"
117+
{{end}}
112118
env:
113119
- name: POD_NAME
114120
valueFrom:

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ spec:
9797
{{if gt .RegistrationKubeAPIBurst 0}}
9898
- "--kube-api-burst={{ .RegistrationKubeAPIBurst }}"
9999
{{end}}
100+
{{if .RegistrationDriver.AuthType}}
101+
- "--registration-auth={{ .RegistrationDriver.AuthType }}"
102+
{{end}}
103+
{{if eq .RegistrationDriver.AuthType "awsirsa"}}
104+
- "--hub-cluster-arn={{ .RegistrationDriver.AwsIrsa.HubClusterArn }}"
105+
{{end}}
100106
env:
101107
- name: POD_NAME
102108
valueFrom:

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const (
4141
klusterletFinalizer = "operator.open-cluster-management.io/klusterlet-cleanup"
4242
managedResourcesEvictionTimestampAnno = "operator.open-cluster-management.io/managed-resources-eviction-timestamp"
4343
klusterletNamespaceLabelKey = "operator.open-cluster-management.io/klusterlet"
44+
AwsIrsaAuthType = "awsirsa"
4445
)
4546

4647
type klusterletController struct {
@@ -110,7 +111,14 @@ func NewKlusterletController(
110111
WithInformersQueueKeysFunc(queue.QueueKeyByMetaName, klusterletInformer.Informer()).
111112
ToController("KlusterletController", recorder)
112113
}
114+
type AwsIrsa struct {
115+
HubClusterArn string
116+
}
113117

118+
type RegistrationDriver struct {
119+
AuthType string
120+
AwsIrsa *AwsIrsa
121+
}
114122
// klusterletConfig is used to render the template of hub manifests
115123
type klusterletConfig struct {
116124
KlusterletName string
@@ -175,6 +183,7 @@ type klusterletConfig struct {
175183

176184
// Labels of the agents are synced from klusterlet CR.
177185
Labels map[string]string
186+
RegistrationDriver RegistrationDriver
178187
}
179188

180189
// If multiplehubs feature gate is enabled, using the bootstrapkubeconfigs from klusterlet CR.
@@ -309,7 +318,19 @@ func (n *klusterletController) sync(ctx context.Context, controllerContext facto
309318
config.ClientCertExpirationSeconds = klusterlet.Spec.RegistrationConfiguration.ClientCertExpirationSeconds
310319
config.RegistrationKubeAPIQPS = float32(klusterlet.Spec.RegistrationConfiguration.KubeAPIQPS)
311320
config.RegistrationKubeAPIBurst = klusterlet.Spec.RegistrationConfiguration.KubeAPIBurst
312-
321+
//Configuring Registration driver depending on registration auth
322+
if &klusterlet.Spec.RegistrationConfiguration.RegistrationDriver != nil && klusterlet.Spec.RegistrationConfiguration.RegistrationDriver.AuthType == AwsIrsaAuthType {
323+
config.RegistrationDriver = RegistrationDriver{
324+
AuthType: klusterlet.Spec.RegistrationConfiguration.RegistrationDriver.AuthType,
325+
AwsIrsa: &AwsIrsa{
326+
HubClusterArn: klusterlet.Spec.RegistrationConfiguration.RegistrationDriver.AwsIrsa.HubClusterArn,
327+
},
328+
}
329+
} else {
330+
config.RegistrationDriver = RegistrationDriver{
331+
AuthType: klusterlet.Spec.RegistrationConfiguration.RegistrationDriver.AuthType,
332+
}
333+
}
313334
// construct cluster annotations string, the final format is "key1=value1,key2=value2"
314335
var annotationsArray []string
315336
for k, v := range commonhelpers.FilterClusterAnnotations(klusterlet.Spec.RegistrationConfiguration.ClusterAnnotations) {

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

Lines changed: 112 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ func newKlusterlet(name, namespace, clustername string) *operatorapiv1.Klusterle
134134

135135
func newKlusterletHosted(name, namespace, clustername string) *operatorapiv1.Klusterlet {
136136
klusterlet := newKlusterlet(name, namespace, clustername)
137+
klusterlet.Spec.RegistrationConfiguration.RegistrationDriver = operatorapiv1.RegistrationDriver{}
137138
klusterlet.Spec.DeployOption.Mode = operatorapiv1.InstallModeHosted
138139
klusterlet.Finalizers = append(klusterlet.Finalizers, klusterletHostedFinalizer)
139140
return klusterlet
@@ -374,7 +375,47 @@ func getDeployments(actions []clienttesting.Action, verb, suffix string) *appsv1
374375
return nil
375376
}
376377

377-
func assertRegistrationDeployment(t *testing.T, actions []clienttesting.Action, verb, serverURL, clusterName string, replica int32) {
378+
func assertKlusterletDeployment(t *testing.T, actions []clienttesting.Action, verb, serverURL, clusterName string) {
379+
deployment := getDeployments(actions, verb, "agent")
380+
if deployment == nil {
381+
t.Errorf("klusterlet deployment not found")
382+
return
383+
}
384+
if len(deployment.Spec.Template.Spec.Containers) != 1 {
385+
t.Errorf("Expect 1 containers in deployment spec, actual %d", len(deployment.Spec.Template.Spec.Containers))
386+
return
387+
}
388+
389+
args := deployment.Spec.Template.Spec.Containers[0].Args
390+
expectedArgs := []string{
391+
"/registration-operator",
392+
"agent",
393+
fmt.Sprintf("--spoke-cluster-name=%s", clusterName),
394+
"--bootstrap-kubeconfig=/spoke/bootstrap/kubeconfig",
395+
}
396+
397+
if serverURL != "" {
398+
expectedArgs = append(expectedArgs, fmt.Sprintf("--spoke-external-server-urls=%s", serverURL))
399+
}
400+
401+
expectedArgs = append(expectedArgs, "--agent-id=", "--workload-source-driver=kube", "--workload-source-config=/spoke/hub-kubeconfig/kubeconfig")
402+
403+
if *deployment.Spec.Replicas == 1 {
404+
expectedArgs = append(expectedArgs, "--disable-leader-election")
405+
}
406+
expectedArgs = append(expectedArgs, "--status-sync-interval=60s")
407+
expectedArgs = append(expectedArgs, "--kube-api-qps=20", "--kube-api-burst=60")
408+
expectedArgs = append(expectedArgs, "--registration-auth=awsirsa", "--hub-cluster-arn=arneks:us-west-2:123456789012:cluster/hub-cluster1")
409+
410+
if !equality.Semantic.DeepEqual(args, expectedArgs) {
411+
t.Errorf("Expect args %v, but got %v", expectedArgs, args)
412+
return
413+
}
414+
415+
}
416+
417+
418+
func assertRegistrationDeployment(t *testing.T, actions []clienttesting.Action, verb, serverURL, clusterName string, replica int32, awsAuth bool) {
378419
deployment := getDeployments(actions, verb, "registration-agent")
379420
if deployment == nil {
380421
t.Errorf("registration deployment not found")
@@ -402,7 +443,9 @@ func assertRegistrationDeployment(t *testing.T, actions []clienttesting.Action,
402443
}
403444

404445
expectedArgs = append(expectedArgs, "--kube-api-qps=10", "--kube-api-burst=60")
405-
446+
if awsAuth {
447+
expectedArgs = append(expectedArgs, "--registration-auth=awsirsa", "--hub-cluster-arn=arneks:us-west-2:123456789012:cluster/hub-cluster1")
448+
}
406449
if !equality.Semantic.DeepEqual(args, expectedArgs) {
407450
t.Errorf("Expect args %v, but got %v", expectedArgs, args)
408451
return
@@ -944,6 +987,68 @@ func TestGetServersFromKlusterlet(t *testing.T) {
944987
}
945988
}
946989

990+
func TestAWSIrsaAuthInSingletonMode(t *testing.T) {
991+
klusterlet := newKlusterlet("klusterlet", "testns", "cluster1")
992+
awsIrsaRegistrationDriver := operatorapiv1.RegistrationDriver{
993+
AuthType: AwsIrsaAuthType,
994+
AwsIrsa: &operatorapiv1.AwsIrsa{
995+
HubClusterArn: "arneks:us-west-2:123456789012:cluster/hub-cluster1",
996+
},
997+
}
998+
klusterlet.Spec.RegistrationConfiguration.RegistrationDriver = awsIrsaRegistrationDriver
999+
klusterlet.Spec.DeployOption.Mode = operatorapiv1.InstallModeSingleton
1000+
hubSecret := newSecret(helpers.HubKubeConfig, "testns")
1001+
hubSecret.Data["kubeconfig"] = []byte("dummuykubeconnfig")
1002+
hubSecret.Data["cluster-name"] = []byte("cluster1")
1003+
objects := []runtime.Object{
1004+
newNamespace("testns"),
1005+
newSecret(helpers.BootstrapHubKubeConfig, "testns"),
1006+
hubSecret,
1007+
}
1008+
1009+
syncContext := testingcommon.NewFakeSyncContext(t, "klusterlet")
1010+
controller := newTestController(t, klusterlet, syncContext.Recorder(), nil, false,
1011+
objects...)
1012+
1013+
err := controller.controller.sync(context.TODO(), syncContext)
1014+
if err != nil {
1015+
t.Errorf("Expected non error when sync, %v", err)
1016+
}
1017+
1018+
assertKlusterletDeployment(t, controller.kubeClient.Actions(), createVerb, "", "cluster1")
1019+
}
1020+
1021+
func TestAWSIrsaAuthInNonSingletonMode(t *testing.T) {
1022+
klusterlet := newKlusterlet("klusterlet", "testns", "cluster1")
1023+
awsIrsaRegistrationDriver := operatorapiv1.RegistrationDriver{
1024+
AuthType: AwsIrsaAuthType,
1025+
AwsIrsa: &operatorapiv1.AwsIrsa{
1026+
HubClusterArn: "arneks:us-west-2:123456789012:cluster/hub-cluster1",
1027+
},
1028+
}
1029+
klusterlet.Spec.RegistrationConfiguration.RegistrationDriver = awsIrsaRegistrationDriver
1030+
hubSecret := newSecret(helpers.HubKubeConfig, "testns")
1031+
hubSecret.Data["kubeconfig"] = []byte("dummuykubeconnfig")
1032+
hubSecret.Data["cluster-name"] = []byte("cluster1")
1033+
objects := []runtime.Object{
1034+
newNamespace("testns"),
1035+
newSecret(helpers.BootstrapHubKubeConfig, "testns"),
1036+
hubSecret,
1037+
}
1038+
1039+
syncContext := testingcommon.NewFakeSyncContext(t, "klusterlet")
1040+
controller := newTestController(t, klusterlet, syncContext.Recorder(), nil, false,
1041+
objects...)
1042+
1043+
err := controller.controller.sync(context.TODO(), syncContext)
1044+
if err != nil {
1045+
t.Errorf("Expected non error when sync, %v", err)
1046+
}
1047+
1048+
assertRegistrationDeployment(t, controller.kubeClient.Actions(), createVerb, "", "cluster1", 1, true)
1049+
}
1050+
1051+
9471052
func TestReplica(t *testing.T) {
9481053
klusterlet := newKlusterlet("klusterlet", "testns", "cluster1")
9491054
hubSecret := newSecret(helpers.HubKubeConfig, "testns")
@@ -965,7 +1070,7 @@ func TestReplica(t *testing.T) {
9651070
}
9661071

9671072
// should have 1 replica for registration deployment and 0 for work
968-
assertRegistrationDeployment(t, controller.kubeClient.Actions(), createVerb, "", "cluster1", 1)
1073+
assertRegistrationDeployment(t, controller.kubeClient.Actions(), createVerb, "", "cluster1", 1, false)
9691074
assertWorkDeployment(t, controller.kubeClient.Actions(), createVerb, "cluster1", operatorapiv1.InstallModeDefault, 0)
9701075

9711076
klusterlet = newKlusterlet("klusterlet", "testns", "cluster1")
@@ -1010,7 +1115,7 @@ func TestReplica(t *testing.T) {
10101115
}
10111116

10121117
// should have 3 replicas for clusters with multiple nodes
1013-
assertRegistrationDeployment(t, controller.kubeClient.Actions(), "update", "", "cluster1", 3)
1118+
assertRegistrationDeployment(t, controller.kubeClient.Actions(), "update", "", "cluster1", 3 ,false)
10141119
assertWorkDeployment(t, controller.kubeClient.Actions(), "update", "cluster1", operatorapiv1.InstallModeDefault, 3)
10151120
}
10161121

@@ -1031,7 +1136,7 @@ func TestClusterNameChange(t *testing.T) {
10311136
}
10321137

10331138
// Check if deployment has the right cluster name set
1034-
assertRegistrationDeployment(t, controller.kubeClient.Actions(), createVerb, "", "cluster1", 1)
1139+
assertRegistrationDeployment(t, controller.kubeClient.Actions(), createVerb, "", "cluster1", 1 ,false)
10351140

10361141
operatorAction := controller.operatorClient.Actions()
10371142
testingcommon.AssertActions(t, operatorAction, "patch")
@@ -1061,7 +1166,7 @@ func TestClusterNameChange(t *testing.T) {
10611166
if err != nil {
10621167
t.Errorf("Expected non error when sync, %v", err)
10631168
}
1064-
assertRegistrationDeployment(t, controller.kubeClient.Actions(), "update", "", "", 1)
1169+
assertRegistrationDeployment(t, controller.kubeClient.Actions(), "update", "", "", 1, false)
10651170

10661171
// Update hubconfigsecret and sync again
10671172
hubSecret.Data["cluster-name"] = []byte("cluster2")
@@ -1099,7 +1204,7 @@ func TestClusterNameChange(t *testing.T) {
10991204
if err != nil {
11001205
t.Errorf("Expected non error when sync, %v", err)
11011206
}
1102-
assertRegistrationDeployment(t, controller.kubeClient.Actions(), "update", "https://localhost", "cluster3", 1)
1207+
assertRegistrationDeployment(t, controller.kubeClient.Actions(), "update", "https://localhost", "cluster3", 1, false)
11031208
assertWorkDeployment(t, controller.kubeClient.Actions(), "update", "cluster3", "", 0)
11041209
}
11051210

pkg/registration/helpers/helpers.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package helpers
33
import (
44
"embed"
55
"net/url"
6+
"regexp"
67

78
"github.com/openshift/library-go/pkg/assets"
89
"github.com/openshift/library-go/pkg/operator/resource/resourceapply"
@@ -158,3 +159,12 @@ func IsCSRSupported(nativeClient kubernetes.Interface) (bool, bool, error) {
158159
}
159160
return v1CSRSupported, v1beta1CSRSupported, nil
160161
}
162+
163+
func IsEksArnWellFormed(eksArn string) bool {
164+
pattern := "^arn:aws:eks:([a-zA-Z0-9-]+):(\\d{12}):cluster/([a-zA-Z0-9-]+)$"
165+
matched, err := regexp.MatchString(pattern, eksArn)
166+
if err != nil {
167+
return false
168+
}
169+
return matched
170+
}

pkg/registration/helpers/testing/testinghelpers.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,10 @@ type TestCert struct {
421421
Key []byte
422422
}
423423

424+
type TestIrsaRequest struct {
425+
}
426+
427+
424428
func NewHubKubeconfigSecret(namespace, name, resourceVersion string, cert *TestCert, data map[string][]byte) *corev1.Secret {
425429
secret := &corev1.Secret{
426430
ObjectMeta: metav1.ObjectMeta{
@@ -512,3 +516,4 @@ func NewManagedClusterAddons(name, namespace string, finalizers []string, deleti
512516
},
513517
}
514518
}
519+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package aws_irsa
2+
3+
import (
4+
"context"
5+
"fmt"
6+
apierrors "k8s.io/apimachinery/pkg/api/errors"
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"k8s.io/client-go/tools/cache"
9+
cluster "open-cluster-management.io/api/client/cluster/clientset/versioned"
10+
managedclusterv1client "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1"
11+
managedclusterinformers "open-cluster-management.io/api/client/cluster/informers/externalversions/cluster"
12+
managedclusterv1lister "open-cluster-management.io/api/client/cluster/listers/cluster/v1"
13+
)
14+
15+
type AWSIRSAControl interface {
16+
isApproved(name string) (bool, error)
17+
generateEKSKubeConfig(name string) ([]byte, error)
18+
19+
// Informer is public so we can add indexer outside
20+
Informer() cache.SharedIndexInformer
21+
}
22+
23+
var _ AWSIRSAControl = &v1AWSIRSAControl{}
24+
25+
type v1AWSIRSAControl struct {
26+
hubManagedClusterInformer cache.SharedIndexInformer
27+
hubManagedClusterLister managedclusterv1lister.ManagedClusterLister
28+
hubManagedClusterClient managedclusterv1client.ManagedClusterInterface
29+
}
30+
31+
func (v *v1AWSIRSAControl) isApproved(name string) (bool, error) {
32+
// TODO: check if the managedclusuter cr on hub has required condition and is approved
33+
approved := false
34+
35+
return approved, nil
36+
}
37+
38+
func (v *v1AWSIRSAControl) generateEKSKubeConfig(name string) ([]byte, error) {
39+
// TODO: generate and return kubeconfig
40+
return nil, nil
41+
}
42+
43+
func (v *v1AWSIRSAControl) Informer() cache.SharedIndexInformer {
44+
return v.hubManagedClusterInformer
45+
}
46+
47+
func (v *v1AWSIRSAControl) get(name string) (metav1.Object, error) {
48+
managedcluster, err := v.hubManagedClusterLister.Get(name)
49+
switch {
50+
case apierrors.IsNotFound(err):
51+
// fallback to fetching managedcluster from hub apiserver in case it is not cached by informer yet
52+
managedcluster, err = v.hubManagedClusterClient.Get(context.Background(), name, metav1.GetOptions{})
53+
if apierrors.IsNotFound(err) {
54+
return nil, fmt.Errorf("unable to get managedcluster %q. It might have already been deleted", name)
55+
}
56+
case err != nil:
57+
return nil, err
58+
}
59+
return managedcluster, nil
60+
}
61+
62+
func NewAWSIRSAControl(hubManagedClusterInformer managedclusterinformers.Interface, hubManagedClusterClient cluster.Interface) (AWSIRSAControl, error) {
63+
return &v1AWSIRSAControl{
64+
hubManagedClusterInformer: hubManagedClusterInformer.V1().ManagedClusters().Informer(),
65+
hubManagedClusterLister: hubManagedClusterInformer.V1().ManagedClusters().Lister(),
66+
hubManagedClusterClient: hubManagedClusterClient.ClusterV1().ManagedClusters(),
67+
}, nil
68+
}

0 commit comments

Comments
 (0)