Skip to content
代码片段 群组 项目
提交 d2ee591f 编辑于 作者: Paul Slaughter's avatar Paul Slaughter
浏览文件

Update workhorse to allow gitlab-static.net CORS

- Needed by Web IDE since extensions run in sandboxed iframe
上级 32e040f3
No related branches found
No related tags found
无相关合并请求
...@@ -43,6 +43,7 @@ type routeOptions struct { ...@@ -43,6 +43,7 @@ type routeOptions struct {
tracing bool tracing bool
isGeoProxyRoute bool isGeoProxyRoute bool
matchers []matcherFunc matchers []matcherFunc
allowOrigins *regexp.Regexp
} }
const ( const (
...@@ -92,6 +93,12 @@ func withGeoProxy() func(*routeOptions) { ...@@ -92,6 +93,12 @@ func withGeoProxy() func(*routeOptions) {
} }
} }
func withAllowOrigins(pattern string) func(*routeOptions) {
return func(options *routeOptions) {
options.allowOrigins = compileRegexp(pattern)
}
}
func (u *upstream) observabilityMiddlewares(handler http.Handler, method string, regexpStr string, opts *routeOptions) http.Handler { func (u *upstream) observabilityMiddlewares(handler http.Handler, method string, regexpStr string, opts *routeOptions) http.Handler {
handler = log.AccessLogger( handler = log.AccessLogger(
handler, handler,
...@@ -128,6 +135,9 @@ func (u *upstream) route(method, regexpStr string, handler http.Handler, opts .. ...@@ -128,6 +135,9 @@ func (u *upstream) route(method, regexpStr string, handler http.Handler, opts ..
// Add distributed tracing // Add distributed tracing
handler = tracing.Handler(handler, tracing.WithRouteIdentifier(regexpStr)) handler = tracing.Handler(handler, tracing.WithRouteIdentifier(regexpStr))
} }
if options.allowOrigins != nil {
handler = corsMiddleware(handler, options.allowOrigins)
}
return routeEntry{ return routeEntry{
method: method, method: method,
...@@ -360,6 +370,7 @@ func configureRoutes(u *upstream) { ...@@ -360,6 +370,7 @@ func configureRoutes(u *upstream) {
assetsNotFoundHandler, assetsNotFoundHandler,
), ),
withoutTracing(), // Tracing on assets is very noisy withoutTracing(), // Tracing on assets is very noisy
withAllowOrigins("^https://.*\\.web-ide\\.gitlab-static\\.net$"),
), ),
// Uploads // Uploads
...@@ -425,6 +436,7 @@ func configureRoutes(u *upstream) { ...@@ -425,6 +436,7 @@ func configureRoutes(u *upstream) {
assetsNotFoundHandler, assetsNotFoundHandler,
), ),
withoutTracing(), // Tracing on assets is very noisy withoutTracing(), // Tracing on assets is very noisy
withAllowOrigins("^https://.*\\.web-ide\\.gitlab-static\\.net$"),
), ),
// Don't define a catch-all route. If a route does not match, then we know // Don't define a catch-all route. If a route does not match, then we know
...@@ -442,3 +454,20 @@ func denyWebsocket(next http.Handler) http.Handler { ...@@ -442,3 +454,20 @@ func denyWebsocket(next http.Handler) http.Handler {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
}) })
} }
func corsMiddleware(next http.Handler, allowOriginRegex *regexp.Regexp) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestOrigin := r.Header.Get("Origin")
hasOriginMatch := allowOriginRegex.MatchString(requestOrigin)
hasMethodMatch := r.Method == "GET" || r.Method == "HEAD" || r.Method == "OPTIONS"
if hasOriginMatch && hasMethodMatch {
w.Header().Set("Access-Control-Allow-Origin", requestOrigin)
// why: `Vary: Origin` is needed because allowable origin is variable
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#the_http_response_headers
w.Header().Set("Vary", "Origin")
}
next.ServeHTTP(w, r)
})
}
...@@ -7,6 +7,24 @@ import ( ...@@ -7,6 +7,24 @@ import (
"gitlab.com/gitlab-org/gitlab/workhorse/internal/testhelper" "gitlab.com/gitlab-org/gitlab/workhorse/internal/testhelper"
) )
func TestStaticCORS(t *testing.T) {
path := "/assets/static.txt"
content := "local geo asset"
testhelper.SetupStaticFileHelper(t, path, content, testDocumentRoot)
testCases := []testCaseRequest{
{"With no origin, does not set cors headers", "GET", "/assets/static.txt", map[string]string{}, map[string]string{"Access-Control-Allow-Origin": ""}},
{"With unknown origin, does not set cors headers", "GET", "/assets/static.txt", map[string]string{"Origin": "https://example.com"}, map[string]string{"Access-Control-Allow-Origin": ""}},
{"With known origin, sets cors headers", "GET", "/assets/static.txt", map[string]string{"Origin": "https://123.cdn.web-ide.gitlab-static.net"}, map[string]string{"Access-Control-Allow-Origin": "https://123.cdn.web-ide.gitlab-static.net", "Vary": "Origin"}},
{"With known origin HEAD, sets cors headers", "HEAD", "/assets/static.txt", map[string]string{"Origin": "https://123.cdn.web-ide.gitlab-static.net"}, map[string]string{"Access-Control-Allow-Origin": "https://123.cdn.web-ide.gitlab-static.net", "Vary": "Origin"}},
{"With known origin OPTIONS, sets cors headers", "OPTIONS", "/assets/static.txt", map[string]string{"Origin": "https://123.cdn.web-ide.gitlab-static.net"}, map[string]string{"Access-Control-Allow-Origin": "https://123.cdn.web-ide.gitlab-static.net", "Vary": "Origin"}},
{"With known origin POST, does not set cors headers", "POST", "/assets/static.txt", map[string]string{"Origin": "https://123.cdn.web-ide.gitlab-static.net"}, map[string]string{"Access-Control-Allow-Origin": ""}},
{"With evil origin, does not set cors headers", "GET", "/assets/static.txt", map[string]string{"Origin": "https://123.cdn.web-ide.gitlab-static.net.evil.com"}, map[string]string{"Access-Control-Allow-Origin": ""}},
}
runTestCasesWithGeoProxyEnabledRequest(t, testCases)
}
func TestAdminGeoPathsWithGeoProxy(t *testing.T) { func TestAdminGeoPathsWithGeoProxy(t *testing.T) {
testCases := []testCase{ testCases := []testCase{
{"Regular admin/geo", "/admin/geo", "Geo primary received request to path /admin/geo"}, {"Regular admin/geo", "/admin/geo", "Geo primary received request to path /admin/geo"},
......
...@@ -37,6 +37,14 @@ type testCasePost struct { ...@@ -37,6 +37,14 @@ type testCasePost struct {
body io.Reader body io.Reader
} }
type testCaseRequest struct {
desc string
method string
path string
headers map[string]string
expectedHeaders map[string]string
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
// Secret should be configured before any Geo API poll happens to prevent // Secret should be configured before any Geo API poll happens to prevent
// race conditions where the first API call happens without a secret path // race conditions where the first API call happens without a secret path
...@@ -367,6 +375,29 @@ func runTestCasesPost(t *testing.T, ws *httptest.Server, testCases []testCasePos ...@@ -367,6 +375,29 @@ func runTestCasesPost(t *testing.T, ws *httptest.Server, testCases []testCasePos
} }
} }
func runTestCasesRequest(t *testing.T, ws *httptest.Server, testCases []testCaseRequest) {
t.Helper()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
client := http.Client{}
request, err := http.NewRequest(tc.method, ws.URL+tc.path, nil)
require.NoError(t, err)
for key, value := range tc.headers {
request.Header.Set(key, value)
}
resp, err := client.Do(request)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode, "response code")
for key, value := range tc.expectedHeaders {
require.Equal(t, resp.Header.Get(key), value, fmt.Sprint("response header ", key))
}
})
}
}
func runTestCasesWithGeoProxyEnabled(t *testing.T, testCases []testCase) { func runTestCasesWithGeoProxyEnabled(t *testing.T, testCases []testCase) {
remoteServer := startRemoteServer(t) remoteServer := startRemoteServer(t)
...@@ -389,6 +420,17 @@ func runTestCasesWithGeoProxyEnabledPost(t *testing.T, testCases []testCasePost) ...@@ -389,6 +420,17 @@ func runTestCasesWithGeoProxyEnabledPost(t *testing.T, testCases []testCasePost)
runTestCasesPost(t, ws, testCases) runTestCasesPost(t, ws, testCases)
} }
func runTestCasesWithGeoProxyEnabledRequest(t *testing.T, testCases []testCaseRequest) {
remoteServer := startRemoteServer(t)
geoProxyEndpointResponseBody := fmt.Sprintf(`{"geo_enabled":true,"geo_proxy_url":"%v"}`, remoteServer.URL)
railsServer := startRailsServer(t, &geoProxyEndpointResponseBody)
ws, _ := startWorkhorseServer(t, railsServer.URL, true)
runTestCasesRequest(t, ws, testCases)
}
func newUpstreamConfig(authBackend string) *config.Config { func newUpstreamConfig(authBackend string) *config.Config {
return &config.Config{ return &config.Config{
Version: "123", Version: "123",
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册