@@ -30,63 +30,138 @@ import (
3030 "github.com/kcp-dev/logicalcluster/v3"
3131
3232 kcpauthorization "github.com/kcp-dev/kcp/pkg/authorization"
33+ "github.com/kcp-dev/kcp/pkg/index"
3334 proxyindex "github.com/kcp-dev/kcp/pkg/proxy/index"
35+ "github.com/kcp-dev/kcp/pkg/server/proxy/types"
3436)
3537
36- func WithClusterResolver (delegate http.Handler , index proxyindex.Index ) http.Handler {
37- return http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
38- var cs = strings .SplitN (strings .TrimLeft (req .URL .Path , "/" ), "/" , 3 )
39- if len (cs ) < 2 || cs [0 ] != "clusters" {
40- delegate .ServeHTTP (w , req )
41- return
38+ func WithClusterResolver (delegate http.Handler , mappings []types.PathMapping , index proxyindex.Index ) http.Handler {
39+ mux := http .NewServeMux ()
40+
41+ // fallback for all unrecognized URLs
42+ mux .Handle ("/" , delegate )
43+
44+ // Use the extra path mappings as an additional source of cluster names in URLs;
45+ // it's okay for a virtual workspace URL to not match here or to not have a
46+ // cluster placeholder in its URL pattern, since the default handler will simply
47+ // forward the request unchanged (and most likely, unauthenticated).
48+
49+ // We can use the same handler for all mappings, since the actual muxing to
50+ // the destinations happens later in proxy.HttpHandler; here we only care about
51+ // detecting the cluster name.
52+ mappingHandler := newMappingHandler (delegate , index )
53+
54+ for _ , mapping := range mappings {
55+ p := strings .TrimRight (mapping .Path , "/" )
56+
57+ // Even though we know how to handle the "special" core clusters path,
58+ // the mapping provides additional PKI configuration that is not available
59+ // by just looking up the cluster in the index and figuring out the
60+ // target shard. That's why it's required to configure /clusters/ in the
61+ // front-proxy mappings and since admins could choose not to include it,
62+ // we only enable the built-in clusterResolveHandler if we actually find
63+ // an appropriate mapping.
64+ if p == "/clusters" {
65+ // we know how to parse cluster URLs
66+ resolveHandler := newClusterResolveHandler (delegate , index )
67+ mux .HandleFunc ("/clusters/{cluster}" , resolveHandler )
68+ mux .HandleFunc ("/clusters/{cluster}/{trail...}" , resolveHandler )
69+ } else {
70+ // mappings are configured with *prefixes*; in order to match both exact matches
71+ // and prefix matches (i.e. if "/foo" is configured, both "/foo" and "/foo/bar"
72+ // must match), each mapping is added twice to the mux.
73+ mux .HandleFunc (p , mappingHandler )
74+ mux .HandleFunc (p + "/{trail...}" , mappingHandler )
4275 }
76+ }
4377
44- ctx := req .Context ()
45- logger := klog .FromContext (ctx )
46- attributes , err := filters .GetAuthorizerAttributes (ctx )
47- if err != nil {
48- responsewriters .InternalError (w , req , err )
49- return
50- }
78+ return mux
79+ }
5180
52- clusterPath := logicalcluster .NewPath (cs [1 ])
53- if ! clusterPath .IsValid () {
54- // this includes wildcards
55- logger .WithValues ("requestPath" , req .URL .Path ).V (4 ).Info ("Invalid cluster path" )
56- responsewriters .Forbidden (req .Context (), attributes , w , req , kcpauthorization .WorkspaceAccessNotPermittedReason , kubernetesscheme .Codecs )
57- return
58- }
81+ func newClusterResolveHandler (delegate http.Handler , index proxyindex.Index ) http.HandlerFunc {
82+ return func (w http.ResponseWriter , req * http.Request ) {
83+ clusterName := req .PathValue ("cluster" )
5984
60- result , found := index .LookupURL (clusterPath )
61- if result .ErrorCode != 0 {
62- http .Error (w , "Not available." , result .ErrorCode )
63- return
64- }
65- if ! found {
66- logger .WithValues ("clusterPath" , clusterPath ).V (4 ).Info ("Unknown cluster path" )
67- responsewriters .Forbidden (req .Context (), attributes , w , req , kcpauthorization .WorkspaceAccessNotPermittedReason , kubernetesscheme .Codecs )
85+ req , result := resolveClusterName (w , req , index , clusterName )
86+ if req == nil {
6887 return
6988 }
89+
7090 shardURL , err := url .Parse (result .URL )
7191 if err != nil {
7292 responsewriters .InternalError (w , req , err )
7393 return
7494 }
7595
76- logger .WithValues ("from" , "/clusters/" + cs [1 ], "to" , shardURL ).V (4 ).Info ("Redirecting" )
96+ ctx := req .Context ()
97+
98+ logger := klog .FromContext (ctx )
99+ logger .WithValues ("from" , "/clusters/" + clusterName , "to" , shardURL ).V (4 ).Info ("Redirecting" )
77100
101+ shardURL .RawQuery = req .URL .RawQuery
78102 shardURL .Path = strings .TrimSuffix (shardURL .Path , "/" )
79- if len (cs ) == 3 {
80- shardURL .Path += "/" + cs [ 2 ]
103+ if trail := req . PathValue ( "trail" ); len (trail ) != 0 {
104+ shardURL .Path += "/" + trail
81105 }
82106
83107 ctx = WithShardURL (ctx , shardURL )
84- ctx = WithClusterName (ctx , result .Cluster )
85- ctx = WithWorkspaceType (ctx , result .Type )
86108 req = req .WithContext (ctx )
87109
88110 delegate .ServeHTTP (w , req )
89- })
111+ }
112+ }
113+
114+ func newMappingHandler (delegate http.Handler , index proxyindex.Index ) http.HandlerFunc {
115+ return func (w http.ResponseWriter , req * http.Request ) {
116+ // not every virtual workspace and/or every mapping has a {cluster} in its URL;
117+ // also wildcard requests have to be passed without lookup
118+ clusterName := req .PathValue ("cluster" )
119+ if clusterName == "" || clusterName == "*" {
120+ delegate .ServeHTTP (w , req )
121+ return
122+ }
123+
124+ req , _ = resolveClusterName (w , req , index , clusterName )
125+ if req == nil {
126+ return
127+ }
128+
129+ delegate .ServeHTTP (w , req )
130+ }
131+ }
132+
133+ func resolveClusterName (w http.ResponseWriter , req * http.Request , index proxyindex.Index , clusterName string ) (* http.Request , * index.Result ) {
134+ ctx := req .Context ()
135+ logger := klog .FromContext (ctx )
136+ attributes , err := filters .GetAuthorizerAttributes (ctx )
137+ if err != nil {
138+ responsewriters .InternalError (w , req , err )
139+ return nil , nil
140+ }
141+
142+ clusterPath := logicalcluster .NewPath (clusterName )
143+ if ! clusterPath .IsValid () {
144+ // this includes wildcards
145+ logger .WithValues ("requestPath" , req .URL .Path ).V (4 ).Info ("Invalid cluster path" )
146+ responsewriters .Forbidden (req .Context (), attributes , w , req , kcpauthorization .WorkspaceAccessNotPermittedReason , kubernetesscheme .Codecs )
147+ return nil , nil
148+ }
149+
150+ result , found := index .LookupURL (clusterPath )
151+ if result .ErrorCode != 0 {
152+ http .Error (w , "Not available." , result .ErrorCode )
153+ return nil , nil
154+ }
155+ if ! found {
156+ logger .WithValues ("clusterPath" , clusterPath ).V (4 ).Info ("Unknown cluster path" )
157+ responsewriters .Forbidden (req .Context (), attributes , w , req , kcpauthorization .WorkspaceAccessNotPermittedReason , kubernetesscheme .Codecs )
158+ return nil , nil
159+ }
160+
161+ ctx = WithClusterName (ctx , result .Cluster )
162+ ctx = WithWorkspaceType (ctx , result .Type )
163+
164+ return req .WithContext (ctx ), & result
90165}
91166
92167type lookupKey int
0 commit comments