diff --git a/integration/scripts/ginkgo-parallel-procs.sh b/integration/scripts/ginkgo-parallel-procs.sh index ea861397d8..0cb94d214a 100755 --- a/integration/scripts/ginkgo-parallel-procs.sh +++ b/integration/scripts/ginkgo-parallel-procs.sh @@ -12,6 +12,9 @@ case $1 in "windows") echo "${PARALLEL_PROCS}3" ;; + "pod_identity_associations") + echo "${PARALLEL_PROCS}2" + ;; *) echo "" ;; diff --git a/integration/tests/pod_identity_associations/pod_identity_associations_test.go b/integration/tests/pod_identity_associations/pod_identity_associations_test.go index 504b496018..69d17ba2e7 100644 --- a/integration/tests/pod_identity_associations/pod_identity_associations_test.go +++ b/integration/tests/pod_identity_associations/pod_identity_associations_test.go @@ -8,16 +8,18 @@ import ( "context" "encoding/json" "fmt" + "strings" "testing" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/aws/aws-sdk-go-v2/aws" awseks "github.com/aws/aws-sdk-go-v2/service/eks" "github.com/aws/aws-sdk-go-v2/service/iam" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + . "github.com/weaveworks/eksctl/integration/matchers" . "github.com/weaveworks/eksctl/integration/runner" - "github.com/weaveworks/eksctl/integration/tests" "github.com/weaveworks/eksctl/pkg/actions/podidentityassociation" api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" @@ -26,6 +28,10 @@ import ( ) const ( + clusterIRSAv1 = "iam-service-accounts" + clusterIRSAv2 = "pod-identity-associations" + + nsDefault = "default" nsInitial = "initial" nsCLI = "cli" nsConfigFile = "config-file" @@ -49,17 +55,18 @@ var ( func init() { // Call testing.Init() prior to tests.NewParams(), as otherwise -test.* will not be recognised. See also: https://golang.org/doc/go1.13#testing testing.Init() - params = tests.NewParamsWithGivenClusterName("pod-identity-associations", "test") + params = tests.NewParamsWithGivenClusterName("", "test") } func TestPodIdentityAssociations(t *testing.T) { testutils.RegisterAndRun(t) } -var _ = BeforeSuite(func() { +var _ = SynchronizedBeforeSuite(func() []byte { var err error ctl, err = eks.New(context.TODO(), &api.ProviderConfig{Region: params.Region}, nil) Expect(err).NotTo(HaveOccurred()) + roleOutput, err := ctl.AWSProvider.IAM().CreateRole(context.Background(), &iam.CreateRoleInput{ RoleName: aws.String(initialRole1), AssumeRolePolicyDocument: trustPolicy, @@ -73,17 +80,138 @@ var _ = BeforeSuite(func() { }) Expect(err).NotTo(HaveOccurred()) role2ARN = *roleOutput.Role.Arn + + return []byte(role1ARN + "," + role2ARN) +}, func(arns []byte) { + roleARNs := strings.Split(string(arns), ",") + role1ARN, role2ARN = roleARNs[0], roleARNs[1] + + var err error + ctl, err = eks.New(context.TODO(), &api.ProviderConfig{Region: params.Region}, nil) + Expect(err).NotTo(HaveOccurred()) }) -var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() { +var _ = Describe("(Integration) [PodIdentityAssociations Test]", func() { - Context("Cluster with pod identity associations", func() { + Context("Cluster with iam service accounts", Ordered, func() { var ( cfg *api.ClusterConfig ) BeforeAll(func() { - cfg = makeClusterConfig() + cfg = makeClusterConfig(clusterIRSAv1) + }) + + It("should create a cluster with iam service accounts", func() { + cfg.IAM = &api.ClusterIAM{ + WithOIDC: aws.Bool(true), + ServiceAccounts: []*api.ClusterIAMServiceAccount{ + { + ClusterIAMMeta: api.ClusterIAMMeta{ + Name: sa1, + }, + AttachPolicyARNs: []string{"arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"}, + }, + { + ClusterIAMMeta: api.ClusterIAMMeta{ + Name: sa2, + }, + AttachRoleARN: role1ARN, + }, + }, + } + + data, err := json.Marshal(cfg) + Expect(err).NotTo(HaveOccurred()) + + Expect(params.EksctlCreateCmd. + WithArgs( + "cluster", + "--config-file", "-", + "--verbose", "4", + ). + WithoutArg("--region", params.Region). + WithStdin(bytes.NewReader(data))).To(RunSuccessfully()) + + awsConfig := NewConfig(params.Region) + stackNamePrefix := fmt.Sprintf("eksctl-%s-addon-iamserviceaccount-", clusterIRSAv1) + Expect(awsConfig).To(HaveExistingStack(stackNamePrefix + "default-service-account-1")) + }) + + It("should migrate to pod identity associations", func() { + Expect(params.EksctlUtilsCmd. + WithArgs( + "migrate-to-pod-identity", + "--cluster", clusterIRSAv1, + "--remove-oidc-provider-trust-relationship", + "--approve", + )).To(RunSuccessfully()) + }) + + It("should fetch all expected associations", func() { + var output []podidentityassociation.Summary + session := params.EksctlGetCmd. + WithArgs( + "podidentityassociation", + "--cluster", clusterIRSAv1, + "--output", "json", + ).Run() + Expect(session.ExitCode()).To(Equal(0)) + Expect(json.Unmarshal(session.Out.Contents(), &output)).To(Succeed()) + Expect(output).To(HaveLen(3)) + }) + + It("should not return any iam service accounts", func() { + Expect(params.EksctlGetCmd. + WithArgs( + "iamserviceaccount", + "--cluster", clusterIRSAv1, + )).To(RunSuccessfullyWithOutputStringLines(ContainElement("No iamserviceaccounts found"))) + }) + + It("should fail to update an owned migrated role", func() { + session := params.EksctlUpdateCmd. + WithArgs( + "podidentityassociation", + "--cluster", clusterIRSAv1, + "--namespace", nsDefault, + "--service-account-name", sa1, + "--role-arn", role1ARN, + ).Run() + Expect(session.ExitCode()).To(Equal(1)) + Expect(session.Err.Contents()).To(ContainSubstring("cannot change podIdentityAssociation.roleARN since the role was created by eksctl")) + }) + + It("should update an unowned migrated role", func() { + Expect(params.EksctlUpdateCmd. + WithArgs( + "podidentityassociation", + "--cluster", clusterIRSAv1, + "--namespace", nsDefault, + "--service-account-name", sa2, + "--role-arn", role1ARN, + ), + ).To(RunSuccessfully()) + }) + + It("should delete an owned migrated role", func() { + Expect(params.EksctlDeleteCmd. + WithArgs( + "podidentityassociation", + "--cluster", clusterIRSAv1, + "--namespace", nsDefault, + "--service-account-name", sa1, + )).To(RunSuccessfully()) + }) + }) + + Context("Cluster with pod identity associations", Ordered, func() { + var ( + cfg *api.ClusterConfig + ) + + BeforeAll(func() { + cfg = makeClusterConfig(clusterIRSAv2) }) It("should create a cluster with pod identity associations", func() { @@ -124,7 +252,7 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() session := params.EksctlGetCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--output", "json", ).Run() Expect(session.ExitCode()).To(Equal(0)) @@ -137,7 +265,7 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() Expect(params.EksctlCreateCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--namespace", nsInitial, "--service-account-name", sa1, "--role-arn", role1ARN, @@ -149,7 +277,7 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() Expect(params.EksctlCreateCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--namespace", nsCLI, "--service-account-name", sa1, "--well-known-policies", "certManager", @@ -194,7 +322,7 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() session := params.EksctlGetCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--output", "json", ).Run() Expect(session.ExitCode()).To(Equal(0)) @@ -207,7 +335,7 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() session := params.EksctlGetCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--namespace", nsConfigFile, "--output", "json", ).Run() @@ -221,7 +349,7 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() session := params.EksctlGetCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--namespace", nsConfigFile, "--service-account-name", sa1, "--output", "json", @@ -234,22 +362,23 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() Context("Updating pod identity associations", func() { It("should fail to update an association with role created by eksctl", func() { - Expect(params.EksctlUpdateCmd. + session := params.EksctlUpdateCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--namespace", nsCLI, "--service-account-name", sa1, "--role-arn", role1ARN, - ), - ).NotTo(RunSuccessfully()) + ).Run() + Expect(session.ExitCode()).To(Equal(1)) + Expect(session.Err.Contents()).To(ContainSubstring("cannot change podIdentityAssociation.roleARN since the role was created by eksctl")) }) It("should update an association via CLI", func() { Expect(params.EksctlUpdateCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--namespace", nsInitial, "--service-account-name", sa1, "--role-arn", role2ARN, @@ -289,7 +418,7 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() session := params.EksctlGetCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--namespace", nsInitial, "--output", "json", ).Run() @@ -307,7 +436,7 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() Expect(params.EksctlDeleteCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--namespace", nsInitial, "--service-account-name", sa1, ), @@ -343,7 +472,7 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() Expect(params.EksctlGetCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--namespace", nsInitial, )).To(RunSuccessfullyWithOutputStringLines(ContainElement("No podidentityassociations found"))) }) @@ -353,7 +482,7 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() BeforeAll(func() { _, err := ctl.AWSProvider.EKS().CreatePodIdentityAssociation(context.Background(), &awseks.CreatePodIdentityAssociationInput{ - ClusterName: ¶ms.ClusterName, + ClusterName: aws.String(clusterIRSAv2), Namespace: aws.String(nsUnowned), ServiceAccount: aws.String(sa1), RoleArn: &role1ARN, @@ -365,7 +494,7 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() Expect(params.EksctlGetCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--namespace", nsUnowned, "--service-account-name", sa1, "--output", "json", @@ -379,7 +508,7 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() Expect(params.EksctlDeleteCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--namespace", nsUnowned, "--service-account-name", sa1, )).To(RunSuccessfully()) @@ -387,7 +516,7 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() Expect(params.EksctlGetCmd. WithArgs( "podidentityassociation", - "--cluster", params.ClusterName, + "--cluster", clusterIRSAv2, "--namespace", nsUnowned, "--service-account-name", sa1, )).To(RunSuccessfullyWithOutputStringLines(ContainElement("No podidentityassociations found"))) @@ -396,10 +525,19 @@ var _ = Describe("(Integration) [PodIdentityAssociations Test]", Ordered, func() }) }) -var _ = AfterSuite(func() { +var _ = SynchronizedAfterSuite(func() {}, func() { if ctl == nil { return } + + Expect(params.EksctlDeleteCmd.WithArgs( + "cluster", clusterIRSAv1, + )).To(RunSuccessfully()) + + Expect(params.EksctlDeleteCmd.WithArgs( + "cluster", clusterIRSAv2, + )).To(RunSuccessfully()) + _, err := ctl.AWSProvider.IAM().DeleteRole(context.Background(), &iam.DeleteRoleInput{ RoleName: aws.String(initialRole1), }) @@ -409,14 +547,12 @@ var _ = AfterSuite(func() { RoleName: aws.String(initialRole2), }) Expect(err).NotTo(HaveOccurred()) - - params.DeleteClusters() }) var ( - makeClusterConfig = func() *api.ClusterConfig { + makeClusterConfig = func(clusterName string) *api.ClusterConfig { cfg := api.NewClusterConfig() - cfg.Metadata.Name = params.ClusterName + cfg.Metadata.Name = clusterName cfg.Metadata.Version = params.Version cfg.Metadata.Region = params.Region return cfg diff --git a/pkg/actions/podidentityassociation/tasks.go b/pkg/actions/podidentityassociation/tasks.go index 830f89a2c2..a130bea456 100644 --- a/pkg/actions/podidentityassociation/tasks.go +++ b/pkg/actions/podidentityassociation/tasks.go @@ -218,11 +218,11 @@ func updateTrustStatements( if err != nil { return trustStatements, err } - documentJsonString, err := url.PathUnescape(*output.Role.AssumeRolePolicyDocument) + documentJSONString, err := url.PathUnescape(*output.Role.AssumeRolePolicyDocument) if err != nil { return trustStatements, err } - if err := json.Unmarshal([]byte(documentJsonString), &trustPolicy); err != nil { + if err := json.Unmarshal([]byte(documentJSONString), &trustPolicy); err != nil { return trustStatements, err } diff --git a/pkg/apis/eksctl.io/v1alpha5/iam.go b/pkg/apis/eksctl.io/v1alpha5/iam.go index 3d5151cf3f..8fbe60cfa6 100644 --- a/pkg/apis/eksctl.io/v1alpha5/iam.go +++ b/pkg/apis/eksctl.io/v1alpha5/iam.go @@ -202,7 +202,7 @@ func (p PodIdentityAssociation) NameString() string { // IAMPolicyDocument represents an IAM assume role policy document type IAMPolicyDocument struct { Version string `json:"Version"` - Id string `json:"Id,omitempty"` + ID string `json:"Id,omitempty"` Statements []IAMStatement `json:"Statement"` } diff --git a/pkg/cfn/manager/api.go b/pkg/cfn/manager/api.go index 7978e3b872..5e68c3fa16 100644 --- a/pkg/cfn/manager/api.go +++ b/pkg/cfn/manager/api.go @@ -32,7 +32,7 @@ const ( resourceTypeAutoScalingGroup = "auto-scaling-group" outputsRootPath = "Outputs" mappingsRootPath = "Mappings" - ourStackRegexFmt = "^(eksctl|EKS)-%s-((cluster|nodegroup-.+|addon-.+|fargate|karpenter)|(VPC|ServiceRole|ControlPlane|DefaultNodeGroup))$" + ourStackRegexFmt = "^(eksctl|EKS)-%s-((cluster|nodegroup-.+|addon-.+|podidentityrole-.+|fargate|karpenter)|(VPC|ServiceRole|ControlPlane|DefaultNodeGroup))$" clusterStackRegex = "eksctl-.*-cluster" ) diff --git a/userdocs/src/usage/pod-identity-associations.md b/userdocs/src/usage/pod-identity-associations.md index 0d998917d4..a4c468ccbb 100644 --- a/userdocs/src/usage/pod-identity-associations.md +++ b/userdocs/src/usage/pod-identity-associations.md @@ -181,13 +181,27 @@ Behind the scenes, the command will apply the following steps: Running the command without the `--approve` flag will only output a plan consisting of a set of tasks reflecting the steps above, e.g. -``` +```bash +[ℹ] (plan) would migrate 2 iamserviceaccount(s) to pod identity association(s) by executing the following tasks +[ℹ] (plan) +3 sequential tasks: { install eks-pod-identity-agent addon, + 2 parallel sub-tasks: { + update trust policy for owned role "eksctl-my-cluster-addon-iamserv-Role1-beYhlhzpwQte", + update trust policy for unowned role "Unowned-Role1", + }, + 2 parallel sub-tasks: { + create pod identity association for service account "default/sa1", + create pod identity association for service account "default/sa2", + } +} +[ℹ] all tasks were skipped +[!] no changes were applied, run again with '--approve' to apply the changes ``` -Additioanlly, to delete the existing OIDC provider trust relationship from all IAM Roles, run the command with `--remove-existing-oidc-trust-relationship` flag, e.g. +Additioanlly, to delete the existing OIDC provider trust relationship from all IAM Roles, run the command with `--remove-oidc-provider-trust-relationship` flag, e.g. ``` -eksctl utils migrate-to-pod-identity --cluster my-cluster --approve --remove-existing-oidc-trust-relationship +eksctl utils migrate-to-pod-identity --cluster my-cluster --approve --remove-oidc-provider-trust-relationship ```