Skip to content

Commit

Permalink
Allow proxy to forward requests to namespaces outside of workspace (#…
Browse files Browse the repository at this point in the history
…1053)

* Allow proxy to forward requests to namespaces outside of workspace

* another test

* reorgonize
  • Loading branch information
alexeykazakov authored Oct 29, 2024
1 parent 571666c commit 7c1b209
Showing 1 changed file with 83 additions and 21 deletions.
104 changes: 83 additions & 21 deletions test/e2e/parallel/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"io"
rbacv1 "k8s.io/api/rbac/v1"
"net/http"
"net/url"
"os"
Expand Down Expand Up @@ -317,25 +318,84 @@ func TestProxyFlow(t *testing.T) {
})
})

t.Run("try to create a resource in an unauthorized namespace", func(t *testing.T) {
t.Run("get resource from namespace outside of user's workspace", func(t *testing.T) {
// given
appName := fmt.Sprintf("%s-proxy-test-app", user.username)
expectedApp := &appstudiov1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: appName,
Namespace: hostAwait.Namespace, // user should not be allowed to create a resource in the host operator namespace
},
Spec: appstudiov1.ApplicationSpec{
DisplayName: "Should be forbidden",
},
}

// when
proxyCl = user.createProxyClient(t, hostAwait)
// create a namespace which does not belong to the user's workspace
anotherNamespace := fmt.Sprintf("outside-%s", user.compliantUsername)
user.expectedMemberCluster.CreateNamespace(t, anotherNamespace)

// then
err := proxyCl.Create(context.TODO(), expectedApp)
require.EqualError(t, err, fmt.Sprintf(`invalid workspace request: access to namespace '%s' in workspace '%s' is forbidden (post applications.appstudio.redhat.com)`, hostAwait.Namespace, user.compliantUsername))
proxyWorkspaceURL := hostAwait.ProxyURLWithWorkspaceContext(user.compliantUsername)
explicitWorkspaceCtxClient, err := hostAwait.CreateAPIProxyClient(t, user.token, proxyWorkspaceURL) // a client with a workspace context
require.NoError(t, err)
withoutWorkspaceCtxClient := user.createProxyClient(t, hostAwait) // a client with no workspace context

t.Run("fail when accessing unshared namespace", func(t *testing.T) {
cm := corev1.ConfigMap{}
t.Run("using explicit workspace context", func(t *testing.T) {
// when
// every namespace has a CM "kube-root-ca.crt" but we can't get it because the user doesn't have access to the namespace
err = explicitWorkspaceCtxClient.Get(context.TODO(), types.NamespacedName{Namespace: anotherNamespace, Name: "kube-root-ca.crt"}, &cm)
// then
require.EqualError(t, err, fmt.Sprintf(`configmaps "kube-root-ca.crt" is forbidden: User "%s" cannot get resource "configmaps" in API group "" in the namespace "%s"`, user.compliantUsername, anotherNamespace))
})
t.Run("not using any workspace context", func(t *testing.T) {
// when
// every namespace has a CM "kube-root-ca.crt" but we can't get it because the user doesn't have access to the namespace
err := withoutWorkspaceCtxClient.Get(context.TODO(), types.NamespacedName{Namespace: anotherNamespace, Name: "kube-root-ca.crt"}, &cm)
// then
require.EqualError(t, err, fmt.Sprintf(`configmaps "kube-root-ca.crt" is forbidden: User "%s" cannot get resource "configmaps" in API group "" in the namespace "%s"`, user.compliantUsername, anotherNamespace))
})
})

t.Run("success when accessing shared namespace", func(t *testing.T) {
// when
// grand all authenticated users view access to the namespace
rb := rbacv1.RoleBinding{
TypeMeta: metav1.TypeMeta{
Kind: "RoleBinding",
},
ObjectMeta: metav1.ObjectMeta{
Name: "authenticated-view",
Namespace: anotherNamespace,
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: "view",
},
Subjects: []rbacv1.Subject{
{
Kind: "Group",
APIGroup: "rbac.authorization.k8s.io",
Name: "system:authenticated",
},
},
}
err := user.expectedMemberCluster.Create(t, &rb)
require.NoError(t, err)
_, err = user.expectedMemberCluster.WaitForRoleBinding(t, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: rb.Namespace}}, rb.Name)
require.NoError(t, err)

t.Run("using explicit workspace context", func(t *testing.T) {
// when
cm := corev1.ConfigMap{}
// every namespace has a CM "kube-root-ca.crt", let's get it and check that it's actually from the shared namespace
err = explicitWorkspaceCtxClient.Get(context.TODO(), types.NamespacedName{Namespace: anotherNamespace, Name: "kube-root-ca.crt"}, &cm)
// then
require.NoError(t, err)
assert.Equal(t, anotherNamespace, cm.Namespace)
})
t.Run("not using any workspace context", func(t *testing.T) {
// when
cm := corev1.ConfigMap{}
// every namespace has a CM "kube-root-ca.crt", let's get it and check that it's actually from the shared namespace
err := withoutWorkspaceCtxClient.Get(context.TODO(), types.NamespacedName{Namespace: anotherNamespace, Name: "kube-root-ca.crt"}, &cm)
// then
require.NoError(t, err)
assert.Equal(t, anotherNamespace, cm.Namespace)
})
})
})

t.Run("unable to create a resource in the other users namespace because the workspace is not shared", func(t *testing.T) {
Expand Down Expand Up @@ -366,7 +426,7 @@ func TestProxyFlow(t *testing.T) {
err = proxyCl.Create(context.TODO(), appToCreate)

// then
// note: the actual error message is "invalid workspace request: access to workspace '%s' is forbidden" but clients make api discovery calls
// note: the actual error message is "applications.appstudio.redhat.com is forbidden: User %s cannot create resource "applications" in the namespace %s" but clients make api discovery calls
// that fail because in this case the api discovery calls go through the proxyWorkspaceURL which is invalid. If using oc or kubectl and you
// enable verbose logging you would see Response Body: invalid workspace request: access to workspace 'proxymember2' is forbidden
require.EqualError(t, err, `no matches for kind "Application" in version "appstudio.redhat.com/v1alpha1"`)
Expand Down Expand Up @@ -561,9 +621,10 @@ func TestProxyFlow(t *testing.T) {
require.NotEmpty(t, createdApp)
assert.Equal(t, expectedApp.Spec.DisplayName, createdApp.Spec.DisplayName)

t.Run("request for namespace that doesn't belong to workspace context should fail", func(t *testing.T) {
// In this test the guest user has access to the primary user's namespace since the primary user's workspace has been shared, but if they specify the wrong
// workspace context (guest user's workspace context) the request should fail. In order for proxy requests to succeed the namespace must belong to the workspace.
t.Run("request for shared namespace that doesn't belong to workspace context should succeed", func(t *testing.T) {
// In this test the guest user has access to the primary user's namespace since the primary user's workspace has been shared,
// and proxy should forward the request even if the namespace does not belong to the workspace.
// It's up to the API server to check user's permissions. Not the proxy.

// given
workspaceName := guestUser.compliantUsername // guestUser's workspace
Expand All @@ -576,7 +637,8 @@ func TestProxyFlow(t *testing.T) {
err = guestUserGuestWsCl.Get(context.TODO(), types.NamespacedName{Name: applicationName, Namespace: primaryUserNamespace}, actualApp) // primaryUser's namespace

// then
require.EqualError(t, err, fmt.Sprintf(`invalid workspace request: access to namespace '%s' in workspace '%s' is forbidden (get applications.appstudio.redhat.com %s)`, primaryUserNamespace, workspaceName, applicationName))
require.NoError(t, err)
assert.Equal(t, primaryUserNamespace, actualApp.Namespace)
})
})

Expand Down

0 comments on commit 7c1b209

Please sign in to comment.