Skip to content

Commit 66b6460

Browse files
committed
support apiserver url rewrite
Signed-off-by: huiwq1990 <[email protected]>
1 parent ac40b0f commit 66b6460

File tree

4 files changed

+254
-0
lines changed

4 files changed

+254
-0
lines changed

pkg/apiserver/apiserver.go

+7
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@ import (
1717
"k8s.io/client-go/discovery"
1818
clientrest "k8s.io/client-go/rest"
1919
"k8s.io/client-go/restmapper"
20+
"k8s.io/klog/v2"
2021

2122
internal "github.com/clusterpedia-io/api/clusterpedia"
2223
"github.com/clusterpedia-io/api/clusterpedia/install"
24+
"github.com/clusterpedia-io/clusterpedia/pkg/apiserver/features"
2325
"github.com/clusterpedia-io/clusterpedia/pkg/apiserver/registry/clusterpedia/collectionresources"
2426
"github.com/clusterpedia-io/clusterpedia/pkg/apiserver/registry/clusterpedia/resources"
2527
"github.com/clusterpedia-io/clusterpedia/pkg/generated/clientset/versioned"
2628
informers "github.com/clusterpedia-io/clusterpedia/pkg/generated/informers/externalversions"
2729
"github.com/clusterpedia-io/clusterpedia/pkg/kubeapiserver"
2830
"github.com/clusterpedia-io/clusterpedia/pkg/storage"
31+
clusterpediafeature "github.com/clusterpedia-io/clusterpedia/pkg/utils/feature"
2932
"github.com/clusterpedia-io/clusterpedia/pkg/utils/filters"
3033
)
3134

@@ -139,6 +142,10 @@ func (config completedConfig) New() (*ClusterPediaServer, error) {
139142
handler := handlerChainFunc(apiHandler, c)
140143
handler = filters.WithRequestQuery(handler)
141144
handler = filters.WithAcceptHeader(handler)
145+
if clusterpediafeature.FeatureGate.Enabled(features.ApiServerURLRewrite) {
146+
klog.InfoS("Enable rewrite apiserver url")
147+
handler = filters.WithRewriteFilter(handler)
148+
}
142149
return handler
143150
}
144151

pkg/apiserver/features/features.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package features
2+
3+
import (
4+
"k8s.io/apimachinery/pkg/util/runtime"
5+
"k8s.io/component-base/featuregate"
6+
7+
clusterpediafeature "github.com/clusterpedia-io/clusterpedia/pkg/utils/feature"
8+
)
9+
10+
const (
11+
12+
// ApiServerURLRewrite is a feature gate for rewrite apiserver request's URL
13+
// owner: @huiwq1990
14+
// alpha: v0.7.0
15+
ApiServerURLRewrite featuregate.Feature = "ApiServerURLRewrite"
16+
)
17+
18+
func init() {
19+
runtime.Must(clusterpediafeature.MutableFeatureGate.Add(defaultApiServerFeatureGates))
20+
}
21+
22+
// defaultApiServerFeatureGates consists of all known apiserver feature keys.
23+
// To add a new feature, define a key for it above and add it here.
24+
var defaultApiServerFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
25+
ApiServerURLRewrite: {Default: false, PreRelease: featuregate.Alpha},
26+
}

pkg/utils/filters/rewrite.go

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package filters
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/url"
7+
"path"
8+
"regexp"
9+
"strings"
10+
11+
"k8s.io/klog/v2"
12+
13+
internal "github.com/clusterpedia-io/api/clusterpedia"
14+
)
15+
16+
const OriginURIHeaderKey = "X-Rewrite-Original-URI"
17+
18+
type Rule struct {
19+
Pattern string
20+
To string
21+
*regexp.Regexp
22+
}
23+
24+
var regfmt = regexp.MustCompile(`:[^/#?()\.\\]+`)
25+
26+
func NewRule(pattern, to string) (*Rule, error) {
27+
pattern = regfmt.ReplaceAllStringFunc(pattern, func(m string) string {
28+
return fmt.Sprintf(`(?P<%s>[^/#?]+)`, m[1:])
29+
})
30+
31+
reg, err := regexp.Compile(pattern)
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
return &Rule{
37+
pattern,
38+
to,
39+
reg,
40+
}, nil
41+
}
42+
43+
func (r *Rule) Rewrite(req *http.Request) bool {
44+
oriPath := req.URL.Path
45+
46+
if !r.MatchString(oriPath) {
47+
return false
48+
}
49+
50+
klog.V(4).InfoS("Rewrite url from", "URL", req.URL.EscapedPath())
51+
52+
to := path.Clean(r.Replace(req.URL))
53+
54+
u, e := url.Parse(to)
55+
if e != nil {
56+
return false
57+
}
58+
59+
req.Header.Set(OriginURIHeaderKey, req.URL.RequestURI())
60+
61+
req.URL.Path = u.Path
62+
req.URL.RawPath = u.RawPath
63+
if u.RawQuery != "" {
64+
req.URL.RawQuery = u.RawQuery
65+
}
66+
67+
klog.V(4).InfoS("Rewrite url to", "URL", req.URL.EscapedPath())
68+
69+
return true
70+
}
71+
72+
func (r *Rule) Replace(u *url.URL) string {
73+
if !hit("\\$|\\:", r.To) {
74+
return r.To
75+
}
76+
77+
uri := u.RequestURI()
78+
79+
regFrom := regexp.MustCompile(r.Pattern)
80+
match := regFrom.FindStringSubmatchIndex(uri)
81+
82+
result := regFrom.ExpandString([]byte(""), r.To, uri, match)
83+
84+
str := string(result[:])
85+
86+
if hit("\\:", str) {
87+
return r.replaceNamedParams(uri, str)
88+
}
89+
90+
return str
91+
}
92+
93+
func (r *Rule) replaceNamedParams(from, to string) string {
94+
fromMatches := r.FindStringSubmatch(from)
95+
96+
if len(fromMatches) > 0 {
97+
for i, name := range r.SubexpNames() {
98+
if len(name) > 0 {
99+
to = strings.Replace(to, ":"+name, fromMatches[i], -1)
100+
}
101+
}
102+
}
103+
104+
return to
105+
}
106+
107+
func NewHandler(rules map[string]string) RewriteHandler {
108+
var h RewriteHandler
109+
110+
for key, val := range rules {
111+
r, e := NewRule(key, val)
112+
if e != nil {
113+
panic(e)
114+
}
115+
116+
h.rules = append(h.rules, r)
117+
}
118+
119+
return h
120+
}
121+
122+
func WithRewriteFilter(handler http.Handler) http.Handler {
123+
rh := NewHandler(map[string]string{
124+
"/resources/(.*)": fmt.Sprintf("/apis/%s/v1beta1/resources/$1", internal.GroupName),
125+
})
126+
127+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
128+
rh.ServeHTTP(w, req)
129+
handler.ServeHTTP(w, req)
130+
})
131+
}
132+
133+
type RewriteHandler struct {
134+
rules []*Rule
135+
}
136+
137+
func (h *RewriteHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
138+
for _, r := range h.rules {
139+
ok := r.Rewrite(req)
140+
if ok {
141+
break
142+
}
143+
}
144+
}
145+
146+
func hit(pattern, str string) bool {
147+
r, e := regexp.MatchString(pattern, str)
148+
if e != nil {
149+
return false
150+
}
151+
152+
return r
153+
}

pkg/utils/filters/rewrite_test.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package filters
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
)
8+
9+
type testFixture struct {
10+
from string
11+
to string
12+
}
13+
14+
type testCase struct {
15+
pattern string
16+
to string
17+
fixtures []testFixture
18+
}
19+
20+
func TestRewrite(t *testing.T) {
21+
tests := []testCase{
22+
{
23+
pattern: "/resources/(.*)",
24+
to: "/apis/clusterpedia.io/v1beta1/resources/$1",
25+
fixtures: []testFixture{
26+
{from: "/resources/api/v1/namespaces/default/pods", to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods"},
27+
{from: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods", to: "/apis/clusterpedia.io/v1beta1/resources/api/v1/namespaces/default/pods"},
28+
{from: "/api/v1/namespaces/default/pods", to: "/api/v1/namespaces/default/pods"},
29+
},
30+
},
31+
}
32+
33+
for _, test := range tests {
34+
t.Logf("Test - pattern: %s, to: %s", test.pattern, test.to)
35+
36+
for _, fixture := range test.fixtures {
37+
req, err := http.NewRequest("GET", fixture.from, nil)
38+
if err != nil {
39+
t.Fatalf("Fixture %v - create HTTP request error: %v", fixture, err)
40+
}
41+
42+
h := NewHandler(map[string]string{
43+
test.pattern: test.to,
44+
})
45+
46+
t.Logf("From: %s", req.URL.EscapedPath())
47+
if req.URL.EscapedPath() != fixture.from {
48+
t.Errorf("Invalid test fixture: %s", fixture.from)
49+
}
50+
51+
res := httptest.NewRecorder()
52+
h.ServeHTTP(res, req)
53+
54+
t.Logf("Rewrited: %s", req.URL.EscapedPath())
55+
if req.URL.EscapedPath() != fixture.to {
56+
t.Errorf("Test failed \n pattern: %s, to: %s, \n fixture: %s to %s, \n result: %s",
57+
test.pattern, test.to, fixture.from, fixture.to, req.URL.EscapedPath())
58+
}
59+
60+
if req.Header.Get(OriginURIHeaderKey) != "" {
61+
// matched
62+
if req.Header.Get(OriginURIHeaderKey) != fixture.from {
63+
t.Error("incorrect flag")
64+
}
65+
}
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)