diff --git a/api/v1alpha1/irsa_types.go b/api/v1alpha1/irsa_types.go index 0e004e5..ed85014 100644 --- a/api/v1alpha1/irsa_types.go +++ b/api/v1alpha1/irsa_types.go @@ -20,6 +20,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const ( + // IRSAKind represents the kind attribute of an IRSA resource. + IRSAKind = "IRSA" +) + // IRSASpec defines the desired state of IRSA type IRSASpec struct { // Cleanup, when enabled, allows the IRSA to perform garbage collection diff --git a/config/crd/bases/irsa.kkb0318.github.io_irsas.yaml b/config/crd/bases/irsa.kkb0318.github.io_irsas.yaml index 14977f2..d9fbdf0 100644 --- a/config/crd/bases/irsa.kkb0318.github.io_irsas.yaml +++ b/config/crd/bases/irsa.kkb0318.github.io_irsas.yaml @@ -39,6 +39,11 @@ spec: spec: description: IRSASpec defines the desired state of IRSA properties: + cleanup: + description: |- + Cleanup, when enabled, allows the IRSA to perform garbage collection + of resources that are no longer needed or managed. + type: boolean iamPolicies: description: IamPolicies represents the list of IAM policies to be attached to the IAM role @@ -68,6 +73,8 @@ spec: type: string type: array type: object + required: + - cleanup type: object status: description: IRSAStatus defines the observed state of IRSA diff --git a/internal/aws/aws_role.go b/internal/aws/aws_role.go index 2bb0ee3..d92d8a1 100644 --- a/internal/aws/aws_role.go +++ b/internal/aws/aws_role.go @@ -12,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/iam" "github.com/aws/smithy-go" + "github.com/kkb0318/irsa-manager/internal/issuer" ) // RoleManager represents the details needed to manage IAM roles @@ -61,8 +62,8 @@ func (a *AwsIamClient) DeleteIRSARole(ctx context.Context, r RoleManager) error } // CreateIRSARole creates an IAM role with the specified trust policy and attaches specified policies to it -func (a *AwsIamClient) CreateIRSARole(ctx context.Context, issuerHostPath string, r RoleManager) error { - providerArn := fmt.Sprintf("arn:aws:iam::%s:oidc-provider/%s", r.AccountId, issuerHostPath) +func (a *AwsIamClient) CreateIRSARole(ctx context.Context, issuerMeta issuer.OIDCIssuerMeta, r RoleManager) error { + providerArn := fmt.Sprintf("arn:aws:iam::%s:oidc-provider/%s", r.AccountId, issuerMeta.IssuerHostPath()) statement := make([]map[string]interface{}, len(r.Namespaces)) for i, ns := range r.Namespaces { statement[i] = map[string]interface{}{ @@ -73,7 +74,7 @@ func (a *AwsIamClient) CreateIRSARole(ctx context.Context, issuerHostPath string "Action": "sts:AssumeRoleWithWebIdentity", "Condition": map[string]interface{}{ "StringEquals": map[string]interface{}{ - fmt.Sprintf("%s:sub", issuerHostPath): fmt.Sprintf("system:serviceaccount:%s:%s", ns, r.RoleName), + fmt.Sprintf("%s:sub", issuerMeta.IssuerHostPath()): fmt.Sprintf("system:serviceaccount:%s:%s", ns, r.RoleName), }, }, } diff --git a/internal/controller/irsa_controller.go b/internal/controller/irsa_controller.go index 89b412b..d498dd6 100644 --- a/internal/controller/irsa_controller.go +++ b/internal/controller/irsa_controller.go @@ -18,9 +18,11 @@ package controller import ( "context" + "fmt" awsclient "github.com/kkb0318/irsa-manager/internal/aws" "github.com/kkb0318/irsa-manager/internal/handler" + "github.com/kkb0318/irsa-manager/internal/issuer" "github.com/kkb0318/irsa-manager/internal/kubernetes" "github.com/kkb0318/irsa-manager/internal/manifests" "k8s.io/apimachinery/pkg/runtime" @@ -126,6 +128,19 @@ func (r *IRSAReconciler) reconcileDelete(ctx context.Context, obj *irsav1alpha1. } func (r *IRSAReconciler) reconcile(ctx context.Context, obj *irsav1alpha1.IRSA, kubeClient *kubernetes.KubernetesClient) error { + list, err := kubeClient.List(ctx, irsav1alpha1.GroupVersion.WithKind(irsav1alpha1.IRSAKind)) + if err != nil { + return err + } + if len(list.Items) != 1 { + return fmt.Errorf("there should be exactly one IRSASetup item") + } + irsaSetup := &irsav1alpha1.IRSASetup{} + err = runtime.DefaultUnstructuredConverter.FromUnstructured(list.Items[0].Object, irsaSetup) + if err != nil { + return fmt.Errorf("error converting to IRSASetup for %s: %v", list.Items[0].GetName(), err) + } + serviceAccount := obj.Spec.ServiceAccount accountId, err := r.AwsClient.StsClient().GetAccountId() if err != nil { @@ -137,7 +152,10 @@ func (r *IRSAReconciler) reconcile(ctx context.Context, obj *irsav1alpha1.IRSA, Policies: obj.Spec.IamPolicies, AccountId: accountId, } - err = r.AwsClient.IamClient().CreateIRSARole(ctx, "", roleManager) + err = r.AwsClient.IamClient().CreateIRSARole(ctx, + issuer.NewS3IssuerMeta(irsaSetup.Spec.Discovery.S3), + roleManager, + ) if err != nil { return err } diff --git a/internal/controller/irsa_controller_test.go b/internal/controller/irsa_controller_test.go index d3c3422..937118a 100644 --- a/internal/controller/irsa_controller_test.go +++ b/internal/controller/irsa_controller_test.go @@ -23,6 +23,7 @@ import ( . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -33,13 +34,14 @@ import ( var _ = Describe("IRSA Controller", func() { Context("When reconciling IRSA", func() { tests := []struct { - name string - objs *irsav1alpha1.IRSA - f func(*IRSAReconciler, *irsav1alpha1.IRSA) + name string + obj *irsav1alpha1.IRSA + irsaSetupObj *irsav1alpha1.IRSASetup + f func(*IRSAReconciler, *irsav1alpha1.IRSA) }{ { name: "should reconcile successfully", - objs: &irsav1alpha1.IRSA{ + obj: &irsav1alpha1.IRSA{ ObjectMeta: metav1.ObjectMeta{ Name: "test-resource1", Namespace: "default", @@ -54,6 +56,7 @@ var _ = Describe("IRSA Controller", func() { }, }, }, + irsaSetupObj: newMockIRSASetup(), f: func(r *IRSAReconciler, obj *irsav1alpha1.IRSA) { expected := []expectedResource{ { @@ -98,7 +101,7 @@ var _ = Describe("IRSA Controller", func() { }, { name: "error", - objs: &irsav1alpha1.IRSA{ + obj: &irsav1alpha1.IRSA{ ObjectMeta: metav1.ObjectMeta{ Name: "test-resource2", Namespace: "default", @@ -113,6 +116,7 @@ var _ = Describe("IRSA Controller", func() { }, }, }, + irsaSetupObj: newMockIRSASetup(), f: func(r *IRSAReconciler, obj *irsav1alpha1.IRSA) { expected := []expectedResource{ { @@ -167,24 +171,39 @@ var _ = Describe("IRSA Controller", func() { for _, tt := range tests { It(tt.name, func() { typeNamespacedName := types.NamespacedName{ - Name: tt.objs.Name, - Namespace: tt.objs.Namespace, + Name: tt.obj.Name, + Namespace: tt.obj.Namespace, } controllerReconciler := &IRSAReconciler{ Client: k8sClient, Scheme: k8sClient.Scheme(), AwsClient: newMockAwsClient(&mockAwsIamAPI{}, nil, nil), } - By("creating the custom resource for the Kind IRSASetup") - err := k8sClient.Get(ctx, typeNamespacedName, tt.objs) + By("creating the mock ISASetup") + if tt.irsaSetupObj != nil { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(tt.irsaSetupObj), tt.irsaSetupObj) + if err != nil && errors.IsNotFound(err) { + Expect(k8sClient.Create(ctx, tt.obj)).To(Succeed()) + } + + } + By("creating the custom resource for the Kind IRSA") + err := k8sClient.Get(ctx, typeNamespacedName, tt.obj) if err != nil && errors.IsNotFound(err) { - Expect(k8sClient.Create(ctx, tt.objs)).To(Succeed()) + Expect(k8sClient.Create(ctx, tt.obj)).To(Succeed()) } _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{ NamespacedName: typeNamespacedName, }) Expect(err).NotTo(HaveOccurred()) - tt.f(controllerReconciler, tt.objs) + tt.f(controllerReconciler, tt.obj) + + By("deleting the mock ISASetup") + err = k8sClient.Get(ctx, client.ObjectKeyFromObject(tt.irsaSetupObj), tt.irsaSetupObj) + if err == nil { + err = k8sClient.Delete(ctx, tt.irsaSetupObj) + Expect(err).NotTo(HaveOccurred()) + } }) } BeforeEach(func() { @@ -193,3 +212,20 @@ var _ = Describe("IRSA Controller", func() { }) }) }) + +func newMockIRSASetup() *irsav1alpha1.IRSASetup { + return &irsav1alpha1.IRSASetup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: irsav1alpha1.IRSASetupSpec{ + Discovery: irsav1alpha1.Discovery{ + S3: irsav1alpha1.S3Discovery{ + Region: "ap-northeast-1", + BucketName: "irsa-manager-1", + }, + }, + }, + } +} diff --git a/internal/controller/irsasetup_controller.go b/internal/controller/irsasetup_controller.go index ce950af..84baffc 100644 --- a/internal/controller/irsasetup_controller.go +++ b/internal/controller/irsasetup_controller.go @@ -28,6 +28,7 @@ import ( irsav1alpha1 "github.com/kkb0318/irsa-manager/api/v1alpha1" awsclient "github.com/kkb0318/irsa-manager/internal/aws" "github.com/kkb0318/irsa-manager/internal/handler" + "github.com/kkb0318/irsa-manager/internal/issuer" "github.com/kkb0318/irsa-manager/internal/kubernetes" "github.com/kkb0318/irsa-manager/internal/manifests" "github.com/kkb0318/irsa-manager/internal/selfhosted" @@ -153,7 +154,11 @@ func (r *IRSASetupReconciler) reconcileDelete(ctx context.Context, obj *irsav1al if err != nil { return err } - return selfhosted.Delete(ctx, factory) + return selfhosted.Delete( + ctx, + factory, + issuer.NewS3IssuerMeta(obj.Spec.Discovery.S3), + ) } // reconcileSelfhosted ensures that the self-hosted resources are set up correctly. @@ -207,7 +212,12 @@ func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsCl string(irsav1alpha1.SelfHostedReasonFailedKeys), string(irsav1alpha1.SelfHostedReasonFailedOidc), ) - err = selfhosted.Execute(ctx, factory, forceUpdate) + err = selfhosted.Execute( + ctx, + factory, + issuer.NewS3IssuerMeta(obj.Spec.Discovery.S3), + forceUpdate, + ) if err != nil { e = err reason = irsav1alpha1.SelfHostedReasonFailedOidc diff --git a/internal/controller/irsasetup_controller_test.go b/internal/controller/irsasetup_controller_test.go index a581d95..872a049 100644 --- a/internal/controller/irsasetup_controller_test.go +++ b/internal/controller/irsasetup_controller_test.go @@ -57,7 +57,7 @@ var _ = Describe("IRSASetup Controller", func() { Discovery: irsav1alpha1.Discovery{ S3: irsav1alpha1.S3Discovery{ Region: "ap-northeast-1", - BucketName: "irsa-manager-kkb-1", + BucketName: "irsa-manager-1", }, }, }, @@ -138,7 +138,7 @@ var _ = Describe("IRSASetup Controller", func() { Discovery: irsav1alpha1.Discovery{ S3: irsav1alpha1.S3Discovery{ Region: "ap-northeast-1", - BucketName: "irsa-manager-kkb-1", + BucketName: "irsa-manager-1", }, }, }, @@ -223,7 +223,7 @@ var _ = Describe("IRSASetup Controller", func() { Discovery: irsav1alpha1.Discovery{ S3: irsav1alpha1.S3Discovery{ Region: "ap-northeast-1", - BucketName: "irsa-manager-kkb-1", + BucketName: "irsa-manager-1", }, }, }, diff --git a/internal/selfhosted/oidc/issuer_meta.go b/internal/issuer/issuer.go similarity index 72% rename from internal/selfhosted/oidc/issuer_meta.go rename to internal/issuer/issuer.go index d66f816..42bd5f0 100644 --- a/internal/selfhosted/oidc/issuer_meta.go +++ b/internal/issuer/issuer.go @@ -1,10 +1,12 @@ -package oidc +package issuer import ( "fmt" + + irsav1alpha1 "github.com/kkb0318/irsa-manager/api/v1alpha1" ) -type IssuerMeta interface { +type OIDCIssuerMeta interface { IssuerHostPath() string IssuerUrl() string } @@ -14,8 +16,8 @@ type S3IssuerMeta struct { bucketName string } -func NewS3IssuerMeta(region, bucketName string) *S3IssuerMeta { - return &S3IssuerMeta{region, bucketName} +func NewS3IssuerMeta(s3 irsav1alpha1.S3Discovery) *S3IssuerMeta { + return &S3IssuerMeta{s3.Region, s3.BucketName} } func (i *S3IssuerMeta) IssuerHostPath() string { diff --git a/internal/kubernetes/list.go b/internal/kubernetes/list.go new file mode 100644 index 0000000..3687891 --- /dev/null +++ b/internal/kubernetes/list.go @@ -0,0 +1,21 @@ +package kubernetes + +import ( + "context" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// List lists all resources of the given kind. +func (c KubernetesClient) List(ctx context.Context, gvk schema.GroupVersionKind, listOpts ...client.ListOption) (*unstructured.UnstructuredList, error) { + list := &unstructured.UnstructuredList{} + list.SetGroupVersionKind(gvk) + err := c.client.List(ctx, list, listOpts...) + if err != nil { + return nil, err + } + + return list, nil +} diff --git a/internal/selfhosted/oidc.go b/internal/selfhosted/oidc.go index 2bfcdda..12ce7f6 100644 --- a/internal/selfhosted/oidc.go +++ b/internal/selfhosted/oidc.go @@ -1,11 +1,10 @@ package selfhosted -import "context" +import ( + "context" -type OIDCIssuerMeta interface { - IssuerHostPath() string - IssuerUrl() string -} + "github.com/kkb0318/irsa-manager/internal/issuer" +) type OIDCIdP interface { Create(ctx context.Context) error @@ -27,8 +26,7 @@ type OIDCIdPDiscovery interface { } type OIDCIdPFactory interface { - IssuerMeta() OIDCIssuerMeta - IdP(i OIDCIssuerMeta) (OIDCIdP, error) + IdP(i issuer.OIDCIssuerMeta) (OIDCIdP, error) IdPDiscovery() OIDCIdPDiscovery - IdPDiscoveryContents(i OIDCIssuerMeta) OIDCIdPDiscoveryContents + IdPDiscoveryContents(i issuer.OIDCIssuerMeta) OIDCIdPDiscoveryContents } diff --git a/internal/selfhosted/oidc/factory.go b/internal/selfhosted/oidc/factory.go index 9f70bcb..c5381e0 100644 --- a/internal/selfhosted/oidc/factory.go +++ b/internal/selfhosted/oidc/factory.go @@ -4,6 +4,7 @@ import ( "context" awsclient "github.com/kkb0318/irsa-manager/internal/aws" + "github.com/kkb0318/irsa-manager/internal/issuer" "github.com/kkb0318/irsa-manager/internal/selfhosted" ) @@ -32,11 +33,7 @@ func NewAwsS3IdpFactory( }, nil } -func (f *AwsS3IdPFactory) IssuerMeta() selfhosted.OIDCIssuerMeta { - return NewS3IssuerMeta(f.region, f.bucketName) -} - -func (f *AwsS3IdPFactory) IdP(i selfhosted.OIDCIssuerMeta) (selfhosted.OIDCIdP, error) { +func (f *AwsS3IdPFactory) IdP(i issuer.OIDCIssuerMeta) (selfhosted.OIDCIdP, error) { return NewAwsIdP(f.awsClient, i) } @@ -44,6 +41,6 @@ func (f *AwsS3IdPFactory) IdPDiscovery() selfhosted.OIDCIdPDiscovery { return NewS3IdPDiscovery(f.awsClient, f.region, f.bucketName) } -func (f *AwsS3IdPFactory) IdPDiscoveryContents(i selfhosted.OIDCIssuerMeta) selfhosted.OIDCIdPDiscoveryContents { +func (f *AwsS3IdPFactory) IdPDiscoveryContents(i issuer.OIDCIssuerMeta) selfhosted.OIDCIdPDiscoveryContents { return NewIdPDiscoveryContents(f.jwk, i, f.jwksFileName) } diff --git a/internal/selfhosted/oidc/id_provider.go b/internal/selfhosted/oidc/id_provider.go index b98d5bb..2e49abc 100644 --- a/internal/selfhosted/oidc/id_provider.go +++ b/internal/selfhosted/oidc/id_provider.go @@ -4,16 +4,16 @@ import ( "context" awsclient "github.com/kkb0318/irsa-manager/internal/aws" - "github.com/kkb0318/irsa-manager/internal/selfhosted" + "github.com/kkb0318/irsa-manager/internal/issuer" ) type AwsIdP struct { iamClient *awsclient.AwsIamClient stsClient *awsclient.AwsStsClient - issuerMeta selfhosted.OIDCIssuerMeta + issuerMeta issuer.OIDCIssuerMeta } -func NewAwsIdP(awsConfig awsclient.AwsClient, issuerMeta selfhosted.OIDCIssuerMeta) (*AwsIdP, error) { +func NewAwsIdP(awsConfig awsclient.AwsClient, issuerMeta issuer.OIDCIssuerMeta) (*AwsIdP, error) { iamClient := awsConfig.IamClient() stsClient := awsConfig.StsClient() return &AwsIdP{iamClient, stsClient, issuerMeta}, nil diff --git a/internal/selfhosted/oidc/id_provider_discovery_contents.go b/internal/selfhosted/oidc/id_provider_discovery_contents.go index 922e0a6..0196214 100644 --- a/internal/selfhosted/oidc/id_provider_discovery_contents.go +++ b/internal/selfhosted/oidc/id_provider_discovery_contents.go @@ -4,12 +4,13 @@ import ( "encoding/json" "fmt" + "github.com/kkb0318/irsa-manager/internal/issuer" "github.com/kkb0318/irsa-manager/internal/selfhosted" ) type IdPDiscoveryContents struct { jwk *selfhosted.JWK - issuerMeta selfhosted.OIDCIssuerMeta + issuerMeta issuer.OIDCIssuerMeta jwksFileName string } @@ -23,7 +24,7 @@ type oidcDiscoveryConfiguration struct { ClaimsSupported []string `json:"claims_supported"` } -func NewIdPDiscoveryContents(jwk *selfhosted.JWK, issuerMeta selfhosted.OIDCIssuerMeta, jwksFileName string) *IdPDiscoveryContents { +func NewIdPDiscoveryContents(jwk *selfhosted.JWK, issuerMeta issuer.OIDCIssuerMeta, jwksFileName string) *IdPDiscoveryContents { return &IdPDiscoveryContents{jwk, issuerMeta, jwksFileName} } diff --git a/internal/selfhosted/selfhosted.go b/internal/selfhosted/selfhosted.go index b113ffe..d6b88f6 100644 --- a/internal/selfhosted/selfhosted.go +++ b/internal/selfhosted/selfhosted.go @@ -2,10 +2,11 @@ package selfhosted import ( "context" + + "github.com/kkb0318/irsa-manager/internal/issuer" ) -func Execute(ctx context.Context, idpComponentsFactory OIDCIdPFactory, forceUpdate bool) error { - issuerMeta := idpComponentsFactory.IssuerMeta() +func Execute(ctx context.Context, idpComponentsFactory OIDCIdPFactory, issuerMeta issuer.OIDCIssuerMeta, forceUpdate bool) error { discovery := idpComponentsFactory.IdPDiscovery() discoveryContents := idpComponentsFactory.IdPDiscoveryContents(issuerMeta) idp, err := idpComponentsFactory.IdP(issuerMeta) @@ -27,8 +28,7 @@ func Execute(ctx context.Context, idpComponentsFactory OIDCIdPFactory, forceUpda return nil } -func Delete(ctx context.Context, factory OIDCIdPFactory) error { - issuerMeta := factory.IssuerMeta() +func Delete(ctx context.Context, factory OIDCIdPFactory, issuerMeta issuer.OIDCIssuerMeta) error { discovery := factory.IdPDiscovery() discoveryContents := factory.IdPDiscoveryContents(issuerMeta) idp, err := factory.IdP(issuerMeta)