diff --git a/api/authorization/user_client_factory_test.go b/api/authorization/user_client_factory_test.go index 036bf58b5..7ef097889 100644 --- a/api/authorization/user_client_factory_test.go +++ b/api/authorization/user_client_factory_test.go @@ -237,7 +237,7 @@ var _ = Describe("Unprivileged User Client Factory", func() { When("the cert is not valid on this cluster", func() { BeforeEach(func() { - authInfo.CertData = helpers.CreateCertificatePEM() + authInfo.CertData = helpers.CreateSelfSignedCertificatePEM() }) It("creates an unusable client", func() { diff --git a/tests/e2e/authorization_test.go b/tests/e2e/authorization_test.go index ec6664981..ac66ba6ad 100644 --- a/tests/e2e/authorization_test.go +++ b/tests/e2e/authorization_test.go @@ -1,7 +1,6 @@ package e2e_test import ( - "crypto/tls" "net/http" "code.cloudfoundry.org/korifi/tests/helpers" @@ -13,9 +12,6 @@ import ( var _ = Describe("Authorization", func() { var ( - serviceaccountFactory *helpers.ServiceAccountFactory - svcAcctName string - userName string userClient *helpers.CorrelatedRestyClient @@ -24,18 +20,12 @@ var _ = Describe("Authorization", func() { ) BeforeEach(func() { - serviceaccountFactory = helpers.NewServiceAccountFactory(rootNamespace) - svcAcctName = uuid.NewString() - - userName = "system:serviceaccount:cf:" + svcAcctName - userToken := serviceaccountFactory.CreateServiceAccount(svcAcctName) - userClient = helpers.NewCorrelatedRestyClient(apiServerRoot, getCorrelationId). - SetAuthToken(userToken). - SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) + userName = uuid.NewString() + userClient = makeTokenClient(serviceAccountFactory.CreateServiceAccount(userName)) }) AfterEach(func() { - serviceaccountFactory.DeleteServiceAccount(svcAcctName) + serviceAccountFactory.DeleteServiceAccount(userName) }) Describe("Unauthorized users", func() { @@ -77,11 +67,11 @@ var _ = Describe("Authorization", func() { BeforeEach(func() { orgName := generateGUID("org") orgGUID = createOrg(orgName) - createOrgRole("organization_user", userName, orgGUID) + createOrgRole("organization_user", serviceAccountFactory.FullyQualifiedName(userName), orgGUID) spaceName := generateGUID("space") spaceGUID = createSpace(spaceName, orgGUID) - createSpaceRole("space_developer", userName, spaceGUID) + createSpaceRole("space_developer", serviceAccountFactory.FullyQualifiedName(userName), spaceGUID) }) AfterEach(func() { diff --git a/tests/e2e/e2e_suite_test.go b/tests/e2e/e2e_suite_test.go index 47e102413..102b2e763 100644 --- a/tests/e2e/e2e_suite_test.go +++ b/tests/e2e/e2e_suite_test.go @@ -4,6 +4,7 @@ import ( "archive/zip" "context" "crypto/tls" + "encoding/base64" "encoding/json" "fmt" "io" @@ -38,21 +39,12 @@ import ( var ( correlationId string - adminClient *helpers.CorrelatedRestyClient - privilegedServiceAccountClient *helpers.CorrelatedRestyClient - longCertClient *helpers.CorrelatedRestyClient + serviceAccountFactory *helpers.ServiceAccountFactory + adminServiceAccount string + adminClient *helpers.CorrelatedRestyClient apiServerRoot string - serviceAccountName string - serviceAccountToken string - - certUserName string - certPEM string - - longCertUserName string - longCertPEM string - rootNamespace string appFQDN string commonTestOrgGUID string @@ -307,14 +299,22 @@ func TestE2E(t *testing.T) { } type sharedSetupData struct { - CommonOrgName string `json:"commonOrgName"` - CommonOrgGUID string `json:"commonOrgGuid"` - DefaultAppBitsFile string `json:"defaultAppBitsFile"` - MultiProcessAppBitsFile string `json:"multiProcessAppBitsFile"` + CommonOrgName string `json:"commonOrgName"` + CommonOrgGUID string `json:"commonOrgGuid"` + DefaultAppBitsFile string `json:"defaultAppBitsFile"` + MultiProcessAppBitsFile string `json:"multiProcessAppBitsFile"` + AdminServiceAccount string `json:"admin_service_account"` + AdminServiceAccountToken string `json:"admin_service_account_token"` } var _ = SynchronizedBeforeSuite(func() []byte { commonTestSetup() + + adminServiceAccount = uuid.NewString() + adminServiceAccountToken := serviceAccountFactory.CreateAdminServiceAccount(adminServiceAccount) + + adminClient = makeTokenClient(adminServiceAccountToken) + commonTestOrgName = generateGUID("common-test-org") commonTestOrgGUID = createOrg(commonTestOrgName) @@ -329,8 +329,10 @@ var _ = SynchronizedBeforeSuite(func() []byte { // The DEFAULT_APP_BITS_PATH and DEFAULT_APP_RESPONSE environment variables are a workaround to allow e2e tests to run // with a different app in these environments. // See https://github.com/cloudfoundry/korifi/issues/2355 for refactoring ideas - DefaultAppBitsFile: zipAsset(helpers.GetDefaultedEnvVar("DEFAULT_APP_BITS_PATH", "../assets/dorifi")), - MultiProcessAppBitsFile: zipAsset("../assets/multi-process"), + DefaultAppBitsFile: zipAsset(helpers.GetDefaultedEnvVar("DEFAULT_APP_BITS_PATH", "../assets/dorifi")), + MultiProcessAppBitsFile: zipAsset("../assets/multi-process"), + AdminServiceAccount: adminServiceAccount, + AdminServiceAccountToken: adminServiceAccountToken, } bs, err := json.Marshal(sharedData) @@ -338,6 +340,8 @@ var _ = SynchronizedBeforeSuite(func() []byte { return bs }, func(bs []byte) { + commonTestSetup() + var sharedSetup sharedSetupData err := json.Unmarshal(bs, &sharedSetup) Expect(err).NotTo(HaveOccurred()) @@ -346,46 +350,44 @@ var _ = SynchronizedBeforeSuite(func() []byte { commonTestOrgName = sharedSetup.CommonOrgName defaultAppBitsFile = sharedSetup.DefaultAppBitsFile multiProcessAppBitsFile = sharedSetup.MultiProcessAppBitsFile + adminServiceAccount = sharedSetup.AdminServiceAccount + adminClient = makeTokenClient(sharedSetup.AdminServiceAccountToken) - eventuallyTimeoutSeconds := 240 - customEventuallyTimeoutSeconds := os.Getenv("E2E_EVENTUALLY_TIMEOUT_SECONDS") - if customEventuallyTimeoutSeconds != "" { - eventuallyTimeoutSeconds, err = strconv.Atoi(customEventuallyTimeoutSeconds) - Expect(err).NotTo(HaveOccurred()) - } - SetDefaultEventuallyTimeout(time.Duration(eventuallyTimeoutSeconds) * time.Second) + SetDefaultEventuallyTimeout(helpers.EventuallyTimeout()) SetDefaultEventuallyPollingInterval(2 * time.Second) logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - commonTestSetup() }) var _ = SynchronizedAfterSuite(func() { }, func() { os.RemoveAll(assetsTmpDir) deleteOrg(commonTestOrgGUID) + serviceAccountFactory.DeleteServiceAccount(adminServiceAccount) }) var _ = BeforeEach(func() { correlationId = uuid.NewString() }) -func makeClient(certEnvVar, tokenEnvVar string) *helpers.CorrelatedRestyClient { +func makeCertClientForUserName(userName string, validFor time.Duration) *helpers.CorrelatedRestyClient { GinkgoHelper() - cert := os.Getenv(certEnvVar) - if cert != "" { - return helpers.NewCorrelatedRestyClient(apiServerRoot, getCorrelationId).SetAuthScheme("ClientCert").SetAuthToken(cert).SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) + if os.Getenv("CLUSTER_TYPE") == "EKS" { + Skip("EKS does not support cert users: https://github.com/aws/containers-roadmap/issues/1604#issuecomment-1072660824") } - token := os.Getenv(tokenEnvVar) - if token != "" { - return helpers.NewCorrelatedRestyClient(apiServerRoot, getCorrelationId).SetAuthToken(token).SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) - } + return helpers.NewCorrelatedRestyClient(apiServerRoot, getCorrelationId). + SetAuthScheme("ClientCert"). + SetAuthToken(base64.StdEncoding.EncodeToString(helpers.CreateTrustedCertificatePEM(userName, validFor))). + SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) +} - Fail(fmt.Sprintf("One of %q or %q should have a value, but they are both empty", certEnvVar, tokenEnvVar)) - return nil +func makeTokenClient(token string) *helpers.CorrelatedRestyClient { + return helpers.NewCorrelatedRestyClient(apiServerRoot, getCorrelationId). + SetAuthScheme("Bearer"). + SetAuthToken(token). + SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) } func ensureServerIsUp() { @@ -1025,14 +1027,6 @@ func addDestinationForRoute(appGUID, routeGUID string) []string { func commonTestSetup() { apiServerRoot = helpers.GetRequiredEnvVar("API_SERVER_ROOT") rootNamespace = helpers.GetRequiredEnvVar("ROOT_NAMESPACE") - serviceAccountName = fmt.Sprintf("system:serviceaccount:%s:%s", rootNamespace, helpers.GetRequiredEnvVar("E2E_SERVICE_ACCOUNT")) - serviceAccountToken = helpers.GetRequiredEnvVar("E2E_SERVICE_ACCOUNT_TOKEN") - - longCertUserName = helpers.GetRequiredEnvVar("E2E_LONGCERT_USER_NAME") - longCertPEM = os.Getenv("E2E_LONGCERT_USER_PEM") - - certUserName = helpers.GetRequiredEnvVar("E2E_USER_NAME") - certPEM = os.Getenv("E2E_USER_PEM") appFQDN = helpers.GetRequiredEnvVar("APP_FQDN") @@ -1041,9 +1035,7 @@ func commonTestSetup() { ensureServerIsUp() - adminClient = makeClient("CF_ADMIN_PEM", "CF_ADMIN_TOKEN") - privilegedServiceAccountClient = helpers.NewCorrelatedRestyClient(apiServerRoot, getCorrelationId).SetAuthToken(serviceAccountToken).SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) - longCertClient = helpers.NewCorrelatedRestyClient(apiServerRoot, getCorrelationId).SetAuthScheme("ClientCert").SetAuthToken(longCertPEM).SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) + serviceAccountFactory = helpers.NewServiceAccountFactory(rootNamespace) } func zipAsset(src string) string { diff --git a/tests/e2e/orgs_test.go b/tests/e2e/orgs_test.go index cd9e11a0d..d7d77dd01 100644 --- a/tests/e2e/orgs_test.go +++ b/tests/e2e/orgs_test.go @@ -4,8 +4,10 @@ import ( "fmt" "net/http" "sync" + "time" "github.com/go-resty/resty/v2" + "github.com/google/uuid" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" @@ -119,12 +121,11 @@ var _ = Describe("Orgs", func() { // that gets called by the CLI on each login. When("The client has a certificate with a long expiry date", func() { BeforeEach(func() { - if longCertPEM == "" { - Skip("No certificate with a long expiry date provided") - } - restyClient = longCertClient - createOrgRole("organization_manager", longCertUserName, org2GUID) + userName := uuid.NewString() + restyClient = makeCertClientForUserName(userName, 365*24*time.Hour) + createOrgRole("organization_manager", userName, org2GUID) }) + It("returns orgs that the client has a role in and sets an HTTP warning header", func() { Expect(resp).To(HaveRestyStatusCode(http.StatusOK)) Expect(resp).To(HaveRestyHeaderWithValue("X-Cf-Warnings", HavePrefix("Warning: The client certificate you provided for user authentication expires at"))) diff --git a/tests/e2e/whoami_test.go b/tests/e2e/whoami_test.go index c41ff232e..2fd5ad877 100644 --- a/tests/e2e/whoami_test.go +++ b/tests/e2e/whoami_test.go @@ -4,9 +4,11 @@ import ( "crypto/tls" "encoding/base64" "net/http" + "time" "code.cloudfoundry.org/korifi/tests/helpers" "github.com/go-resty/resty/v2" + "github.com/google/uuid" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" rbacv1 "k8s.io/api/rbac/v1" @@ -19,15 +21,11 @@ type identityResource struct { var _ = Describe("WhoAmI", func() { var ( - client *resty.Client + client *helpers.CorrelatedRestyClient httpResp *resty.Response result identityResource ) - BeforeEach(func() { - client = resty.New().SetBaseURL(apiServerRoot).SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) - }) - JustBeforeEach(func() { var err error httpResp, err = client.R(). @@ -37,13 +35,21 @@ var _ = Describe("WhoAmI", func() { }) When("authenticating with a Bearer token", func() { + var svcAcctName string + BeforeEach(func() { - client = client.SetAuthToken(serviceAccountToken) + svcAcctName = uuid.NewString() + serviceAccountToken := serviceAccountFactory.CreateServiceAccount(svcAcctName) + client = makeTokenClient(serviceAccountToken) + }) + + AfterEach(func() { + serviceAccountFactory.DeleteServiceAccount(svcAcctName) }) It("returns the user identity", func() { Expect(httpResp).To(HaveRestyStatusCode(http.StatusOK)) - Expect(result.Name).To(Equal(serviceAccountName)) + Expect(result.Name).To(Equal(serviceAccountFactory.FullyQualifiedName(svcAcctName))) Expect(result.Kind).To(Equal(rbacv1.ServiceAccountKind)) }) @@ -59,16 +65,16 @@ var _ = Describe("WhoAmI", func() { }) When("authenticating with a client certificate", func() { + var userName string + BeforeEach(func() { - if certPEM == "" { - Skip("No certificate provided.") - } - client = client.SetAuthScheme("ClientCert").SetAuthToken(certPEM) + userName = uuid.NewString() + client = makeCertClientForUserName(userName, time.Hour) }) It("returns the user identity", func() { Expect(httpResp).To(HaveRestyStatusCode(http.StatusOK)) - Expect(result.Name).To(Equal(certUserName)) + Expect(result.Name).To(Equal(userName)) Expect(result.Kind).To(Equal(rbacv1.UserKind)) }) @@ -84,8 +90,7 @@ var _ = Describe("WhoAmI", func() { When("the cert is unauthorized", func() { BeforeEach(func() { - unauthorisedCertPEM := base64.StdEncoding.EncodeToString(helpers.CreateCertificatePEM()) - client = client.SetAuthToken(unauthorisedCertPEM) + client = client.SetAuthToken(base64.StdEncoding.EncodeToString(helpers.CreateSelfSignedCertificatePEM())) }) It("returns an unauthorized error", func() { @@ -95,6 +100,9 @@ var _ = Describe("WhoAmI", func() { }) When("no Authorization header is available in the request", func() { + BeforeEach(func() { + client = helpers.NewCorrelatedRestyClient(apiServerRoot, getCorrelationId).SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) + }) It("returns unauthorized error", func() { Expect(httpResp).To(HaveRestyStatusCode(http.StatusUnauthorized)) }) diff --git a/tests/helpers/cert_auth.go b/tests/helpers/cert_auth.go new file mode 100644 index 000000000..3b328660f --- /dev/null +++ b/tests/helpers/cert_auth.go @@ -0,0 +1,172 @@ +package helpers + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "time" + + "code.cloudfoundry.org/korifi/tools" + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" //lint:ignore ST1001 this is a test file + . "github.com/onsi/gomega" //lint:ignore ST1001 this is a test file + certv1 "k8s.io/api/certificates/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + controllerruntime "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func CreateSelfSignedCertificatePEM() []byte { + certPrivKey := generateRSAKey() + + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"cf-on-k8s"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 180), + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + certBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &certPrivKey.PublicKey, certPrivKey) + Expect(err).NotTo(HaveOccurred()) + + keyPem := pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), + }) + + certPem := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + + return append(certPem, keyPem...) +} + +func CreateTrustedCertificatePEM(userName string, validFor time.Duration) []byte { + certPrivKey := generateRSAKey() + certPem := generateTrustedCertificate(certPrivKey, userName, validFor) + + keyPem := pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), + }) + + return append(certPem, keyPem...) +} + +func generateRSAKey() *rsa.PrivateKey { + GinkgoHelper() + + key, err := rsa.GenerateKey(rand.Reader, 4096) + Expect(err).NotTo(HaveOccurred()) + + return key +} + +func generateTrustedCertificate(key *rsa.PrivateKey, userName string, validFor time.Duration) []byte { + GinkgoHelper() + + config, err := controllerruntime.GetConfig() + Expect(err).NotTo(HaveOccurred()) + + Expect(certv1.AddToScheme(scheme.Scheme)).To(Succeed()) + k8sClient, err := client.New(config, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + + csr := generateCSR(k8sClient, key, userName, validFor) + defer func() { + err := k8sClient.Delete(context.Background(), csr) + if err != nil { + fmt.Printf("failed to delete csr %q: %v", csr.Name, err) + } + }() + + approveCSR(csr) + + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(context.Background(), client.ObjectKeyFromObject(csr), csr)).To(Succeed()) + + g.Expect(csr.Status.Certificate).NotTo(BeEmpty()) + }).WithTimeout(EventuallyTimeout()).Should(Succeed()) + + return csr.Status.Certificate +} + +func generateCSR(k8sClient client.Client, key *rsa.PrivateKey, userName string, validFor time.Duration) *certv1.CertificateSigningRequest { + GinkgoHelper() + + subject := pkix.Name{ + CommonName: userName, + } + + createCertificateRequest, err := x509.CreateCertificateRequest( + rand.Reader, + &x509.CertificateRequest{ + Subject: subject, + // DNSNames: []string{commonName}, + }, + key, + ) + Expect(err).NotTo(HaveOccurred()) + + certRequest := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE REQUEST", Bytes: createCertificateRequest, + }) + + Expect(certv1.AddToScheme(scheme.Scheme)).To(Succeed()) + + csr := &certv1.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + }, + Spec: certv1.CertificateSigningRequestSpec{ + Request: certRequest, + SignerName: "kubernetes.io/kube-apiserver-client", + ExpirationSeconds: tools.PtrTo(int32(validFor.Seconds())), + Usages: []certv1.KeyUsage{"client auth"}, + }, + } + err = k8sClient.Create(context.Background(), csr) + Expect(err).NotTo(HaveOccurred()) + + return csr +} + +func approveCSR(csr *certv1.CertificateSigningRequest) { + GinkgoHelper() + + config, err := controllerruntime.GetConfig() + Expect(err).NotTo(HaveOccurred()) + clientSet, err := kubernetes.NewForConfig(config) + Expect(err).NotTo(HaveOccurred()) + + csr.Status.Conditions = append(csr.Status.Conditions, certv1.CertificateSigningRequestCondition{ + Type: certv1.RequestConditionType(certv1.CertificateApproved), + Status: corev1.ConditionTrue, + Reason: "e2e-approved", + Message: "approved by e2e suite test", + LastUpdateTime: metav1.Now(), + }) + + _, err = clientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval( + context.Background(), + csr.Name, + csr, + metav1.UpdateOptions{}, + ) + Expect(err).NotTo(HaveOccurred()) +} diff --git a/tests/helpers/cert_auth_header.go b/tests/helpers/cert_auth_header.go deleted file mode 100644 index 5b3d0c27e..000000000 --- a/tests/helpers/cert_auth_header.go +++ /dev/null @@ -1,50 +0,0 @@ -package helpers - -import ( - "bytes" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "math/big" - "time" - - . "github.com/onsi/gomega" //lint:ignore ST1001 this is a test file -) - -func CreateCertificatePEM() []byte { - certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) - Expect(err).NotTo(HaveOccurred()) - - cert := &x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{"cf-on-k8s"}, - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(time.Hour * 24 * 180), - - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } - - certBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &certPrivKey.PublicKey, certPrivKey) - Expect(err).NotTo(HaveOccurred()) - - out := new(bytes.Buffer) - err = pem.Encode(out, &pem.Block{ - Type: "CERTIFICATE", - Bytes: certBytes, - }) - Expect(err).NotTo(HaveOccurred()) - - err = pem.Encode(out, &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), - }) - Expect(err).NotTo(HaveOccurred()) - - return out.Bytes() -} diff --git a/tests/helpers/eventually.go b/tests/helpers/eventually.go new file mode 100644 index 000000000..3b7d22a74 --- /dev/null +++ b/tests/helpers/eventually.go @@ -0,0 +1,27 @@ +package helpers + +import ( + "os" + "strconv" + "time" + + . "github.com/onsi/gomega" //lint:ignore ST1001 this is a test file +) + +func EventuallyShouldHold(condition func(g Gomega)) { + Eventually(condition).WithTimeout(EventuallyTimeout()).Should(Succeed()) + Consistently(condition).Should(Succeed()) +} + +func EventuallyTimeout() time.Duration { + eventuallyTimeout := 4 * time.Minute + eventuallyTimeoutSecondsString := os.Getenv("E2E_EVENTUALLY_TIMEOUT_SECONDS") + + if eventuallyTimeoutSecondsString != "" { + eventuallyTimeoutSeconds, err := strconv.Atoi(eventuallyTimeoutSecondsString) + Expect(err).NotTo(HaveOccurred()) + eventuallyTimeout = time.Duration(eventuallyTimeoutSeconds) * time.Second + } + + return eventuallyTimeout +} diff --git a/tests/helpers/eventually_should_hold.go b/tests/helpers/eventually_should_hold.go deleted file mode 100644 index d4f4c2c34..000000000 --- a/tests/helpers/eventually_should_hold.go +++ /dev/null @@ -1,10 +0,0 @@ -package helpers - -import ( - . "github.com/onsi/gomega" //lint:ignore ST1001 this is a test file -) - -func EventuallyShouldHold(condition func(g Gomega)) { - Eventually(condition).Should(Succeed()) - Consistently(condition).Should(Succeed()) -} diff --git a/tests/helpers/users.go b/tests/helpers/service_account.go similarity index 55% rename from tests/helpers/users.go rename to tests/helpers/service_account.go index 352c0f67f..742398506 100644 --- a/tests/helpers/users.go +++ b/tests/helpers/service_account.go @@ -2,12 +2,15 @@ package helpers import ( "context" + "fmt" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" controllerruntime "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" . "github.com/onsi/ginkgo/v2" //lint:ignore ST1001 this is a test file . "github.com/onsi/gomega" //lint:ignore ST1001 this is a test file @@ -36,12 +39,49 @@ func NewServiceAccountFactory(rootNamespace string) *ServiceAccountFactory { func (f *ServiceAccountFactory) CreateServiceAccount(name string) string { GinkgoHelper() - Expect(f.k8sClient.Create(context.Background(), &corev1.ServiceAccount{ + _, serviceAccountToken := f.createServiceAccount(name) + return serviceAccountToken +} + +func (f *ServiceAccountFactory) CreateAdminServiceAccount(adminServiceAccount string) string { + GinkgoHelper() + + serviceAccount, adminServiceAccountToken := f.createServiceAccount(adminServiceAccount) + + adminRoleBinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: f.rootNamespace, + Name: adminServiceAccount, + Annotations: map[string]string{ + "cloudfoundry.org/propagate-cf-role": "true", + }, + }, + Subjects: []rbacv1.Subject{{ + Kind: rbacv1.ServiceAccountKind, + Name: adminServiceAccount, + Namespace: f.rootNamespace, + }}, + RoleRef: rbacv1.RoleRef{ + Kind: "ClusterRole", + Name: "korifi-controllers-admin", + }, + } + Expect(controllerutil.SetOwnerReference(serviceAccount, adminRoleBinding, scheme.Scheme)).To(Succeed()) + Expect(f.k8sClient.Create(context.Background(), adminRoleBinding)).To(Succeed()) + + return adminServiceAccountToken +} + +func (f *ServiceAccountFactory) createServiceAccount(name string) (*corev1.ServiceAccount, string) { + GinkgoHelper() + + serviceAccount := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Namespace: f.rootNamespace, Name: name, }, - })).To(Succeed()) + } + Expect(f.k8sClient.Create(context.Background(), serviceAccount)).To(Succeed()) serviceAccountSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -64,7 +104,7 @@ func (f *ServiceAccountFactory) CreateServiceAccount(name string) string { g.Expect(serviceAccountSecret.Data).To(HaveKey(corev1.ServiceAccountTokenKey)) }).Should(Succeed()) - return string(serviceAccountSecret.Data[corev1.ServiceAccountTokenKey]) + return serviceAccount, string(serviceAccountSecret.Data[corev1.ServiceAccountTokenKey]) } func (f *ServiceAccountFactory) DeleteServiceAccount(name string) { @@ -77,3 +117,7 @@ func (f *ServiceAccountFactory) DeleteServiceAccount(name string) { }, })).To(Succeed()) } + +func (f *ServiceAccountFactory) FullyQualifiedName(svcAcctName string) string { + return fmt.Sprintf("system:serviceaccount:%s:%s", f.rootNamespace, svcAcctName) +}