From 5ffb448ecd463df1aaa5a71fb3f221a22f4a1d3f Mon Sep 17 00:00:00 2001 From: kkb0318 Date: Fri, 26 Jul 2024 20:12:02 +0900 Subject: [PATCH 1/5] update api for eks support --- api/v1alpha1/irsasetup_types.go | 21 ++++++++++++++++--- charts/irsa-manager/crds/irsasetup-crd.yaml | 14 +++++++++++-- ...-manager.kkb0318.github.io_irsasetups.yaml | 14 +++++++++++-- docs/api.md | 19 +++++++++++++++-- 4 files changed, 59 insertions(+), 9 deletions(-) diff --git a/api/v1alpha1/irsasetup_types.go b/api/v1alpha1/irsasetup_types.go index 2f4910a..285810e 100644 --- a/api/v1alpha1/irsasetup_types.go +++ b/api/v1alpha1/irsasetup_types.go @@ -33,17 +33,32 @@ type IRSASetupSpec struct { // +required Cleanup bool `json:"cleanup"` - // Mode (Optional, Future Feature) Defines how the controller will operate once this feature is enabled. - // Currently unused. Planned values: + // Mode specifies the operation mode of the controller. + // Possible values: // - "selfhosted": For self-managed Kubernetes clusters. // - "eks": For Amazon EKS environments. - Mode string `json:"mode,omitempty"` + // Default: "selfhosted" + Mode SetupMode `json:"mode,omitempty"` // Discovery configures the IdP Discovery process, essential for setting up IRSA by locating // the OIDC provider information. + // Only applicable when Mode is "selfhosted". Discovery Discovery `json:"discovery"` + + // IamOIDCProvider configures IAM OIDC IamOIDCProvider Name + // Only applicable when Mode is "eks". + IamOIDCProvider string `json:"provider,omitempty"` } +// +kubebuilder:default=selfhosted +// +kubebuilder:validation:Enum=selfhosted;eks +type SetupMode string + +const ( + ModeSelfhosted = SetupMode("selfhosted") + ModeEks = SetupMode("eks") +) + // Discovery holds the configuration for IdP Discovery, which is crucial for locating // the OIDC provider in a self-hosted environment. type Discovery struct { diff --git a/charts/irsa-manager/crds/irsasetup-crd.yaml b/charts/irsa-manager/crds/irsasetup-crd.yaml index 7cc00d0..b6bcd31 100644 --- a/charts/irsa-manager/crds/irsasetup-crd.yaml +++ b/charts/irsa-manager/crds/irsasetup-crd.yaml @@ -52,6 +52,7 @@ spec: description: |- Discovery configures the IdP Discovery process, essential for setting up IRSA by locating the OIDC provider information. + Only applicable when Mode is "selfhosted". properties: s3: description: S3 specifies the AWS S3 bucket details where the @@ -72,10 +73,19 @@ spec: type: object mode: description: |- - Mode (Optional, Future Feature) Defines how the controller will operate once this feature is enabled. - Currently unused. Planned values: + Mode specifies the operation mode of the controller. + Possible values: - "selfhosted": For self-managed Kubernetes clusters. - "eks": For Amazon EKS environments. + Default: "selfhosted" + enum: + - selfhosted + - eks + type: string + provider: + description: |- + IamOIDCProvider configures IAM OIDC IamOIDCProvider Name + Only applicable when Mode is "eks". type: string required: - cleanup diff --git a/config/crd/bases/irsa-manager.kkb0318.github.io_irsasetups.yaml b/config/crd/bases/irsa-manager.kkb0318.github.io_irsasetups.yaml index c8fe6c9..0cc7e3f 100644 --- a/config/crd/bases/irsa-manager.kkb0318.github.io_irsasetups.yaml +++ b/config/crd/bases/irsa-manager.kkb0318.github.io_irsasetups.yaml @@ -53,6 +53,7 @@ spec: description: |- Discovery configures the IdP Discovery process, essential for setting up IRSA by locating the OIDC provider information. + Only applicable when Mode is "selfhosted". properties: s3: description: S3 specifies the AWS S3 bucket details where the @@ -73,10 +74,19 @@ spec: type: object mode: description: |- - Mode (Optional, Future Feature) Defines how the controller will operate once this feature is enabled. - Currently unused. Planned values: + Mode specifies the operation mode of the controller. + Possible values: - "selfhosted": For self-managed Kubernetes clusters. - "eks": For Amazon EKS environments. + Default: "selfhosted" + enum: + - selfhosted + - eks + type: string + provider: + description: |- + IamOIDCProvider configures IAM OIDC IamOIDCProvider Name + Only applicable when Mode is "eks". type: string required: - cleanup diff --git a/docs/api.md b/docs/api.md index 3d29967..3bb9855 100644 --- a/docs/api.md +++ b/docs/api.md @@ -102,8 +102,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `cleanup` _boolean_ | Cleanup, when enabled, allows the IRSASetup to perform garbage collection
of resources that are no longer needed or managed. | | | -| `mode` _string_ | Mode (Optional, Future Feature) Defines how the controller will operate once this feature is enabled.
Currently unused. Planned values:
- "selfhosted": For self-managed Kubernetes clusters.
- "eks": For Amazon EKS environments. | | | -| `discovery` _[Discovery](#discovery)_ | Discovery configures the IdP Discovery process, essential for setting up IRSA by locating
the OIDC provider information. | | | +| `mode` _[SetupMode](#setupmode)_ | Mode specifies the operation mode of the controller.
Possible values:
- "selfhosted": For self-managed Kubernetes clusters.
- "eks": For Amazon EKS environments.
Default: "selfhosted" | | Enum: [selfhosted eks]
| +| `discovery` _[Discovery](#discovery)_ | Discovery configures the IdP Discovery process, essential for setting up IRSA by locating
the OIDC provider information.
Only applicable when Mode is "selfhosted". | | | +| `provider` _string_ | IamOIDCProvider configures IAM OIDC IamOIDCProvider Name
Only applicable when Mode is "eks". | | | @@ -164,3 +165,17 @@ _Appears in:_ +#### SetupMode + +_Underlying type:_ _string_ + + + +_Validation:_ +- Enum: [selfhosted eks] + +_Appears in:_ +- [IRSASetupSpec](#irsasetupspec) + + + From 9d7542b7ad5799cfb7c4546a50aece2673b5208f Mon Sep 17 00:00:00 2001 From: kkb0318 Date: Fri, 26 Jul 2024 20:29:02 +0900 Subject: [PATCH 2/5] change issuer settings of irsa_controller for eks mode --- internal/controller/irsa_controller.go | 9 +-- internal/controller/irsa_controller_test.go | 86 ++++++++++++++++++++- internal/controller/irsasetup_controller.go | 7 -- internal/issuer/issuer.go | 29 ++++++- 4 files changed, 112 insertions(+), 19 deletions(-) diff --git a/internal/controller/irsa_controller.go b/internal/controller/irsa_controller.go index df8f97c..997a489 100644 --- a/internal/controller/irsa_controller.go +++ b/internal/controller/irsa_controller.go @@ -51,13 +51,6 @@ type IRSAReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the IRSA object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.3/pkg/reconcile func (r *IRSAReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := ctrllog.FromContext(ctx) obj := &irsav1alpha1.IRSA{} @@ -152,7 +145,7 @@ func (r *IRSAReconciler) reconcile(ctx context.Context, obj *irsav1alpha1.IRSA, return fmt.Errorf("error converting to IRSASetup for %s: %v", list.Items[0].GetName(), err) } serviceAccount := obj.Spec.ServiceAccount - issuerMeta, err := issuer.NewS3IssuerMeta(&irsaSetup.Spec.Discovery.S3) + issuerMeta, err := issuer.NewOIDCIssuerMeta(irsaSetup) if err != nil { return err } diff --git a/internal/controller/irsa_controller_test.go b/internal/controller/irsa_controller_test.go index 3e70e13..a245698 100644 --- a/internal/controller/irsa_controller_test.go +++ b/internal/controller/irsa_controller_test.go @@ -211,7 +211,7 @@ var _ = Describe("IRSA Controller", func() { f: newServiceAccount, }, ) - f := createCallBack(ctx, r, typeNamespacedName, obj) + f := createCallBackForFixingNamespace(ctx, r, typeNamespacedName, obj) By("Add Namespace 'default'") f(obj.Spec.ServiceAccount.Name, []string{"default", "kube-system"}) @@ -222,6 +222,75 @@ var _ = Describe("IRSA Controller", func() { f: newServiceAccount, }) + By("removing the custom resource for the Kind") + Eventually(func() error { + return k8sClient.Delete(ctx, obj) + }, timeout).Should(Succeed()) + _, err = r.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).To(Not(HaveOccurred())) + for _, expect := range expected { + checkNoExist(expect) + } + }, + }, + { + name: "should update serviceaccount successfully with EKS mode", + obj: &irsav1alpha1.IRSA{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-resource-eks-1", + Namespace: "default", + }, + Spec: irsav1alpha1.IRSASpec{ + Cleanup: true, + ServiceAccount: irsav1alpha1.IRSAServiceAccount{ + Name: "sa-eks-1", + Namespaces: []string{ + "kube-system", + }, + }, + }, + }, + irsaSetupObj: newMockIRSASetupForEKS(), + f: func(r *IRSAReconciler, obj *irsav1alpha1.IRSA) { + expected := []expectedResource{ + { + NamespacedName: types.NamespacedName{Name: "sa-eks-1", Namespace: "kube-system"}, + f: newServiceAccount, + }, + { + NamespacedName: types.NamespacedName{Name: "sa-eks-1", Namespace: "default"}, + f: newServiceAccount, + }, + } + + By("Reconciling the created resource") + typeNamespacedName := types.NamespacedName{ + Name: obj.Name, + Namespace: obj.Namespace, + } + _, err := r.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + checkExist( + expectedResource{ + NamespacedName: types.NamespacedName{Name: "sa-eks-1", Namespace: "kube-system"}, + f: newServiceAccount, + }, + ) + f := createCallBackForFixingNamespace(ctx, r, typeNamespacedName, obj) + + By("Add Namespace 'default'") + f(obj.Spec.ServiceAccount.Name, []string{"default", "kube-system"}) + By("Remove Namespace 'kube-system'") + f(obj.Spec.ServiceAccount.Name, []string{"default"}) + checkNoExist(expectedResource{ + NamespacedName: types.NamespacedName{Name: "sa-eks-1", Namespace: "kube-system"}, + f: newServiceAccount, + }) + By("removing the custom resource for the Kind") Eventually(func() error { return k8sClient.Delete(ctx, obj) @@ -298,7 +367,20 @@ func newMockIRSASetup() *irsav1alpha1.IRSASetup { } } -func createCallBack(ctx context.Context, r *IRSAReconciler, typeNamespacedName types.NamespacedName, obj *irsav1alpha1.IRSA) func(name string, namespaces []string) { +func newMockIRSASetupForEKS() *irsav1alpha1.IRSASetup { + return &irsav1alpha1.IRSASetup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: irsav1alpha1.IRSASetupSpec{ + Mode: irsav1alpha1.ModeEks, + IamOIDCProvider: "oidc.example", + }, + } +} + +func createCallBackForFixingNamespace(ctx context.Context, r *IRSAReconciler, typeNamespacedName types.NamespacedName, obj *irsav1alpha1.IRSA) func(name string, namespaces []string) { return func(name string, namespaces []string) { fixNamespacesAndReconcile(ctx, r, typeNamespacedName, obj, name, namespaces) } diff --git a/internal/controller/irsasetup_controller.go b/internal/controller/irsasetup_controller.go index 184c48c..2be0614 100644 --- a/internal/controller/irsasetup_controller.go +++ b/internal/controller/irsasetup_controller.go @@ -59,13 +59,6 @@ type IRSASetupReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the IRSASetup object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.3/pkg/reconcile func (r *IRSASetupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := ctrllog.FromContext(ctx) obj := &irsav1alpha1.IRSASetup{} diff --git a/internal/issuer/issuer.go b/internal/issuer/issuer.go index 7329702..8b3711f 100644 --- a/internal/issuer/issuer.go +++ b/internal/issuer/issuer.go @@ -16,6 +16,13 @@ type S3IssuerMeta struct { bucketName string } +func NewOIDCIssuerMeta(i *irsav1alpha1.IRSASetup) (OIDCIssuerMeta, error) { + if i.Spec.Mode == irsav1alpha1.ModeEks { + return newIamOIDCProviderIssuerMeta(i.Spec.IamOIDCProvider) + } + return NewS3IssuerMeta(&i.Spec.Discovery.S3) +} + func NewS3IssuerMeta(s3 *irsav1alpha1.S3Discovery) (*S3IssuerMeta, error) { region := s3.Region bucketName := s3.BucketName @@ -32,6 +39,24 @@ func (i *S3IssuerMeta) IssuerHostPath() string { // IssuerUrl constructs the URL path for the OIDC issuer based on the provided AWS region and bucket name. // This utility function generates the expected host path for accessing the OIDC configuration stored in an S3 bucket. func (i *S3IssuerMeta) IssuerUrl() string { - return fmt.Sprintf("https://%s", i. - IssuerHostPath()) + return fmt.Sprintf("https://%s", i.IssuerHostPath()) +} + +func newIamOIDCProviderIssuerMeta(providerName string) (*iamOIDCProviderIssuerMeta, error) { + if providerName == "" { + return nil, fmt.Errorf("IAM OIDC Provider Name must not be empty") + } + return &iamOIDCProviderIssuerMeta{providerName}, nil +} + +type iamOIDCProviderIssuerMeta struct { + providerName string +} + +func (i *iamOIDCProviderIssuerMeta) IssuerHostPath() string { + return i.providerName +} + +func (i *iamOIDCProviderIssuerMeta) IssuerUrl() string { + return fmt.Sprintf("https://%s", i.IssuerHostPath()) } From 7f8135d4dc4a0bb59b017c0945899e1120cc38ed Mon Sep 17 00:00:00 2001 From: kkb0318 Date: Fri, 26 Jul 2024 20:50:28 +0900 Subject: [PATCH 3/5] support eks mode, fixing irsasetup_controller --- internal/controller/irsasetup_controller.go | 28 ++++++++++++--- .../controller/irsasetup_controller_test.go | 36 +++++++++++++++++++ internal/issuer/issuer.go | 12 +++---- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/internal/controller/irsasetup_controller.go b/internal/controller/irsasetup_controller.go index 2be0614..3b978aa 100644 --- a/internal/controller/irsasetup_controller.go +++ b/internal/controller/irsasetup_controller.go @@ -18,6 +18,7 @@ package controller import ( "context" + "fmt" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" @@ -97,7 +98,11 @@ func (r *IRSASetupReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( }() if !obj.DeletionTimestamp.IsZero() { - err = r.reconcileDelete(ctx, obj, kubeClient) + if obj.Spec.Mode == irsav1alpha1.ModeEks { + err = r.reconcileDeleteEks() + } else { + err = r.reconcileDeleteSelfhosted(ctx, obj, kubeClient) + } if err != nil { return ctrl.Result{}, err } @@ -118,11 +123,18 @@ func (r *IRSASetupReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } func (r *IRSASetupReconciler) reconcile(ctx context.Context, obj *irsav1alpha1.IRSASetup, kubeClient *kubernetes.KubernetesClient) error { + if obj.Spec.Mode == irsav1alpha1.ModeEks { + return reconcileEks(obj) + } err := reconcileSelfhosted(ctx, obj, r.AwsClient, kubeClient) return err } -func (r *IRSASetupReconciler) reconcileDelete(ctx context.Context, obj *irsav1alpha1.IRSASetup, kubeClient *kubernetes.KubernetesClient) error { +func (r *IRSASetupReconciler) reconcileDeleteEks() error { + return nil +} + +func (r *IRSASetupReconciler) reconcileDeleteSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, kubeClient *kubernetes.KubernetesClient) error { if !obj.Spec.Cleanup { return nil } @@ -147,7 +159,7 @@ func (r *IRSASetupReconciler) reconcileDelete(ctx context.Context, obj *irsav1al if err != nil { return err } - issuerMeta, err := issuer.NewS3IssuerMeta(&obj.Spec.Discovery.S3) + issuerMeta, err := issuer.NewOIDCIssuerMeta(obj) if err != nil { return err } @@ -210,7 +222,7 @@ func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsCl string(irsav1alpha1.SelfHostedReasonFailedKeys), string(irsav1alpha1.SelfHostedReasonFailedOidc), ) - issuerMeta, err := issuer.NewS3IssuerMeta(&obj.Spec.Discovery.S3) + issuerMeta, err := issuer.NewOIDCIssuerMeta(obj) if err != nil { return err } @@ -251,6 +263,14 @@ func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsCl return nil } +// reconcileEks ensures the required IAM OIDC Provider is set for EKS mode. +func reconcileEks(obj *irsav1alpha1.IRSASetup) error { + if obj.Spec.IamOIDCProvider == "" { + return fmt.Errorf("IamOIDCProvider parameter must be set when Mode is 'eks'") + } + return nil +} + func newOIDCIdpFactory(ctx context.Context, obj *irsav1alpha1.IRSASetup, jwk *selfhosted.JWK, awsClient awsclient.AwsClient) (selfhosted.OIDCIdPFactory, error) { region := obj.Spec.Discovery.S3.Region bucketName := obj.Spec.Discovery.S3.BucketName diff --git a/internal/controller/irsasetup_controller_test.go b/internal/controller/irsasetup_controller_test.go index ae4b011..93e4575 100644 --- a/internal/controller/irsasetup_controller_test.go +++ b/internal/controller/irsasetup_controller_test.go @@ -288,6 +288,42 @@ var _ = Describe("IRSASetup Controller", func() { } }, }, + { + name: "EKS mode", + obj: &irsav1alpha1.IRSASetup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-resource-eks1", + Namespace: "default", + }, + Spec: irsav1alpha1.IRSASetupSpec{ + Cleanup: false, + Mode: irsav1alpha1.ModeEks, + IamOIDCProvider: "oidc.example", + }, + }, + f: func(r *IRSASetupReconciler, obj *irsav1alpha1.IRSASetup) { + typeNamespacedName := types.NamespacedName{ + Name: obj.Name, + Namespace: obj.Namespace, + } + _, err := r.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + _, err = r.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).To(Not(HaveOccurred())) + By("removing the custom resource (not cleanup)") + Eventually(func() error { + return k8sClient.Delete(ctx, obj) + }, timeout).Should(Succeed()) + _, err = r.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).To(Not(HaveOccurred())) + }, + }, } for _, tt := range tests { It(tt.name, func() { diff --git a/internal/issuer/issuer.go b/internal/issuer/issuer.go index 8b3711f..2417183 100644 --- a/internal/issuer/issuer.go +++ b/internal/issuer/issuer.go @@ -11,7 +11,7 @@ type OIDCIssuerMeta interface { IssuerUrl() string } -type S3IssuerMeta struct { +type s3IssuerMeta struct { region string bucketName string } @@ -20,25 +20,25 @@ func NewOIDCIssuerMeta(i *irsav1alpha1.IRSASetup) (OIDCIssuerMeta, error) { if i.Spec.Mode == irsav1alpha1.ModeEks { return newIamOIDCProviderIssuerMeta(i.Spec.IamOIDCProvider) } - return NewS3IssuerMeta(&i.Spec.Discovery.S3) + return newS3IssuerMeta(&i.Spec.Discovery.S3) } -func NewS3IssuerMeta(s3 *irsav1alpha1.S3Discovery) (*S3IssuerMeta, error) { +func newS3IssuerMeta(s3 *irsav1alpha1.S3Discovery) (*s3IssuerMeta, error) { region := s3.Region bucketName := s3.BucketName if region == "" || bucketName == "" { return nil, fmt.Errorf("s3 region and bucket name must not be empty. region: %s, bucketName: %s", region, bucketName) } - return &S3IssuerMeta{region, bucketName}, nil + return &s3IssuerMeta{region, bucketName}, nil } -func (i *S3IssuerMeta) IssuerHostPath() string { +func (i *s3IssuerMeta) IssuerHostPath() string { return fmt.Sprintf("s3-%s.amazonaws.com/%s", i.region, i.bucketName) } // IssuerUrl constructs the URL path for the OIDC issuer based on the provided AWS region and bucket name. // This utility function generates the expected host path for accessing the OIDC configuration stored in an S3 bucket. -func (i *S3IssuerMeta) IssuerUrl() string { +func (i *s3IssuerMeta) IssuerUrl() string { return fmt.Sprintf("https://%s", i.IssuerHostPath()) } From 7c3da8a6447dab86c5148be7b8f20674e751df5b Mon Sep 17 00:00:00 2001 From: kkb0318 Date: Fri, 2 Aug 2024 20:06:10 +0900 Subject: [PATCH 4/5] immutable field: "mode" --- api/v1alpha1/irsasetup_types.go | 1 + charts/irsa-manager/crds/irsasetup-crd.yaml | 3 +++ charts/irsa-manager/templates/deployment.yaml | 2 ++ .../crd/bases/irsa-manager.kkb0318.github.io_irsasetups.yaml | 3 +++ 4 files changed, 9 insertions(+) diff --git a/api/v1alpha1/irsasetup_types.go b/api/v1alpha1/irsasetup_types.go index 285810e..bcb87ec 100644 --- a/api/v1alpha1/irsasetup_types.go +++ b/api/v1alpha1/irsasetup_types.go @@ -52,6 +52,7 @@ type IRSASetupSpec struct { // +kubebuilder:default=selfhosted // +kubebuilder:validation:Enum=selfhosted;eks +// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" type SetupMode string const ( diff --git a/charts/irsa-manager/crds/irsasetup-crd.yaml b/charts/irsa-manager/crds/irsasetup-crd.yaml index b6bcd31..6ea464b 100644 --- a/charts/irsa-manager/crds/irsasetup-crd.yaml +++ b/charts/irsa-manager/crds/irsasetup-crd.yaml @@ -82,6 +82,9 @@ spec: - selfhosted - eks type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf provider: description: |- IamOIDCProvider configures IAM OIDC IamOIDCProvider Name diff --git a/charts/irsa-manager/templates/deployment.yaml b/charts/irsa-manager/templates/deployment.yaml index c75e3ce..a712ba7 100644 --- a/charts/irsa-manager/templates/deployment.yaml +++ b/charts/irsa-manager/templates/deployment.yaml @@ -71,5 +71,7 @@ spec: | nindent 10 }} securityContext: runAsNonRoot: true + seccompProfile: + type: RuntimeDefault serviceAccountName: {{ include "irsa-manager.fullname" . }}-controller-manager terminationGracePeriodSeconds: 10 \ No newline at end of file diff --git a/config/crd/bases/irsa-manager.kkb0318.github.io_irsasetups.yaml b/config/crd/bases/irsa-manager.kkb0318.github.io_irsasetups.yaml index 0cc7e3f..c3ffe7b 100644 --- a/config/crd/bases/irsa-manager.kkb0318.github.io_irsasetups.yaml +++ b/config/crd/bases/irsa-manager.kkb0318.github.io_irsasetups.yaml @@ -83,6 +83,9 @@ spec: - selfhosted - eks type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf provider: description: |- IamOIDCProvider configures IAM OIDC IamOIDCProvider Name From 5239068502512fffb6e4b8fe04fa45b91cf0977c Mon Sep 17 00:00:00 2001 From: kkb0318 Date: Fri, 9 Aug 2024 19:19:22 +0900 Subject: [PATCH 5/5] fix api (for mode: eks), status --- api/v1alpha1/irsasetup_types.go | 51 +++++++++++-------- api/v1alpha1/zz_generated.deepcopy.go | 4 +- charts/irsa-manager/crds/irsasetup-crd.yaml | 17 +++---- ...-manager.kkb0318.github.io_irsasetups.yaml | 17 +++---- config/manager/kustomization.yaml | 8 ++- docs/api.md | 4 +- examples/eks.yaml | 9 ++++ examples/irsa.yaml | 4 +- internal/controller/irsasetup_controller.go | 42 ++++++++++----- internal/eks/validation.go | 14 +++++ 10 files changed, 112 insertions(+), 58 deletions(-) create mode 100644 examples/eks.yaml create mode 100644 internal/eks/validation.go diff --git a/api/v1alpha1/irsasetup_types.go b/api/v1alpha1/irsasetup_types.go index bcb87ec..707ae85 100644 --- a/api/v1alpha1/irsasetup_types.go +++ b/api/v1alpha1/irsasetup_types.go @@ -43,11 +43,12 @@ type IRSASetupSpec struct { // Discovery configures the IdP Discovery process, essential for setting up IRSA by locating // the OIDC provider information. // Only applicable when Mode is "selfhosted". - Discovery Discovery `json:"discovery"` + // +optional + Discovery Discovery `json:"discovery,omitempty"` // IamOIDCProvider configures IAM OIDC IamOIDCProvider Name // Only applicable when Mode is "eks". - IamOIDCProvider string `json:"provider,omitempty"` + IamOIDCProvider string `json:"iamOIDCProvider,omitempty"` } // +kubebuilder:default=selfhosted @@ -78,39 +79,39 @@ type S3Discovery struct { // IRSASetupStatus defines the observed state of IRSASetup type IRSASetupStatus struct { - SelfHostedSetup []metav1.Condition `json:"selfHostedSetup,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` } -// GetSelfhostedStatusConditions returns a pointer to the Status.Conditions slice -func (in *IRSASetup) GetSelfhostedStatusConditions() *[]metav1.Condition { - return &in.Status.SelfHostedSetup +// GetStatusConditions returns a pointer to the Status.Conditions slice +func (in *IRSASetup) GetStatusConditions() *[]metav1.Condition { + return &in.Status.Conditions } -func SetupSelfHostedStatusReady(irsa IRSASetup, reason, message string) IRSASetup { +func SetupStatusReady(irsa IRSASetup, reason, message string) IRSASetup { newCondition := metav1.Condition{ Type: ReadyCondition, Status: metav1.ConditionTrue, Reason: reason, Message: message, } - apimeta.SetStatusCondition(irsa.GetSelfhostedStatusConditions(), newCondition) + apimeta.SetStatusCondition(irsa.GetStatusConditions(), newCondition) return irsa } -func SelfHostedStatusNotReady(irsa IRSASetup, reason, message string) IRSASetup { +func StatusNotReady(irsa IRSASetup, reason, message string) IRSASetup { newCondition := metav1.Condition{ Type: ReadyCondition, Status: metav1.ConditionFalse, Reason: reason, Message: message, } - apimeta.SetStatusCondition(irsa.GetSelfhostedStatusConditions(), newCondition) + apimeta.SetStatusCondition(irsa.GetStatusConditions(), newCondition) return irsa } -// SelfHostedReadyStatus -func SelfHostedReadyStatus(irsa IRSASetup) *metav1.Condition { - if c := apimeta.FindStatusCondition(irsa.Status.SelfHostedSetup, ReadyCondition); c != nil { +// ReadyStatus +func ReadyStatus(irsa IRSASetup) *metav1.Condition { + if c := apimeta.FindStatusCondition(irsa.Status.Conditions, ReadyCondition); c != nil { return c } return nil @@ -129,22 +130,30 @@ func HasConditionReason(cond *metav1.Condition, reasons ...string) bool { return false } -func IsSelfHostedReadyConditionTrue(irsa IRSASetup) bool { - return apimeta.IsStatusConditionTrue(irsa.Status.SelfHostedSetup, ReadyCondition) +func IsReadyConditionTrue(irsa IRSASetup) bool { + return apimeta.IsStatusConditionTrue(irsa.Status.Conditions, ReadyCondition) } -type SelfHostedReason string +type SelfhostedConditionReason string const ( - SelfHostedReasonFailedWebhook SelfHostedReason = "SelfHostedSetupFailedWebhookCreation" - SelfHostedReasonFailedOidc SelfHostedReason = "SelfHostedSetupFailedOidcCreation" - SelfHostedReasonFailedKeys SelfHostedReason = "SelfHostedSetupFailedKeysCreation" - SelfHostedReasonReady SelfHostedReason = "SelfHostedSetupReady" + SelfHostedReasonFailedWebhook SelfhostedConditionReason = "SelfHostedSetupFailedWebhookCreation" + SelfHostedReasonFailedOidc SelfhostedConditionReason = "SelfHostedSetupFailedOidcCreation" + SelfHostedReasonFailedIssuer SelfhostedConditionReason = "SelfHostedSetupFailedIssuer" + SelfHostedReasonFailedKeys SelfhostedConditionReason = "SelfHostedSetupFailedKeysCreation" + SelfHostedReasonReady SelfhostedConditionReason = "SelfHostedSetupReady" +) + +type EksConditionReason string + +const ( + EksNotReady EksConditionReason = "EksOIDCNotReady" + EksReasonReady EksConditionReason = "EksOIDCSetupReady" ) //+kubebuilder:object:root=true //+kubebuilder:subresource:status -//+kubebuilder:printcolumn:name="SelfHostedReady",type="string",JSONPath=".status.selfHostedSetup[?(@.type==\"Ready\")].status",description="" +//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="" // IRSASetup represents a configuration for setting up IAM Roles for Service Accounts (IRSA) in a Kubernetes cluster. type IRSASetup struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 65e1d9d..a7f7d20 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -213,8 +213,8 @@ func (in *IRSASetupSpec) DeepCopy() *IRSASetupSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IRSASetupStatus) DeepCopyInto(out *IRSASetupStatus) { *out = *in - if in.SelfHostedSetup != nil { - in, out := &in.SelfHostedSetup, &out.SelfHostedSetup + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions *out = make([]v1.Condition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) diff --git a/charts/irsa-manager/crds/irsasetup-crd.yaml b/charts/irsa-manager/crds/irsasetup-crd.yaml index 6ea464b..9ea7918 100644 --- a/charts/irsa-manager/crds/irsasetup-crd.yaml +++ b/charts/irsa-manager/crds/irsasetup-crd.yaml @@ -14,8 +14,8 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - jsonPath: .status.selfHostedSetup[?(@.type=="Ready")].status - name: SelfHostedReady + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready type: string name: v1alpha1 schema: @@ -71,6 +71,11 @@ spec: - region type: object type: object + iamOIDCProvider: + description: |- + IamOIDCProvider configures IAM OIDC IamOIDCProvider Name + Only applicable when Mode is "eks". + type: string mode: description: |- Mode specifies the operation mode of the controller. @@ -85,19 +90,13 @@ spec: x-kubernetes-validations: - message: Value is immutable rule: self == oldSelf - provider: - description: |- - IamOIDCProvider configures IAM OIDC IamOIDCProvider Name - Only applicable when Mode is "eks". - type: string required: - cleanup - - discovery type: object status: description: IRSASetupStatus defines the observed state of IRSASetup properties: - selfHostedSetup: + conditions: items: description: "Condition contains details for one aspect of the current state of this API Resource.\n---\nThis struct is intended for diff --git a/config/crd/bases/irsa-manager.kkb0318.github.io_irsasetups.yaml b/config/crd/bases/irsa-manager.kkb0318.github.io_irsasetups.yaml index c3ffe7b..fc94415 100644 --- a/config/crd/bases/irsa-manager.kkb0318.github.io_irsasetups.yaml +++ b/config/crd/bases/irsa-manager.kkb0318.github.io_irsasetups.yaml @@ -15,8 +15,8 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - jsonPath: .status.selfHostedSetup[?(@.type=="Ready")].status - name: SelfHostedReady + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready type: string name: v1alpha1 schema: @@ -72,6 +72,11 @@ spec: - region type: object type: object + iamOIDCProvider: + description: |- + IamOIDCProvider configures IAM OIDC IamOIDCProvider Name + Only applicable when Mode is "eks". + type: string mode: description: |- Mode specifies the operation mode of the controller. @@ -86,19 +91,13 @@ spec: x-kubernetes-validations: - message: Value is immutable rule: self == oldSelf - provider: - description: |- - IamOIDCProvider configures IAM OIDC IamOIDCProvider Name - Only applicable when Mode is "eks". - type: string required: - cleanup - - discovery type: object status: description: IRSASetupStatus defines the observed state of IRSASetup properties: - selfHostedSetup: + conditions: items: description: "Condition contains details for one aspect of the current state of this API Resource.\n---\nThis struct is intended for diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 7394a6d..4b9bfd7 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,2 +1,8 @@ resources: - - manager.yaml +- manager.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: ghcr.io/kkb0318/irsa-manager + newTag: latest diff --git a/docs/api.md b/docs/api.md index 3bb9855..afc8c5b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -31,6 +31,8 @@ _Appears in:_ | `s3` _[S3Discovery](#s3discovery)_ | S3 specifies the AWS S3 bucket details where the OIDC provider's discovery information is hosted. | | | + + #### IRSA @@ -104,7 +106,7 @@ _Appears in:_ | `cleanup` _boolean_ | Cleanup, when enabled, allows the IRSASetup to perform garbage collection
of resources that are no longer needed or managed. | | | | `mode` _[SetupMode](#setupmode)_ | Mode specifies the operation mode of the controller.
Possible values:
- "selfhosted": For self-managed Kubernetes clusters.
- "eks": For Amazon EKS environments.
Default: "selfhosted" | | Enum: [selfhosted eks]
| | `discovery` _[Discovery](#discovery)_ | Discovery configures the IdP Discovery process, essential for setting up IRSA by locating
the OIDC provider information.
Only applicable when Mode is "selfhosted". | | | -| `provider` _string_ | IamOIDCProvider configures IAM OIDC IamOIDCProvider Name
Only applicable when Mode is "eks". | | | +| `iamOIDCProvider` _string_ | IamOIDCProvider configures IAM OIDC IamOIDCProvider Name
Only applicable when Mode is "eks". | | | diff --git a/examples/eks.yaml b/examples/eks.yaml new file mode 100644 index 0000000..b6172a2 --- /dev/null +++ b/examples/eks.yaml @@ -0,0 +1,9 @@ +apiVersion: irsa-manager.kkb0318.github.io/v1alpha1 +kind: IRSASetup +metadata: + name: irsa-init + namespace: irsa-manager-system +spec: + mode: eks + cleanup: true + iamOIDCProvider: "oidc.eks..amazonaws.com/id/" diff --git a/examples/irsa.yaml b/examples/irsa.yaml index 6b4373e..5f774e9 100644 --- a/examples/irsa.yaml +++ b/examples/irsa.yaml @@ -6,11 +6,11 @@ metadata: spec: cleanup: true serviceAccount: - name: irsa1-sa + name: irsa111-sa namespaces: - kube-system - default iamRole: - name: irsa1-role + name: irsa111-role iamPolicies: - AmazonS3FullAccess diff --git a/internal/controller/irsasetup_controller.go b/internal/controller/irsasetup_controller.go index 3b978aa..ad55884 100644 --- a/internal/controller/irsasetup_controller.go +++ b/internal/controller/irsasetup_controller.go @@ -18,7 +18,6 @@ package controller import ( "context" - "fmt" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" @@ -28,6 +27,7 @@ import ( irsav1alpha1 "github.com/kkb0318/irsa-manager/api/v1alpha1" awsclient "github.com/kkb0318/irsa-manager/internal/aws" + "github.com/kkb0318/irsa-manager/internal/eks" "github.com/kkb0318/irsa-manager/internal/handler" "github.com/kkb0318/irsa-manager/internal/issuer" "github.com/kkb0318/irsa-manager/internal/kubernetes" @@ -124,10 +124,9 @@ func (r *IRSASetupReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( func (r *IRSASetupReconciler) reconcile(ctx context.Context, obj *irsav1alpha1.IRSASetup, kubeClient *kubernetes.KubernetesClient) error { if obj.Spec.Mode == irsav1alpha1.ModeEks { - return reconcileEks(obj) + return reconcileEks(ctx, obj) } - err := reconcileSelfhosted(ctx, obj, r.AwsClient, kubeClient) - return err + return reconcileSelfhosted(ctx, obj, r.AwsClient, kubeClient) } func (r *IRSASetupReconciler) reconcileDeleteEks() error { @@ -177,7 +176,7 @@ func (r *IRSASetupReconciler) reconcileDeleteSelfhosted(ctx context.Context, obj // - The function enforces a 'force update' strategy in case of failures related to kubernetes Secrets creation or OIDC setup. This means it starts from scratch to ensure all components are correctly configured. func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsClient awsclient.AwsClient, kubeClient *kubernetes.KubernetesClient) error { log := ctrllog.FromContext(ctx) - if irsav1alpha1.IsSelfHostedReadyConditionTrue(*obj) { + if irsav1alpha1.IsReadyConditionTrue(*obj) { // Selfhosted Setup have already succeeded log.Info("the self-hosted resources have already set up") return nil @@ -210,20 +209,22 @@ func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsCl // e is set only when an error occurs in an external dependency process and is reflected in the CRs status var e error - var reason irsav1alpha1.SelfHostedReason + var reason irsav1alpha1.SelfhostedConditionReason defer func() { if e != nil { - *obj = irsav1alpha1.SelfHostedStatusNotReady(*obj, string(reason), e.Error()) + *obj = irsav1alpha1.StatusNotReady(*obj, string(reason), e.Error()) } }() forceUpdate := irsav1alpha1.HasConditionReason( - irsav1alpha1.SelfHostedReadyStatus(*obj), + irsav1alpha1.ReadyStatus(*obj), string(irsav1alpha1.SelfHostedReasonFailedKeys), string(irsav1alpha1.SelfHostedReasonFailedOidc), ) issuerMeta, err := issuer.NewOIDCIssuerMeta(obj) if err != nil { + e = err + reason = irsav1alpha1.SelfHostedReasonFailedIssuer return err } err = selfhosted.Execute( @@ -258,16 +259,31 @@ func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsCl reason = irsav1alpha1.SelfHostedReasonFailedWebhook return err } - *obj = irsav1alpha1.SetupSelfHostedStatusReady(*obj, string(irsav1alpha1.SelfHostedReasonReady), "successfully setup resources for self-hosted") + *obj = irsav1alpha1.SetupStatusReady(*obj, string(irsav1alpha1.SelfHostedReasonReady), "successfully setup resources for self-hosted") log.Info("the self-hosted resources have successfully set up") return nil } -// reconcileEks ensures the required IAM OIDC Provider is set for EKS mode. -func reconcileEks(obj *irsav1alpha1.IRSASetup) error { - if obj.Spec.IamOIDCProvider == "" { - return fmt.Errorf("IamOIDCProvider parameter must be set when Mode is 'eks'") +// reconcileEks iterates tasks for EKS mode. +func reconcileEks(ctx context.Context, obj *irsav1alpha1.IRSASetup) error { + log := ctrllog.FromContext(ctx) + var reason irsav1alpha1.EksConditionReason + + // e is set only when an error occurs in an external dependency process and is reflected in the CRs status + var e error + defer func() { + if e != nil { + *obj = irsav1alpha1.StatusNotReady(*obj, string(reason), e.Error()) + } + }() + err := eks.Validate(obj) + if err != nil { + e = err + reason = irsav1alpha1.EksNotReady + return err } + *obj = irsav1alpha1.SetupStatusReady(*obj, string(irsav1alpha1.EksReasonReady), "successfully setup for eks") + log.Info("The OIDC for EKS has been successfully set up") return nil } diff --git a/internal/eks/validation.go b/internal/eks/validation.go new file mode 100644 index 0000000..a592d1e --- /dev/null +++ b/internal/eks/validation.go @@ -0,0 +1,14 @@ +package eks + +import ( + "fmt" + + irsav1alpha1 "github.com/kkb0318/irsa-manager/api/v1alpha1" +) + +func Validate(obj *irsav1alpha1.IRSASetup) error { + if obj.Spec.IamOIDCProvider == "" { + return fmt.Errorf("IamOIDCProvider parameter must be set when Mode is 'eks'") + } + return nil +}