diff --git a/pkg/aws/validations/iam_helpers.go b/pkg/aws/validations/iam_helpers.go index 7f5c9688..2932ccb1 100644 --- a/pkg/aws/validations/iam_helpers.go +++ b/pkg/aws/validations/iam_helpers.go @@ -2,11 +2,22 @@ package validations import ( "fmt" + "maps" "github.com/aws/aws-sdk-go-v2/aws" + iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types" semver "github.com/hashicorp/go-version" . "github.com/openshift-online/ocm-common/pkg/aws/consts" + . "github.com/openshift-online/ocm-common/pkg/rosa/accountroles" + . "github.com/openshift-online/ocm-common/pkg/rosa/operatorroles" + cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" +) + +const ( + duplicateIamRoleArnErrorMsg = "ROSA IAM roles must have unique ARNs " + + "and should not be shared with other IAM roles within the same cluster. " + + "Duplicated role arn: %s" ) func GetRoleName(prefix string, role string) string { @@ -62,3 +73,17 @@ func IamResourceHasTag(iamTags []iamtypes.Tag, tagKey string, tagValue string) b return false } + +func IamRoleArnsValidator(cluster *cmv1.Cluster) error { + validatingMap := map[string]struct{}{} + clusterIamRoles := GetAccountRolesArnsMap(cluster) + maps.Copy(clusterIamRoles, GetOperatorRolesArnsMap(cluster)) + + for _, arn := range clusterIamRoles { + if _, exist := validatingMap[arn]; exist { + return fmt.Errorf(duplicateIamRoleArnErrorMsg, arn) + } + validatingMap[arn] = struct{}{} + } + return nil +} diff --git a/pkg/aws/validations/iam_helpers_test.go b/pkg/aws/validations/iam_helpers_test.go index 4e6d7a78..12ec3bd2 100644 --- a/pkg/aws/validations/iam_helpers_test.go +++ b/pkg/aws/validations/iam_helpers_test.go @@ -2,11 +2,13 @@ package validations import ( "fmt" + "github.com/aws/aws-sdk-go-v2/aws" iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" ) var _ = Describe("AWS iamtypes Functions", func() { @@ -141,4 +143,49 @@ var _ = Describe("AWS iamtypes Functions", func() { Expect(result).To(BeFalse()) }) }) + + var _ = Describe("IamRoleArnsValidator", func() { + It("should return error if duplicate arns exist", func() { + fakeCluster, err := cmv1.NewCluster(). + AWS( + cmv1.NewAWS(). + STS( + cmv1.NewSTS(). + RoleARN("installer"). + SupportRoleARN("support"). + InstanceIAMRoles( + cmv1.NewInstanceIAMRoles(). + MasterRoleARN("installer"). + WorkerRoleARN("worker"), + ), + ), + ).Build() + Expect(err).ToNot(HaveOccurred()) + err = IamRoleArnsValidator(fakeCluster) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal( + fmt.Sprintf(duplicateIamRoleArnErrorMsg, "installer"), + )) + }) + + It("should return nil no duplicate arns are detected", func() { + fakeCluster, err := cmv1.NewCluster(). + AWS( + cmv1.NewAWS(). + STS( + cmv1.NewSTS(). + RoleARN("installer"). + SupportRoleARN("support"). + InstanceIAMRoles( + cmv1.NewInstanceIAMRoles(). + MasterRoleARN("controlplane"). + WorkerRoleARN("worker"), + ), + ), + ).Build() + Expect(err).ToNot(HaveOccurred()) + err = IamRoleArnsValidator(fakeCluster) + Expect(err).ToNot(HaveOccurred()) + }) + }) }) diff --git a/pkg/rosa/operatorroles/operatorroles.go b/pkg/rosa/operatorroles/operatorroles.go new file mode 100644 index 00000000..ef0d9ae4 --- /dev/null +++ b/pkg/rosa/operatorroles/operatorroles.go @@ -0,0 +1,13 @@ +package operatorroles + +import ( + cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" +) + +func GetOperatorRolesArnsMap(cluster *cmv1.Cluster) map[string]string { + operatorRolesMap := map[string]string{} + for _, role := range cluster.AWS().STS().OperatorIAMRoles() { + operatorRolesMap[role.Name()] = role.RoleARN() + } + return operatorRolesMap +} diff --git a/pkg/rosa/operatorroles/operatorroles_suite_test.go b/pkg/rosa/operatorroles/operatorroles_suite_test.go new file mode 100644 index 00000000..23264a14 --- /dev/null +++ b/pkg/rosa/operatorroles/operatorroles_suite_test.go @@ -0,0 +1,13 @@ +package operatorroles_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestOperatorroles(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Operatorroles Suite") +} diff --git a/pkg/rosa/operatorroles/operatorroles_test.go b/pkg/rosa/operatorroles/operatorroles_test.go new file mode 100644 index 00000000..d13352f7 --- /dev/null +++ b/pkg/rosa/operatorroles/operatorroles_test.go @@ -0,0 +1,34 @@ +package operatorroles_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/openshift-online/ocm-common/pkg/rosa/operatorroles" + cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" +) + +var _ = Describe("Operator Role functions", func() { + var _ = Describe("Validates GetOperatorRolesArnsMap function", func() { + It("Checks the operator roles are retrieved from a cluster", func() { + fakeCluster, err := cmv1.NewCluster(). + AWS( + cmv1.NewAWS(). + STS( + cmv1.NewSTS(). + OperatorIAMRoles( + cmv1.NewOperatorIAMRole().Name("operator-role-1").RoleARN("arn-1"), + cmv1.NewOperatorIAMRole().Name("operator-role-2").RoleARN("arn-2"), + cmv1.NewOperatorIAMRole().Name("operator-role-3").RoleARN("arn-3"), + ), + ), + ).Build() + Expect(err).ToNot(HaveOccurred()) + operatorRolesMap := GetOperatorRolesArnsMap(fakeCluster) + Expect(len(operatorRolesMap)).To(Equal(3)) + for name, arn := range operatorRolesMap { + Expect(name).ToNot(Equal("")) + Expect(arn).ToNot(Equal("")) + } + }) + }) +})