Skip to content

Commit 5222d9c

Browse files
authored
feat(ui): flag to enable service proxy, filter proxy routes (#5800)
1 parent c72c813 commit 5222d9c

File tree

7 files changed

+105
-40
lines changed

7 files changed

+105
-40
lines changed

docker-compose.yml

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ services:
105105
elasticsearch.elasticsearch.indexMetrics=cds-index-metrics \
106106
elasticsearch.elasticsearch.url=http://elasticsearch:9200 \
107107
ui.url=http://${HOSTNAME}:8080 \
108+
ui.enableServiceProxy=true \
108109
ui.api.http.url=http://cds-api:8081 \
109110
ui.hooksURL=http://cds-hooks:8083 \
110111
ui.cdnURL=http://cds-cdn:8089;

engine/api/router.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,11 @@ func (r *Router) handle(uri string, scope HandlerScope, handlers ...*service.Han
340340
telemetry.Path, req.URL.Path,
341341
telemetry.Method, req.Method)
342342

343-
// Retrieve the client ip address from the header (X-Forwarded-For by default)
344-
clientIP := req.Header.Get(r.Config.HeaderXForwardedFor)
343+
var clientIP string
344+
if r.Config.HeaderXForwardedFor != "" {
345+
// Retrieve the client ip address from the header (X-Forwarded-For by default)
346+
clientIP = req.Header.Get(r.Config.HeaderXForwardedFor)
347+
}
345348
if clientIP == "" {
346349
// If the header has not been found, fallback on the remote adress from the http request
347350
clientIP = req.RemoteAddr
@@ -545,8 +548,11 @@ func MaintenanceAware() service.HandlerConfigParam {
545548
func (r *Router) NotFoundHandler(w http.ResponseWriter, req *http.Request) {
546549
ctx := req.Context()
547550

548-
// Retrieve the client ip address from the header (X-Forwarded-For by default)
549-
clientIP := req.Header.Get(r.Config.HeaderXForwardedFor)
551+
var clientIP string
552+
if r.Config.HeaderXForwardedFor != "" {
553+
// Retrieve the client ip address from the header (X-Forwarded-For by default)
554+
clientIP = req.Header.Get(r.Config.HeaderXForwardedFor)
555+
}
550556
if clientIP == "" {
551557
// If the header has not been found, fallback on the remote adress from the http request
552558
clientIP = req.RemoteAddr

engine/service/types.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type APIServiceConfiguration struct {
2727
type HTTPRouterConfiguration struct {
2828
Addr string `toml:"addr" default:"" commented:"true" comment:"Listen HTTP address without port, example: 127.0.0.1" json:"addr"`
2929
Port int `toml:"port" default:"8081" json:"port"`
30-
HeaderXForwardedFor string `toml:"headerXForwardedFor" default:"X-Forwarded-For" json:"header_w_forwarded_for"`
30+
HeaderXForwardedFor string `toml:"headerXForwardedFor" commented:"true" comment:"Forward source addr from given header, let empty to use request addr." default:"X-Forwarded-For" json:"header_w_forwarded_for"`
3131
}
3232

3333
// HatcheryCommonConfiguration is the base configuration for all hatcheries

engine/ui/types.go

+11-10
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ type Service struct {
1919

2020
// Configuration is the ui configuration structure
2121
type Configuration struct {
22-
Name string `toml:"name" comment:"Name of this CDS UI Service\n Enter a name to enable this service" json:"name"`
23-
Staticdir string `toml:"staticdir" default:"./ui_static_files" comment:"This directory must contain the dist directory." json:"staticdir"`
24-
BaseURL string `toml:"baseURL" commented:"true" comment:"If you expose CDS UI with https://your-domain.com/ui, enter the value '/ui/'. Optional" json:"baseURL"`
25-
DeployURL string `toml:"deployURL" commented:"true" comment:"You can start CDS UI proxy on a sub path like https://your-domain.com/ui with value '/ui' (the value should not be given when the sub path is added by a proxy in front of CDS). Optional" json:"deployURL"`
26-
SentryURL string `toml:"sentryURL" commented:"true" comment:"Sentry URL. Optional" json:"-"`
27-
HTTP service.HTTPRouterConfiguration `toml:"http" comment:"######################\n CDS UI HTTP Configuration \n######################" json:"http"`
28-
URL string `toml:"url" comment:"Public URL of this UI service." default:"http://localhost:8080" json:"url"`
29-
API service.APIServiceConfiguration `toml:"api" comment:"######################\n CDS API Settings \n######################" json:"api"`
30-
HooksURL string `toml:"hooksURL" comment:"Hooks µService URL" default:"http://localhost:8083" json:"hooksURL"`
31-
CDNURL string `toml:"cdnURL" comment:"CDN µService URL" default:"http://localhost:8089" json:"cdnURL"`
22+
Name string `toml:"name" comment:"Name of this CDS UI Service\n Enter a name to enable this service" json:"name"`
23+
Staticdir string `toml:"staticdir" default:"./ui_static_files" comment:"This directory must contain the dist directory." json:"staticdir"`
24+
BaseURL string `toml:"baseURL" commented:"true" comment:"If you expose CDS UI with https://your-domain.com/ui, enter the value '/ui/'. Optional" json:"baseURL"`
25+
DeployURL string `toml:"deployURL" commented:"true" comment:"You can start CDS UI proxy on a sub path like https://your-domain.com/ui with value '/ui' (the value should not be given when the sub path is added by a proxy in front of CDS). Optional" json:"deployURL"`
26+
SentryURL string `toml:"sentryURL" commented:"true" comment:"Sentry URL. Optional" json:"-"`
27+
HTTP service.HTTPRouterConfiguration `toml:"http" comment:"######################\n CDS UI HTTP Configuration \n######################" json:"http"`
28+
URL string `toml:"url" comment:"Public URL of this UI service." default:"http://localhost:8080" json:"url"`
29+
API service.APIServiceConfiguration `toml:"api" comment:"######################\n CDS API Settings \n######################" json:"api"`
30+
HooksURL string `toml:"hooksURL" comment:"Hooks µService URL" default:"http://localhost:8083" json:"hooksURL"`
31+
CDNURL string `toml:"cdnURL" comment:"CDN µService URL" default:"http://localhost:8089" json:"cdnURL"`
32+
EnableServiceProxy bool `toml:"enableServiceProxy" default:"false" commented:"true" comment:"Enable service proxy will allows CDS UI to handle request to API, Hooks and CDN services. Optional" json:"enableServiceProxy"`
3233
}

engine/ui/ui.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,10 @@ func (s *Service) indexHTMLReplaceVar() error {
240240
return sdk.WrapError(err, "cannot parse base href regex")
241241
}
242242
indexContent := regexBaseHref.ReplaceAllString(string(read), "<base href=\""+s.Cfg.BaseURL+"\">")
243-
indexContent = strings.Replace(indexContent, "window.cds_sentry_url = '';", "window.cds_sentry_url = '"+s.Cfg.SentryURL+"';", -1)
244243
indexContent = strings.Replace(indexContent, "window.cds_version = '';", "window.cds_version='"+sdk.VERSION+"';", -1)
244+
if s.Cfg.SentryURL != "" {
245+
indexContent = strings.Replace(indexContent, "window.cds_sentry_url = '';", "window.cds_sentry_url = '"+s.Cfg.SentryURL+"';", -1)
246+
}
245247
return ioutil.WriteFile(indexHTML, []byte(indexContent), 0)
246248
}
247249

engine/ui/ui_router.go

+79-23
Original file line numberDiff line numberDiff line change
@@ -29,41 +29,97 @@ func (s *Service) initRouter(ctx context.Context) {
2929
r.Handle(s.Cfg.DeployURL+"/mon/metrics", nil, r.GET(service.GetPrometheustMetricsHandler(s)))
3030
r.Handle(s.Cfg.DeployURL+"/mon/metrics/all", nil, r.GET(service.GetMetricsHandler))
3131

32-
// proxypass
33-
if s.Cfg.API.HTTP.URL != "" {
34-
r.Mux.PathPrefix(s.Cfg.DeployURL + "/cdsapi").Handler(s.getReverseProxy(s.Cfg.DeployURL+"/cdsapi", s.Cfg.API.HTTP.URL))
35-
}
36-
if s.Cfg.HooksURL != "" {
37-
r.Mux.PathPrefix(s.Cfg.DeployURL + "/cdshooks").Handler(s.getReverseProxy(s.Cfg.DeployURL+"/cdshooks", s.Cfg.HooksURL))
38-
}
39-
if s.Cfg.CDNURL != "" {
40-
r.Mux.PathPrefix(s.Cfg.DeployURL + "/cdscdn").Handler(s.getReverseProxy(s.Cfg.DeployURL+"/cdscdn", s.Cfg.CDNURL))
32+
// proxypass if enabled
33+
if s.Cfg.EnableServiceProxy {
34+
if s.Cfg.API.HTTP.URL != "" {
35+
apiPath := s.Cfg.DeployURL + "/cdsapi"
36+
r.Mux.PathPrefix(apiPath).Handler(s.getReverseProxy(ctx, apiPath, s.Cfg.API.HTTP.URL))
37+
}
38+
if s.Cfg.HooksURL != "" {
39+
hooksPath := s.Cfg.DeployURL + "/cdshooks"
40+
r.Mux.PathPrefix(hooksPath).Handler(s.getReverseProxy(ctx, hooksPath, s.Cfg.HooksURL))
41+
}
42+
if s.Cfg.CDNURL != "" {
43+
cdnPath := s.Cfg.DeployURL + "/cdscdn"
44+
r.Mux.PathPrefix(cdnPath).Handler(s.getReverseProxy(ctx, cdnPath, s.Cfg.CDNURL))
45+
}
4146
}
4247

4348
// serve static UI files
4449
r.Mux.PathPrefix("/docs").Handler(s.uiServe(http.Dir(s.DocsDir), s.DocsDir))
4550
r.Mux.PathPrefix("/").Handler(s.uiServe(http.Dir(s.HTMLDir), s.HTMLDir))
4651
}
4752

48-
func (s *Service) getReverseProxy(path, urlRemote string) *httputil.ReverseProxy {
49-
origin, _ := url.Parse(urlRemote)
53+
func (s *Service) getReverseProxy(ctx context.Context, path, urlRemote string) http.Handler {
54+
filter := func(req *http.Request) bool {
55+
reqPath := strings.TrimPrefix(req.URL.Path, path)
56+
57+
// on api proxypass, deny request on /mon/metrics
58+
if strings.HasSuffix(path, "api") && strings.HasPrefix(reqPath, "/mon/metrics") {
59+
return false
60+
}
61+
62+
// on hooks proxypass, allow only request on /webhook/ path
63+
if strings.HasSuffix(path, "hooks") && !strings.HasPrefix(reqPath, "/webhook/") {
64+
return false
65+
}
66+
67+
// on cdn proxypass, allow only request on /item
68+
if strings.HasSuffix(path, "cdn") && !strings.HasPrefix(reqPath, "/item") {
69+
return false
70+
}
71+
72+
return true
73+
}
5074

75+
origin, _ := url.Parse(urlRemote)
5176
director := func(req *http.Request) {
5277
reqPath := strings.TrimPrefix(req.URL.Path, path)
53-
// on proxypass /cdshooks, allow only request on /webhook/ path
54-
if strings.HasSuffix(path, "/cdshooks") && !strings.HasPrefix(reqPath, "/webhook/") {
55-
// return 502 bad gateway
56-
req = &http.Request{} // nolint
57-
} else {
58-
req.Header.Add("X-Forwarded-Host", req.Host)
59-
req.Header.Add("X-Origin-Host", origin.Host)
60-
req.URL.Scheme = origin.Scheme
61-
req.URL.Host = origin.Host
62-
req.URL.Path = origin.Path + reqPath
63-
req.Host = origin.Host
78+
79+
var clientIP string
80+
if s.Cfg.HTTP.HeaderXForwardedFor != "" {
81+
// Retrieve the client ip address from the header (X-Forwarded-For by default)
82+
clientIP = req.Header.Get(s.Cfg.HTTP.HeaderXForwardedFor)
83+
}
84+
if clientIP == "" {
85+
// If the header has not been found, fallback on the remote adress from the http request
86+
clientIP = req.RemoteAddr
6487
}
88+
89+
headerForward := "X-Forwarded-For"
90+
if s.Cfg.HTTP.HeaderXForwardedFor != "" {
91+
headerForward = s.Cfg.HTTP.HeaderXForwardedFor
92+
}
93+
94+
req.Header.Add(headerForward, clientIP)
95+
req.Header.Add("X-Forwarded-Host", req.Host)
96+
req.Header.Add("X-Origin-Host", origin.Host)
97+
req.URL.Scheme = origin.Scheme
98+
req.URL.Host = origin.Host
99+
req.URL.Path = origin.Path + reqPath
100+
req.Host = origin.Host
101+
}
102+
103+
return &reverseProxyWithFilter{
104+
ctx: ctx,
105+
rp: &httputil.ReverseProxy{Director: director},
106+
filter: filter,
107+
}
108+
}
109+
110+
type reverseProxyWithFilter struct {
111+
ctx context.Context
112+
rp *httputil.ReverseProxy
113+
filter func(r *http.Request) bool
114+
}
115+
116+
func (r *reverseProxyWithFilter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
117+
if !r.filter(req) {
118+
log.Debug(r.ctx, "proxy deny on target route")
119+
rw.WriteHeader(http.StatusBadGateway)
120+
return
65121
}
66-
return &httputil.ReverseProxy{Director: director}
122+
r.rp.ServeHTTP(rw, req)
67123
}
68124

69125
func (s *Service) uiServe(fs http.FileSystem, dir string) http.Handler {

sdk/cdsclient/client_cdn.go

-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ func (c *client) CDNItemUpload(ctx context.Context, cdnAddr string, signature st
4747
time.Sleep(1 * time.Second)
4848
continue
4949
}
50-
//_, _, _, err = c.Request(ctx, http.MethodPost, fmt.Sprintf("%s/item/upload", cdnAddr), f, SetHeader("X-CDS-WORKER-SIGNATURE", signature))
5150
savedError = nil
5251
break
5352
}

0 commit comments

Comments
 (0)