Skip to content

Commit be71072

Browse files
committed
add e2e test to ensure cluster context is set
On-behalf-of: @SAP [email protected]
1 parent c767a4d commit be71072

File tree

3 files changed

+166
-1
lines changed

3 files changed

+166
-1
lines changed

cmd/sharded-test-server/frontproxy.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ func startFrontProxy(
7676
ProxyClientCert: filepath.Join(workDirPath, ".kcp-front-proxy", "requestheader.crt"),
7777
ProxyClientKey: filepath.Join(workDirPath, ".kcp-front-proxy", "requestheader.key"),
7878
},
79+
{
80+
Path: "/e2e/clusters/{cluster}/",
81+
Backend: "https://localhost:2443",
82+
BackendServerCA: filepath.Join(workDirPath, ".kcp", "serving-ca.crt"),
83+
// in the existing testcases, these two do not matter, but have to be non-empty
84+
ProxyClientCert: filepath.Join(workDirPath, ".kcp-front-proxy", "requestheader.crt"),
85+
ProxyClientKey: filepath.Join(workDirPath, ".kcp-front-proxy", "requestheader.key"),
86+
},
7987
{
8088
Path: "/clusters/",
8189
// this path is not actually used, since shard URLs are determined based on the Shard

test/e2e/authentication/workspace_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func TestWorkspaceOIDC(t *testing.T) {
5858
kcpClusterClient, err := kcpclientset.NewForConfig(kcpConfig)
5959
require.NoError(t, err)
6060

61-
// start a two mock OIDC servers that will listen on random ports
61+
// start two mock OIDC servers that will listen on random ports
6262
// (only for discovery and keyset handling, no actual login workflows)
6363
mockA, ca := authfixtures.StartMockOIDC(t, server)
6464
mockB, _ := authfixtures.StartMockOIDC(t, server)

test/e2e/proxy/proxy_test.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
Copyright 2025 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package proxy
18+
19+
import (
20+
"context"
21+
"net"
22+
"net/http"
23+
"path/filepath"
24+
"testing"
25+
"time"
26+
27+
"github.com/stretchr/testify/require"
28+
29+
rbacv1 "k8s.io/api/rbac/v1"
30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
"k8s.io/apimachinery/pkg/util/wait"
32+
33+
kcpkubernetesclientset "github.com/kcp-dev/client-go/kubernetes"
34+
"github.com/kcp-dev/logicalcluster/v3"
35+
36+
tenancyv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/tenancy/v1alpha1"
37+
kcpclientset "github.com/kcp-dev/kcp/sdk/client/clientset/versioned/cluster"
38+
kcptesting "github.com/kcp-dev/kcp/sdk/testing"
39+
"github.com/kcp-dev/kcp/test/e2e/fixtures/authfixtures"
40+
"github.com/kcp-dev/kcp/test/e2e/framework"
41+
)
42+
43+
func TestMappingWithClusterContext(t *testing.T) {
44+
framework.Suite(t, "control-plane")
45+
46+
ctx := context.Background()
47+
48+
// start kcp and setup clients;
49+
// note that the sharded-test-server will configure a special
50+
// `/e2e/clusters/{cluster}` route, which we are testing here
51+
server := kcptesting.SharedKcpServer(t)
52+
53+
if len(server.ShardNames()) < 2 {
54+
t.Skip("This test requires a multi-shard setup with front-proxy.")
55+
}
56+
57+
// The goal is to prove that having a {cluster} placeholder in the URL
58+
// correctly provides a cluster context to the underlying handler. This
59+
// handler is normally a custom virtual workspace, since kcp itself can
60+
// only handle health endpoints and /clusters/ URLs.
61+
// Instead of wiring up a custom virtual workspace, we just start a minimal
62+
// echo server to see if the correct headers are received.
63+
var lastHeaders http.Header
64+
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
65+
lastHeaders = r.Header.Clone()
66+
})
67+
68+
srv := &http.Server{
69+
Handler: handler,
70+
BaseContext: func(l net.Listener) context.Context {
71+
return t.Context()
72+
},
73+
Addr: "localhost:2443", // as per front-proxy mapping
74+
}
75+
76+
dirPath := filepath.Dir(server.KubeconfigPath())
77+
go func() {
78+
if err := srv.ListenAndServeTLS(
79+
filepath.Join(dirPath, "apiserver.crt"),
80+
filepath.Join(dirPath, "apiserver.key"),
81+
); err != nil {
82+
t.Logf("Error: %v", err)
83+
}
84+
}()
85+
86+
baseWsPath, _ := kcptesting.NewWorkspaceFixture(t, server, logicalcluster.NewPath("root"), kcptesting.WithNamePrefix("oidc"))
87+
88+
kcpConfig := server.BaseConfig(t)
89+
kubeClusterClient, err := kcpkubernetesclientset.NewForConfig(kcpConfig)
90+
require.NoError(t, err)
91+
kcpClusterClient, err := kcpclientset.NewForConfig(kcpConfig)
92+
require.NoError(t, err)
93+
94+
// start a mock OIDC servers that will listen on a random port
95+
// (only for discovery and keyset handling, no actual login workflows)
96+
mock, ca := authfixtures.StartMockOIDC(t, server)
97+
98+
// setup a new workspace auth config that uses mockoidc's server
99+
authConfig := authfixtures.CreateWorkspaceOIDCAuthentication(t, ctx, kcpClusterClient, baseWsPath, mock, ca, nil)
100+
101+
// use these configs in new WorkspaceTypes
102+
wsType := authfixtures.CreateWorkspaceType(t, ctx, kcpClusterClient, baseWsPath, "with-oidc", authConfig)
103+
104+
// create a new workspace with our new type
105+
t.Log("Creating Workspace...")
106+
teamPath, teamWs := kcptesting.NewWorkspaceFixture(t, server, baseWsPath, kcptesting.WithName("team-a"), kcptesting.WithType(baseWsPath, tenancyv1alpha1.WorkspaceTypeName(wsType)))
107+
teamCluster := teamWs.Spec.Cluster
108+
109+
// grant permissions to the user
110+
authfixtures.GrantWorkspaceAccess(t, ctx, kubeClusterClient, teamPath, "grant-oidc-user", "cluster-admin", []rbacv1.Subject{{
111+
Kind: "User",
112+
Name: "oidc:[email protected]",
113+
}, {
114+
Kind: "Group",
115+
Name: "oidc:developers",
116+
}})
117+
118+
var (
119+
username = "billybob"
120+
121+
groups = []string{"developers"}
122+
123+
expectedScope = "cluster:" + teamCluster
124+
expectedClusterName = teamCluster
125+
expectedUsername = "oidc:" + email
126+
expectedGroups = []string{"system:authenticated"}
127+
)
128+
129+
for _, group := range groups {
130+
expectedGroups = append(expectedGroups, "oidc:"+group)
131+
}
132+
133+
token := authfixtures.CreateOIDCToken(t, mock, username, email, groups)
134+
135+
cfg := framework.ConfigWithToken(token, kcpConfig)
136+
cfg.Host += "/e2e"
137+
138+
client, err := kcpkubernetesclientset.NewForConfig(cfg)
139+
require.NoError(t, err)
140+
141+
require.Eventually(t, func() bool {
142+
_, _ = client.Cluster(teamPath).CoreV1().ConfigMaps("default").List(ctx, metav1.ListOptions{})
143+
144+
if lastHeaders == nil {
145+
return false
146+
}
147+
148+
// if we find anything else in the auth infos, no need to try any further, neither the token
149+
// nor the OIDC mapping will change to produce other values
150+
require.Equal(t, expectedScope, lastHeaders.Get("X-Remote-Extra-Authentication.kcp.io%2fscopes"))
151+
require.Equal(t, expectedClusterName, lastHeaders.Get("X-Remote-Extra-Authentication.kcp.io%2fcluster-Name"))
152+
require.Equal(t, expectedUsername, lastHeaders.Get("X-Remote-User"))
153+
require.ElementsMatch(t, expectedGroups, lastHeaders.Values("X-Remote-Group"))
154+
155+
return true
156+
}, wait.ForeverTestTimeout, 500*time.Millisecond)
157+
}

0 commit comments

Comments
 (0)