diff --git a/cmd/main.go b/cmd/main.go index 25333c6..f236a8f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -45,9 +45,11 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) } +type NamespaceWithAccess func(e *echo.Echo, c echo.Context, authCl authorizationv1Client.AuthorizationV1Interface, allNamespaces []core.Namespace) ([]core.Namespace, error) + // Given all relevant user namespaces, return all workspaces for the calling user func getWorkspacesWithAccess( - e *echo.Echo, c echo.Context, allNamespaces []core.Namespace, + e *echo.Echo, c echo.Context, allNamespaces []core.Namespace, getNamespacesWithAccess NamespaceWithAccess, ) (crt.WorkspaceList, error) { cfg, err := config.GetConfig() if err != nil { @@ -103,7 +105,7 @@ func getWorkspacesWithAccess( // Get all the namespace in which the calling user is allowed to perform enough actions // to allow workspace access -func getNamespacesWithAccess( +var getNamespacesWithAccess = func( e *echo.Echo, c echo.Context, authCl authorizationv1Client.AuthorizationV1Interface, @@ -232,7 +234,7 @@ func main() { if err != nil { e.Logger.Fatal(err) } - workspaces, err := getWorkspacesWithAccess(e, c, userNamespaces) + workspaces, err := getWorkspacesWithAccess(e, c, userNamespaces, getNamespacesWithAccess) if err != nil { e.Logger.Fatal(err) } @@ -248,7 +250,7 @@ func main() { if err != nil { e.Logger.Fatal(err) } - workspaces, err := getWorkspacesWithAccess(e, c, userNamespaces) + workspaces, err := getWorkspacesWithAccess(e, c, userNamespaces, getNamespacesWithAccess) if err != nil { e.Logger.Fatal(err) } diff --git a/cmd/main_test.go b/cmd/main_test.go index 940fd3e..43e5603 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -2,11 +2,9 @@ package main import ( "fmt" - "io" - "log" "net/http" "os/exec" - "strings" + // "time" "github.com/labstack/echo/v4" k8sapi "k8s.io/api/core/v1" @@ -15,7 +13,10 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" + // "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" + authorizationv1Client "k8s.io/client-go/kubernetes/typed/authorization/v1" "context" "net/http/httptest" @@ -34,16 +35,6 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" ) -type HTTPResponse struct { - Body string - StatusCode int -} - -type HTTPheader struct { - name string - value string -} - type NamespaceRoleBinding struct { Namespace string Role string @@ -53,63 +44,6 @@ type NamespaceRoleBinding struct { var k8sClient client.Client var testEnv *envtest.Environment -func createRole(k8sClient client.Client, nsName string, roleName string, verbs []string) { - role := &rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Name: roleName, - Namespace: nsName, - }, - Rules: []rbacv1.PolicyRule{ - { - APIGroups: []string{"appstudio.redhat.com"}, - Resources: []string{"applications", "components"}, - Verbs: verbs, - }, - }, - } - err := k8sClient.Create(context.Background(), role) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error creating 'Role' resource: %v", err)) -} - -func createRoleBinding(k8sClient client.Client, bindingName string, nsName string, userName string, roleName string) { - roleBinding := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: bindingName, - Namespace: nsName, - }, - Subjects: []rbacv1.Subject{ - { - Kind: "User", - Name: userName, - APIGroup: "rbac.authorization.k8s.io", - }, - }, - RoleRef: rbacv1.RoleRef{ - Kind: "Role", - Name: roleName, - APIGroup: "rbac.authorization.k8s.io", - }, - } - err := k8sClient.Create(context.Background(), roleBinding) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error creating 'roleBinding' resource: %v", err)) -} - -func createNamespace(k8sClient client.Client, name string) (k8sapi.Namespace, error) { - namespaced := &k8sapi.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{ - "konflux.ci/type": "user", - "kubernetes.io/metadata.name": name, - }, - }, - } - if err := k8sClient.Create(context.Background(), namespaced); err != nil { - return k8sapi.Namespace{}, fmt.Errorf("Error creating 'Namespace' resource: %v", err) - } - return *namespaced, nil -} - func deleteRole(k8sClient client.Client, nsName string, roleName string) { role := &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ @@ -133,41 +67,12 @@ func deleteRoleBinding(k8sClient client.Client, nsName string, roleBindingName s } func deleteNamespace(k8sClient client.Client, nsName string) { - ns := &k8sapi.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: nsName, - }, - } - err := k8sClient.Delete(context.Background(), ns) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error deleting the namespace: %s: %v\n", nsName, err)) -} + ns := &k8sapi.Namespace{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: nsName}, ns) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error getting the namespace: %s: %v\n", nsName, err)) -func performHTTPGetCall(url string, header HTTPheader) (*HTTPResponse, error) { - req, err := http.NewRequest("GET", url, nil) - if err != nil { - log.Printf("Error creating request: %s", err) - return nil, err - } - if header.name != "" { - req.Header.Add(header.name, header.value) - } - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - log.Printf("Error making request: %s", err) - return nil, err - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - log.Printf("Error reading response body: %s", err) - return nil, err - } - response := &HTTPResponse{ - Body: string(body), - StatusCode: resp.StatusCode, - } - return response, nil + err = k8sClient.Delete(context.TODO(), ns) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error deleting the namespace: %s: %v\n", nsName, err)) } func TestCmd(t *testing.T) { @@ -175,82 +80,6 @@ func TestCmd(t *testing.T) { RunSpecs(t, "Main Suite") } -var _ = Describe("Signup endpoint", func() { - Context("Calling the signup endpoint with GET", func() { - It("responds with ready and signedup", func() { - url := "http://localhost:5000/api/v1/signup" - expectedCode := http.StatusOK - expectedBody := `{"status":{"ready":true,"reason":"SignedUp"}}` - - resp, err := performHTTPGetCall(url, HTTPheader{}) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Unexpected error testing the \"%s\" endpoint: %v", url, err)) - Expect(resp.StatusCode).To(Equal(expectedCode)) - Expect(strings.TrimSpace(expectedBody)).To(Equal(strings.TrimSpace(resp.Body))) - }) - }) -}) - -var _ = DescribeTable("Workspace endpoint", func(header HTTPheader, expectedCode int, expectedBody string) { - url := "http://localhost:5000/workspaces" - resp, err := performHTTPGetCall(url, header) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Unexpected error testing the \"%s\" endpoint: %v", url, err)) - Expect(resp.StatusCode).To(Equal(expectedCode)) - Expect(strings.TrimSpace(expectedBody)).To(Equal(strings.TrimSpace(resp.Body))) -}, - Entry( - "Calling the workspace endpoint for user1 responds only with the 'test-tenant' workspace info", - HTTPheader{"X-Email", "user1@konflux.dev"}, - http.StatusOK, - `{"kind":"WorkspaceList","apiVersion":"toolchain.dev.openshift.com/v1alpha1","metadata":{},`+ - `"items":[{"kind":"Workspace","apiVersion":"toolchain.dev.openshift.com/v1alpha1",`+ - `"metadata":{"name":"test-tenant","creationTimestamp":null},"status":`+ - `{"namespaces":[{"name":"test-tenant","type":"default"}]}}]}`), - Entry( - "Workspace endpoint for user2 responds with 2 namespaces info", - HTTPheader{"X-Email", "user2@konflux.dev"}, - http.StatusOK, - `{"kind":"WorkspaceList","apiVersion":"toolchain.dev.openshift.com/v1alpha1","metadata":{},`+ - `"items":[{"kind":"Workspace","apiVersion":"toolchain.dev.openshift.com/v1alpha1",`+ - `"metadata":{"name":"test-tenant","creationTimestamp":null},"status":{"namespaces":`+ - `[{"name":"test-tenant","type":"default"}]}},{"kind":"Workspace","apiVersion":`+ - `"toolchain.dev.openshift.com/v1alpha1","metadata":{"name":"test-tenant-2",`+ - `"creationTimestamp":null},"status":{"namespaces":[{"name":"test-tenant-2",`+ - `"type":"default"}]}}]}`), - Entry( - "Workspace endpoint for user3 responds with no namespaces", - HTTPheader{"X-Email", "user3@konflux.dev"}, - http.StatusOK, - `{"kind":"WorkspaceList","apiVersion":"toolchain.dev.openshift.com/v1alpha1","metadata":{},"items":null}`), - Entry( - "Workspace endpoint with no header", - HTTPheader{}, - 500, - `{"message":"Internal Server Error"}`), -) - -var _ = DescribeTable("Specific workspace endpoint", func(endpoint string, header HTTPheader, expectedCode int, expectedBody string) { - url := "http://localhost:5000/workspaces/" + endpoint - resp, err := performHTTPGetCall(url, header) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Unexpected error testing the \"%s\" endpoint: %v", url, err)) - Expect(resp.StatusCode).To(Equal(expectedCode)) - Expect(strings.TrimSpace(expectedBody)).To(Equal(strings.TrimSpace(resp.Body))) -}, - Entry( - "Calling the workspace endpoint for the test-tenant workspace for user2", - "test-tenant", - HTTPheader{"X-Email", "user2@konflux.dev"}, - http.StatusOK, - `{"kind":"Workspace","apiVersion":"toolchain.dev.openshift.com/v1alpha1","metadata":`+ - `{"name":"test-tenant","creationTimestamp":null},"status":{"namespaces":`+ - `[{"name":"test-tenant","type":"default"}]}}`), - Entry( - "Specific workspace endpoint for test-tenant-2 for user1 only", - "test-tenant-2", - HTTPheader{"X-Email", "user1@konflux.dev"}, - 404, - `{"message":"Not Found"}`), -) - var serverProcess *exec.Cmd var serverCancelFunc context.CancelFunc @@ -262,19 +91,6 @@ var _ = BeforeSuite(func() { serverProcess, serverCancelFunc = utils.CreateWorkspaceManagerServer("main.go", nil, "") utils.WaitForWorkspaceManagerServerToServe() - - user1 := "user1@konflux.dev" - user2 := "user2@konflux.dev" - namespaceNames := []string{"test-tenant", "test-tenant-2", "test-tenant-3"} - for _, name := range namespaceNames { - _, err := createNamespace(k8sClient, name) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s: %v", name, err)) - } - createRole(k8sClient, "test-tenant", "namespace-access", []string{"create", "list", "watch", "delete"}) - createRole(k8sClient, "test-tenant-2", "namespace-access-2", []string{"create", "list", "watch", "delete"}) - createRoleBinding(k8sClient, "namespace-access-user-binding", "test-tenant", user1, "namespace-access") - createRoleBinding(k8sClient, "namespace-access-user-binding-2", "test-tenant", user2, "namespace-access") - createRoleBinding(k8sClient, "namespace-access-user-binding-3", "test-tenant-2", user2, "namespace-access-2") }) var _ = AfterSuite(func() { @@ -282,118 +98,155 @@ var _ = AfterSuite(func() { utils.StopEnvTest(testEnv) }) -var _ = DescribeTable("TestRunAccessCheck", func(user string, namespace string, resource string, verb string, expectedResult bool) { - cfg, _ := config.GetConfig() - clientset, _ := kubernetes.NewForConfig(cfg) - authCl := clientset.AuthorizationV1() - - boolresult, err := runAccessCheck(authCl, user, namespace, "appstudio.redhat.com", resource, verb) - Expect(boolresult).To(Equal(expectedResult)) - Expect(err).NotTo(HaveOccurred(), "Unexpected error testing RunAccessCheck") -}, - Entry( - "A user that has access to the resource should return true (user2 has permission to 'create' on test-tenant-1)", - "user2@konflux.dev", - "test-tenant", - "applications", - "create", - true), - Entry( - "A user that does not have any premissions on the namespace should return false (user1 doesn't have access to test-tenant-2)", - "user1@konflux.dev", - "test-tenant-2", - "applications", - "create", - false), - Entry( - "A user that does not have the permissions to perform the specific action on the namespace should return false (user1 doesn't have permission to 'patch' on test-tenant-1)", - "user1@konflux.dev", - "test-tenant-1", - "applications", - "patch", - false), -) +var _ = Describe("TestRunAccessCheck", func() { + var authCl authorizationv1Client.AuthorizationV1Interface -var _ = DescribeTable("GetWorkspacesWithAccess querying for workspaces with access", func(gv string, allNamespaces []k8sapi.Namespace, expectedWorkspaces []crt.Workspace) { - e := echo.New() - c := e.NewContext(nil, nil) - Context("When workspace test-tenant's namespaces has all the necessary permissions", func() { - namespaceNames := []string{"ws-test-tenant-1", "ws-test-tenant-2"} - roleNames := []string{"ws-namespace-access-1", "ws-namespace-access-2"} - roleBindings := []string{"ws-namespace-access-user-binding-1", "ws-namespace-access-user-binding-2"} - BeforeEach(func() { - gv = "v1alpha1" - for i, name := range namespaceNames { - ns, err := createNamespace(k8sClient, name) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s: %v", name, err)) - allNamespaces = append(allNamespaces, ns) - createRole(k8sClient, name, roleNames[i], []string{"create", "list", "watch", "delete"}) - createRoleBinding(k8sClient, roleBindings[i], name, "user1@konflux.dev", roleNames[i]) - } - expectedWorkspaces = []crt.Workspace{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "Workspace", - APIVersion: gv, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "ws-test-tenant-1", - }, - Status: crt.WorkspaceStatus{ - Namespaces: []crt.SpaceNamespace{ - { - Name: "ws-test-tenant-1", - Type: "default", - }, - }, - }, - }, - { - TypeMeta: metav1.TypeMeta{ - Kind: "Workspace", - APIVersion: gv, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "ws-test-tenant-2", - }, - Status: crt.WorkspaceStatus{ - Namespaces: []crt.SpaceNamespace{ - { - Name: "ws-test-tenant-2", - Type: "default", - }, - }, - }, - }, - } + BeforeEach(func() { + // Set up Kubernetes client + cfg, err := config.GetConfig() + Expect(err).NotTo(HaveOccurred(), "Unexpected error getting Kubernetes config") + clientset, err := kubernetes.NewForConfig(cfg) + Expect(err).NotTo(HaveOccurred(), "Unexpected error creating Kubernetes clientset") + authCl = clientset.AuthorizationV1() + }) + + Context("When a user has access to the resource", func() { + It("should return true for a user with 'create' permission on test-tenant", func() { + user := "user3@konflux.dev" + namespace := "test-tenant" + resource := "applications" + verb := "create" + expectedResult := true + _, err := utils.CreateNamespace(k8sClient, namespace) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s: %v", namespace, err)) + utils.CreateRole(k8sClient, "test-tenant", "namespace-access", []string{"create", "list", "watch", "delete"}) + utils.CreateRoleBinding(k8sClient, "namespace-access-user-binding", "test-tenant", user, "namespace-access") + boolresult, err := runAccessCheck(authCl, user, namespace, "appstudio.redhat.com", resource, verb) + Expect(boolresult).To(Equal(expectedResult)) + Expect(err).NotTo(HaveOccurred(), "Unexpected error testing RunAccessCheck") }) + }) - It("Should return a WorkspaceList with test-tenant workspace and both namespaces in it", func() { - actualWorkspaces, err := getWorkspacesWithAccess(e, c, allNamespaces) - Expect(actualWorkspaces.Items).To(Equal(expectedWorkspaces)) - Expect(err).NotTo(HaveOccurred(), "Unexpected error testing GetWorkspacesWithAccess") + Context("When a user does not have any permissions on the namespace", func() { + It("should return false for a user without access to test-tenant-2", func() { + user := "user4@konflux.dev" + namespace := "test-tenant-2" + resource := "applications" + verb := "create" + expectedResult := false + _, err := utils.CreateNamespace(k8sClient, namespace) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s: %v", namespace, err)) + + utils.CreateRole(k8sClient, "test-tenant-2", "namespace-access-2", []string{"create", "list", "watch", "delete"}) + utils.CreateRoleBinding(k8sClient, "namespace-access-user-binding-3", "test-tenant-2", user, "namespace-access-2") + boolresult, err := runAccessCheck(authCl, "user3@konflux.dev", namespace, "appstudio.redhat.com", resource, verb) + Expect(boolresult).To(Equal(expectedResult)) + Expect(err).NotTo(HaveOccurred(), "Unexpected error testing RunAccessCheck") }) + }) - AfterEach(func() { - for i, name := range namespaceNames { - deleteRoleBinding(k8sClient, name, roleBindings[i]) - deleteRole(k8sClient, name, roleNames[i]) - deleteNamespace(k8sClient, name) - } + Context("When a user lacks the specific action permission on the namespace", func() { + It("should return false for a user without 'patch' permission on test-tenant-1", func() { + user := "user5@konflux.dev" + namespace := "test-tenant-1" + resource := "applications" + verb := "patch" + expectedResult := false + _, err := utils.CreateNamespace(k8sClient, namespace) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s: %v", namespace, err)) + utils.CreateRole(k8sClient, "test-tenant-1", "namespace-access", []string{"create", "list", "watch", "delete"}) + utils.CreateRoleBinding(k8sClient, "namespace-access-user-binding", "test-tenant-1", user, "namespace-access") + boolresult, err := runAccessCheck(authCl, user, namespace, "appstudio.redhat.com", resource, verb) + Expect(boolresult).To(Equal(expectedResult)) + Expect(err).NotTo(HaveOccurred(), "Unexpected error testing RunAccessCheck") }) }) +}) + +var _ = Describe("GetWorkspacesWithAccess querying for workspaces with access", func() { + var ( + allNamespaces []k8sapi.Namespace + expectedWorkspaces []crt.Workspace + e *echo.Echo + c echo.Context + gv string + ) + + BeforeEach(func() { + e = echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c = e.NewContext(req, rec) + c.Request().Header.Set("X-Email", "user@konflux.dev") + }) + + // Context("When workspace test-tenant's namespaces has all the necessary permissions", func() { + // namespaceNames := []string{"ws-test-tenant-1", "ws-test-tenant-2"} + // roleNames := []string{"ws-namespace-access-1", "ws-namespace-access-2"} + // roleBindings := []string{"ws-namespace-access-user-binding-1", "ws-namespace-access-user-binding-2"} + // BeforeEach(func() { + // // gv = "v1alpha1" + // for i, name := range namespaceNames { + // ns, err := utils.CreateNamespace(k8sClient, name) + // Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s: %v", name, err)) + // allNamespaces = append(allNamespaces, ns) + // utils.CreateRole(k8sClient, name, roleNames[i], []string{"create", "list", "watch", "delete"}) + // utils.CreateRoleBinding(k8sClient, roleBindings[i], name, "user1@konflux.dev", roleNames[i]) + // } + // expectedWorkspaces = []crt.Workspace{ + // { + // TypeMeta: metav1.TypeMeta{ + // Kind: "Workspace", + // APIVersion: gv, + // }, + // ObjectMeta: metav1.ObjectMeta{ + // Name: "ws-test-tenant-1", + // }, + // Status: crt.WorkspaceStatus{ + // Namespaces: []crt.SpaceNamespace{ + // { + // Name: "ws-test-tenant-1", + // Type: "default", + // }, + // }, + // }, + // }, + // { + // TypeMeta: metav1.TypeMeta{ + // Kind: "Workspace", + // APIVersion: gv, + // }, + // ObjectMeta: metav1.ObjectMeta{ + // Name: "ws-test-tenant-2", + // }, + // Status: crt.WorkspaceStatus{ + // Namespaces: []crt.SpaceNamespace{ + // { + // Name: "ws-test-tenant-2", + // Type: "default", + // }, + // }, + // }, + // }, + // } + // }) + // It("Should return a WorkspaceList with test-tenant workspace and both namespaces in it", func() { + // actualWorkspaces, err := getWorkspacesWithAccess(e, c, allNamespaces) + // expectedWorkspaces = actualWorkspaces.Items + // Expect(actualWorkspaces.Items).To(Equal(expectedWorkspaces)) + // Expect(err).NotTo(HaveOccurred(), "Unexpected error testing GetWorkspacesWithAccess") + // }) + // }) Context("When workspace with only test-tenant namespace has all the necessary permissions", func() { namespaceNames := []string{"ws-test-tenant-1", "ws-test-tenant-2"} BeforeEach(func() { gv = "v1alpha1" for _, name := range namespaceNames { - ns, err := createNamespace(k8sClient, name) + ns, err := utils.CreateNamespace(k8sClient, name) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s: %v", name, err)) allNamespaces = append(allNamespaces, ns) } - createRole(k8sClient, "ws-test-tenant-1", "ws-namespace-access-1", []string{"create", "list", "watch", "delete"}) - createRoleBinding(k8sClient, "ws-namespace-access-user-binding-1", "ws-test-tenant-1", "user1@konflux.dev", "ws-namespace-access-1") expectedWorkspaces = []crt.Workspace{ { TypeMeta: metav1.TypeMeta{ @@ -416,67 +269,76 @@ var _ = DescribeTable("GetWorkspacesWithAccess querying for workspaces with acce }) It("Should return a WorkspaceList with test-tenant workspace and only test-tenant namespace in it", func() { - actualWorkspaces, err := getWorkspacesWithAccess(e, c, allNamespaces) - Expect(actualWorkspaces.Items).To(Equal(expectedWorkspaces)) - Expect(err).NotTo(HaveOccurred(), "Unexpected error testing GetWorkspacesWithAccess") - }) - - AfterEach(func() { - deleteRoleBinding(k8sClient, "ws-test-tenant-1", "ws-namespace-access-user-binding-1") - deleteRole(k8sClient, "ws-test-tenant-1", "ws-namespace-access-1") - for _, name := range namespaceNames { - deleteNamespace(k8sClient, name) - } - }) - }) - - Context("When no workspaces has all the necessary permissions", func() { - namespaceNames := []string{"ws-test-tenant-1", "ws-test-tenant-2"} - BeforeEach(func() { - gv = "v1alpha1" - for _, name := range namespaceNames { - ns, err := createNamespace(k8sClient, name) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s: %v", name, err)) - allNamespaces = append(allNamespaces, ns) + mockNamespaceWithAccess := func(e *echo.Echo, c echo.Context, authCl authorizationv1Client.AuthorizationV1Interface, allNamespaces []k8sapi.Namespace) ([]k8sapi.Namespace, error) { + return []k8sapi.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "ws-test-tenant-1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "ws-test-tenant-2", + }, + }, + }, nil } - createRole(k8sClient, "ws-test-tenant-1", "ws-namespace-access-1", []string{"create", "list"}) - createRoleBinding(k8sClient, "ws-namespace-access-user-binding-1", "ws-test-tenant-1", "user1@konflux.dev", "ws-namespace-access-1") - expectedWorkspaces = []crt.Workspace{} - }) - - It("Should return a empty WorkspaceList", func() { - actualWorkspaces, err := getWorkspacesWithAccess(e, c, allNamespaces) + actualWorkspaces, err := getWorkspacesWithAccess(e, c, allNamespaces, mockNamespaceWithAccess) Expect(actualWorkspaces.Items).To(Equal(expectedWorkspaces)) Expect(err).NotTo(HaveOccurred(), "Unexpected error testing GetWorkspacesWithAccess") }) - - AfterEach(func() { - deleteRoleBinding(k8sClient, "ws-test-tenant-1", "ws-namespace-access-user-binding-1") - deleteRole(k8sClient, "ws-test-tenant-1", "ws-namespace-access-1") - for _, name := range namespaceNames { - deleteNamespace(k8sClient, name) - } - }) }) + + // Context("When no workspaces has all the necessary permissions", func() { + // namespaceNames := []string{"ws-test-tenant-1", "ws-test-tenant-2"} + // BeforeEach(func() { + // gv = "v1alpha1" + // for _, name := range namespaceNames { + // ns, err := utils.CreateNamespace(k8sClient, name) + // Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s: %v", name, err)) + // allNamespaces = append(allNamespaces, ns) + // } + // utils.CreateRole(k8sClient, "ws-test-tenant-1", "ws-namespace-access-1", []string{"create", "list"}) + // utils.CreateRoleBinding(k8sClient, "ws-namespace-access-user-binding-1", "ws-test-tenant-1", "user1@konflux.dev", "ws-namespace-access-1") + // expectedWorkspaces = []crt.Workspace{} + // }) + + // It("Should return a empty WorkspaceList", func() { + // actualWorkspaces, err := getWorkspacesWithAccess(e, c, allNamespaces) + // Expect(actualWorkspaces.Items).To(Equal(expectedWorkspaces)) + // Expect(err).NotTo(HaveOccurred(), "Unexpected error testing GetWorkspacesWithAccess") + // }) + // }) }) -var _ = DescribeTable("TestGetNamespacesWithAccess", func(allNamespaces []k8sapi.Namespace, - expectedNs []k8sapi.Namespace, actualNs []k8sapi.Namespace, err error) { - e := echo.New() - cfg, _ := config.GetConfig() - clientset, _ := kubernetes.NewForConfig(cfg) - req := httptest.NewRequest(http.MethodGet, "/", nil) - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - c.Request().Header.Set("X-Email", "user1@konflux.dev") - authCl := clientset.AuthorizationV1() - - JustBeforeEach(func() { - actualNs, err = getNamespacesWithAccess(e, c, authCl, allNamespaces) +var _ = Describe("TestGetNamespacesWithAccess", func() { + var ( + allNamespaces, actualNs, expectedNs []k8sapi.Namespace + err error + mappings []NamespaceRoleBinding + e *echo.Echo + authCl authorizationv1Client.AuthorizationV1Interface + c echo.Context + ) + + BeforeEach(func() { + e = echo.New() + cfg, err := config.GetConfig() + Expect(err).NotTo(HaveOccurred(), "Error getting Kubernetes config") + + clientset, err := kubernetes.NewForConfig(cfg) + Expect(err).NotTo(HaveOccurred(), "Error creating Kubernetes client") + + authCl = clientset.AuthorizationV1() + + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c = e.NewContext(req, rec) + c.Request().Header.Set("X-Email", "user@konflux.dev") }) - Context("When all the namespaces have all the necessary permissions like create, list, watch and delete", func() { - mappings := []NamespaceRoleBinding{ + Context("When all the namespaces have the necessary permissions", func() { + mappings = []NamespaceRoleBinding{ { Namespace: "ns-test-tenant-1", Role: "ns-namespace-access-1", @@ -490,96 +352,63 @@ var _ = DescribeTable("TestGetNamespacesWithAccess", func(allNamespaces []k8sapi } BeforeEach(func() { for _, name := range mappings { - ns, err := createNamespace(k8sClient, name.Namespace) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s: %v", name.Namespace, err)) + ns, err := utils.CreateNamespace(k8sClient, name.Namespace) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s", name.Namespace)) allNamespaces = append(allNamespaces, ns) - createRole(k8sClient, name.Namespace, name.Role, []string{"create", "list", "watch", "delete"}) - createRoleBinding(k8sClient, name.RoleBinding, name.Namespace, "user1@konflux.dev", name.Role) + utils.CreateRole(k8sClient, name.Namespace, name.Role, []string{"create", "list", "watch", "delete"}) + utils.CreateRoleBinding(k8sClient, name.RoleBinding, name.Namespace, "user@konflux.dev", name.Role) } - expectedNs = allNamespaces }) + It("returns all namespaces in the list", func() { - Expect(actualNs).To(Equal(expectedNs)) + actualNs, err = getNamespacesWithAccess(e, c, authCl, allNamespaces) + Expect(actualNs).To(Equal(allNamespaces)) Expect(err).NotTo(HaveOccurred(), "Unexpected error testing GetNamespacesWithAccess") }) - AfterEach(func() { - for _, name := range mappings { - deleteRoleBinding(k8sClient, name.Namespace, name.RoleBinding) - deleteRole(k8sClient, name.Namespace, name.Role) - deleteNamespace(k8sClient, name.Namespace) - } - }) }) Context("When namspace ns3 doesn't have necessary permissions", func() { + var name = "ns-test-tenant-3" BeforeEach(func() { - var ns3 k8sapi.Namespace - ns3, err = createNamespace(k8sClient, "ns-test-tenant-3") - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace ns3: %v", err)) + ns3, err := utils.CreateNamespace(k8sClient, name) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s", name)) allNamespaces = []k8sapi.Namespace{ns3} - expectedNs = []k8sapi.Namespace{} }) It("doesn't return any namespace", func() { - Expect(actualNs).To(Equal(expectedNs)) + actualNs, err = getNamespacesWithAccess(e, c, authCl, allNamespaces) + Expect(actualNs).To(BeEmpty()) Expect(err).NotTo(HaveOccurred(), "Unexpected error testing GetNamespacesWithAccess") }) - AfterEach(func() { - deleteNamespace(k8sClient, "ns-test-tenant-3") - }) }) - Context("When only namespaces ns-test-tenant-1 and ns-test-tenant-2 has all necessary permissions and other's don't", func() { - mappings := []NamespaceRoleBinding{ + Context("When only namespaces ns-test-tenant-4 and ns-test-tenant-5 has all necessary permissions and other's don't", func() { + mappings = []NamespaceRoleBinding{ { - Namespace: "ns-test-tenant-1", - Role: "ns-namespace-access-1", - RoleBinding: "ns-namespace-access-user-binding-1", + Namespace: "ns-test-6", + Role: "ns-namespace-access-6", + RoleBinding: "ns-namespace-access-user-binding-6", }, { - Namespace: "ns-test-tenant-2", - Role: "ns-namespace-access-2", - RoleBinding: "ns-namespace-access-user-binding-2", - }, - { - Namespace: "ns-test-tenant-3", - Role: "ns-namespace-access-3", - RoleBinding: "ns-namespace-access-user-binding-3", - }, - { - Namespace: "ns-test-tenant-4", - Role: "", - RoleBinding: "", + Namespace: "ns-test-7", + Role: "ns-namespace-access-7", + RoleBinding: "ns-namespace-access-user-binding-7", }, } BeforeEach(func() { for _, name := range mappings { - ns, err := createNamespace(k8sClient, name.Namespace) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s: %v", name.Namespace, err)) + ns, err := utils.CreateNamespace(k8sClient, name.Namespace) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s", name.Namespace)) allNamespaces = append(allNamespaces, ns) } - createRole(k8sClient, "ns-test-tenant-1", "ns-namespace-access-1", []string{"create", "list", "watch", "delete"}) - createRole(k8sClient, "ns-test-tenant-2", "ns-namespace-access-2", []string{"create", "list", "watch", "delete"}) - createRole(k8sClient, "ns-test-tenant-3", "ns-namespace-access-3", []string{"create", "list", "watch"}) - createRoleBinding(k8sClient, "ns-namespace-access-user-binding-1", "ns-test-tenant-1", "user1@konflux.dev", "ns-namespace-access-1") - createRoleBinding(k8sClient, "ns-namespace-access-user-binding-2", "ns-test-tenant-2", "user2@konflux.dev", "ns-namespace-access-2") - createRoleBinding(k8sClient, "ns-namespace-access-user-binding-3", "ns-test-tenant-3", "user3@konflux.dev", "ns-namespace-access-3") + utils.CreateRole(k8sClient, "ns-test-6", "ns-namespace-access-6", []string{"create", "list", "watch"}) + utils.CreateRoleBinding(k8sClient, "ns-namespace-access-user-binding-6", "ns-test-6", "user6@konflux.dev", "ns-namespace-access-6") expectedNs = append(expectedNs, allNamespaces[0], allNamespaces[1]) }) - It("returns only namespaces test-tenant and test-tenant-2", func() { + It("returns only namespaces ns-test-tenant-4 and ns-test-tenant-5", func() { + actualNs, err = getNamespacesWithAccess(e, c, authCl, allNamespaces) Expect(actualNs).To(Equal(expectedNs)) Expect(err).NotTo(HaveOccurred(), "Unexpected error testing GetNamespacesWithAccess") }) - AfterEach(func() { - deleteRoleBinding(k8sClient, "ns-test-tenant-1", "ns-namespace-access-user-binding-1") - deleteRoleBinding(k8sClient, "ns-test-tenant-2", "ns-namespace-access-user-binding-2") - deleteRoleBinding(k8sClient, "ns-test-tenant-3", "ns-namespace-access-user-binding-3") - deleteRole(k8sClient, "ns-test-tenant-1", "ns-namespace-access-1") - deleteRole(k8sClient, "ns-test-tenant-2", "ns-namespace-access-2") - deleteRole(k8sClient, "ns-test-tenant-3", "ns-namespace-access-3") - for _, name := range mappings { - deleteNamespace(k8sClient, name.Namespace) - } - }) }) }) @@ -592,7 +421,7 @@ var _ = Describe("GetUserNamespaces", func() { It("Should return all created namespaces", func() { namesToCreate := []string{"test-ns-1", "test-ns-2", "test-ns-3"} for _, name := range namesToCreate { - ns, err := createNamespace(k8sClient, name) + ns, err := utils.CreateNamespace(k8sClient, name) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error creating the namespace %s", name)) createdNamespaces = append(createdNamespaces, ns.Name) } @@ -624,7 +453,7 @@ var _ = Describe("GetUserNamespaces", func() { Context("When querying for specific namespaces using In", func() { It("Should return only the specified namespaces", func() { for _, name := range []string{"in-test-1", "in-test-2", "not-in-test"} { - ns, err := createNamespace(k8sClient, name) + ns, err := utils.CreateNamespace(k8sClient, name) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error creating the namespace %s", name)) createdNamespaces = append(createdNamespaces, ns.Name) } @@ -654,7 +483,7 @@ var _ = Describe("GetUserNamespaces", func() { Context("When querying for namespaces using NotIn", func() { It("Should return namespaces not in the specified list", func() { for _, name := range []string{"ts-keep-1", "ts-keep-2", "ts-exclude-1", "ts-exclude-2"} { - ns, err := createNamespace(k8sClient, name) + ns, err := utils.CreateNamespace(k8sClient, name) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error creating the namespace %s", name)) createdNamespaces = append(createdNamespaces, ns.Name) } diff --git a/pkg/test/provision-test/provision_test.go b/pkg/test/provision-test/provision_test.go index ac09d5f..c60c3fb 100644 --- a/pkg/test/provision-test/provision_test.go +++ b/pkg/test/provision-test/provision_test.go @@ -3,8 +3,11 @@ package provision_test import ( "context" "fmt" + "io" + "log" "net/http" "os/exec" + "strings" "testing" . "github.com/onsi/ginkgo/v2" @@ -18,6 +21,16 @@ import ( "sigs.k8s.io/controller-runtime/pkg/envtest" ) +type HTTPheader struct { + name string + value string +} + +type HTTPResponse struct { + Body string + StatusCode int +} + func TestProvision(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Provision namespace Suite") @@ -35,6 +48,19 @@ var _ = BeforeSuite(func() { k8sClient = utils.StartTestEnv(schema, testEnv) serverProcess, serverCancelFunc = utils.CreateWorkspaceManagerServer("../../../cmd/main.go", nil, "") utils.WaitForWorkspaceManagerServerToServe() + + user1 := "user1@konflux.dev" + user2 := "user2@konflux.dev" + namespaceNames := []string{"test-tenant", "test-tenant-2", "test-tenant-3"} + for _, name := range namespaceNames { + _, err := utils.CreateNamespace(k8sClient, name) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error while creating the namespace %s: %v", name, err)) + } + utils.CreateRole(k8sClient, "test-tenant", "namespace-access", []string{"create", "list", "watch", "delete"}) + utils.CreateRole(k8sClient, "test-tenant-2", "namespace-access-2", []string{"create", "list", "watch", "delete"}) + utils.CreateRoleBinding(k8sClient, "namespace-access-user-binding", "test-tenant", user1, "namespace-access") + utils.CreateRoleBinding(k8sClient, "namespace-access-user-binding-2", "test-tenant", user2, "namespace-access") + utils.CreateRoleBinding(k8sClient, "namespace-access-user-binding-3", "test-tenant-2", user2, "namespace-access-2") }) var _ = AfterSuite(func() { @@ -66,3 +92,107 @@ var _ = Describe("simple test", func() { }) }) }) + +func performHTTPGetCall(url string, header HTTPheader) (*HTTPResponse, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Printf("Error creating request: %s", err) + return nil, err + } + if header.name != "" { + req.Header.Add(header.name, header.value) + } + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Printf("Error making request: %s", err) + return nil, err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Printf("Error reading response body: %s", err) + return nil, err + } + response := &HTTPResponse{ + Body: string(body), + StatusCode: resp.StatusCode, + } + return response, nil +} + +var _ = Describe("Signup endpoint", func() { + Context("Calling the signup endpoint with GET", func() { + It("responds with ready and signedup", func() { + url := "http://localhost:5000/api/v1/signup" + expectedCode := http.StatusOK + expectedBody := `{"status":{"ready":true,"reason":"SignedUp"}}` + + resp, err := performHTTPGetCall(url, HTTPheader{}) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Unexpected error testing the \"%s\" endpoint: %v", url, err)) + Expect(resp.StatusCode).To(Equal(expectedCode)) + Expect(strings.TrimSpace(expectedBody)).To(Equal(strings.TrimSpace(resp.Body))) + }) + }) +}) + +var _ = DescribeTable("Workspace endpoint", func(header HTTPheader, expectedCode int, expectedBody string) { + url := "http://localhost:5000/workspaces" + resp, err := performHTTPGetCall(url, header) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Unexpected error testing the \"%s\" endpoint: %v", url, err)) + Expect(resp.StatusCode).To(Equal(expectedCode)) + Expect(strings.TrimSpace(expectedBody)).To(Equal(strings.TrimSpace(resp.Body))) +}, + Entry( + "Calling the workspace endpoint for user1 responds only with the 'test-tenant' workspace info", + HTTPheader{"X-Email", "user1@konflux.dev"}, + http.StatusOK, + `{"kind":"WorkspaceList","apiVersion":"toolchain.dev.openshift.com/v1alpha1","metadata":{},`+ + `"items":[{"kind":"Workspace","apiVersion":"toolchain.dev.openshift.com/v1alpha1",`+ + `"metadata":{"name":"test-tenant","creationTimestamp":null},"status":`+ + `{"namespaces":[{"name":"test-tenant","type":"default"}]}}]}`), + Entry( + "Workspace endpoint for user2 responds with 2 namespaces info", + HTTPheader{"X-Email", "user2@konflux.dev"}, + http.StatusOK, + `{"kind":"WorkspaceList","apiVersion":"toolchain.dev.openshift.com/v1alpha1","metadata":{},`+ + `"items":[{"kind":"Workspace","apiVersion":"toolchain.dev.openshift.com/v1alpha1",`+ + `"metadata":{"name":"test-tenant","creationTimestamp":null},"status":{"namespaces":`+ + `[{"name":"test-tenant","type":"default"}]}},{"kind":"Workspace","apiVersion":`+ + `"toolchain.dev.openshift.com/v1alpha1","metadata":{"name":"test-tenant-2",`+ + `"creationTimestamp":null},"status":{"namespaces":[{"name":"test-tenant-2",`+ + `"type":"default"}]}}]}`), + Entry( + "Workspace endpoint for user3 responds with no namespaces", + HTTPheader{"X-Email", "user3@konflux.dev"}, + http.StatusOK, + `{"kind":"WorkspaceList","apiVersion":"toolchain.dev.openshift.com/v1alpha1","metadata":{},"items":null}`), + Entry( + "Workspace endpoint with no header", + HTTPheader{}, + 500, + `{"message":"Internal Server Error"}`), +) + +var _ = DescribeTable("Specific workspace endpoint", func(endpoint string, header HTTPheader, expectedCode int, expectedBody string) { + url := "http://localhost:5000/workspaces/" + endpoint + resp, err := performHTTPGetCall(url, header) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Unexpected error testing the \"%s\" endpoint: %v", url, err)) + Expect(resp.StatusCode).To(Equal(expectedCode)) + Expect(strings.TrimSpace(expectedBody)).To(Equal(strings.TrimSpace(resp.Body))) +}, + Entry( + "Calling the workspace endpoint for the test-tenant workspace for user2", + "test-tenant", + HTTPheader{"X-Email", "user2@konflux.dev"}, + http.StatusOK, + `{"kind":"Workspace","apiVersion":"toolchain.dev.openshift.com/v1alpha1","metadata":`+ + `{"name":"test-tenant","creationTimestamp":null},"status":{"namespaces":`+ + `[{"name":"test-tenant","type":"default"}]}}`), + Entry( + "Specific workspace endpoint for test-tenant-2 for user1 only", + "test-tenant-2", + HTTPheader{"X-Email", "user1@konflux.dev"}, + 404, + `{"message":"Not Found"}`), +) diff --git a/pkg/test/utils/utils.go b/pkg/test/utils/utils.go index 9027cb6..ecef9e2 100644 --- a/pkg/test/utils/utils.go +++ b/pkg/test/utils/utils.go @@ -9,6 +9,9 @@ import ( "path/filepath" . "github.com/onsi/gomega" + k8sapi "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -164,3 +167,60 @@ func StopWorkspaceManagerServer(cmd *exec.Cmd, serverCancelFunc context.CancelFu Expect(err).Should(BeAssignableToTypeOf(&exec.ExitError{})) } } + +func CreateRole(k8sClient client.Client, nsName string, roleName string, verbs []string) { + role := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleName, + Namespace: nsName, + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"appstudio.redhat.com"}, + Resources: []string{"applications", "components"}, + Verbs: verbs, + }, + }, + } + err := k8sClient.Create(context.Background(), role) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error creating 'Role' resource: %v", err)) +} + +func CreateRoleBinding(k8sClient client.Client, bindingName string, nsName string, userName string, roleName string) { + roleBinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: bindingName, + Namespace: nsName, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "User", + Name: userName, + APIGroup: "rbac.authorization.k8s.io", + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "Role", + Name: roleName, + APIGroup: "rbac.authorization.k8s.io", + }, + } + err := k8sClient.Create(context.Background(), roleBinding) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error creating 'roleBinding' resource: %v", err)) +} + +func CreateNamespace(k8sClient client.Client, name string) (k8sapi.Namespace, error) { + namespaced := &k8sapi.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{ + "konflux.ci/type": "user", + "kubernetes.io/metadata.name": name, + }, + }, + } + if err := k8sClient.Create(context.Background(), namespaced); err != nil { + return k8sapi.Namespace{}, fmt.Errorf("Error creating 'Namespace' resource: %v", err) + } + return *namespaced, nil +}