From 795f1da6e8ab442a284d9ede0dae0a6097d4b1c0 Mon Sep 17 00:00:00 2001 From: jacov Date: Wed, 4 Mar 2026 17:43:45 +0200 Subject: [PATCH 1/2] Router CORS New functionality for router to add headers for CORS sites New config to specify allowed sites Small test server and script for testing ASG-5216 --- .gitignore | 2 +- README.md | 5 +++++ go.mod | 2 ++ server/config.go | 1 + server/logic_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ server/server.go | 16 ++++++++++++++++ test/README.md | 18 ++++++++++++++++++ test/cors_test.html | 38 ++++++++++++++++++++++++++++++++++++++ test/servefile.go | 13 +++++++++++++ 9 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 test/README.md create mode 100644 test/cors_test.html create mode 100644 test/servefile.go diff --git a/.gitignore b/.gitignore index 5a8a606..df529cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ pkg bin -.hg *.sublime-project *.sublime-workspace *.log @@ -12,3 +11,4 @@ router/router-error-test.log .idea /router.exe router +**/*.exe diff --git a/README.md b/README.md index 838b3aa..63188d5 100644 --- a/README.md +++ b/README.md @@ -18,3 +18,8 @@ We needed a performant and well-behaved front door to all of our services which could forward HTTP as well as Websockets. Nginx fits this bill, but since we need to run on Windows, Nginx is a non-starter. We tried for some time to get Apache to do this job, but we failed to get Apache to robustly forward websockets. + +Testing +------- +For instructions on testing CORS settings using the included utilities, +see [test/README.md](test/README.md). diff --git a/go.mod b/go.mod index 6522dbe..eb021bf 100644 --- a/go.mod +++ b/go.mod @@ -10,9 +10,11 @@ require ( github.com/IMQS/serviceconfigsgo v1.4.0 golang.org/x/net v0.29.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 + gotest.tools/v3 v3.5.1 ) require ( + github.com/google/go-cmp v0.5.9 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect ) diff --git a/server/config.go b/server/config.go index 6ff0593..a4bd74f 100644 --- a/server/config.go +++ b/server/config.go @@ -106,6 +106,7 @@ type ConfigHTTP struct { ResponseHeaderTimeout int RedirectHTTP bool AutomaticGzip automaticGzip + Origins map[string]struct{} } type ConfigRoute struct { diff --git a/server/logic_test.go b/server/logic_test.go index 5900192..a1a06df 100644 --- a/server/logic_test.go +++ b/server/logic_test.go @@ -1,6 +1,7 @@ package server import ( + "gotest.tools/v3/assert" "net/http" "net/url" "testing" @@ -148,3 +149,44 @@ func TestInvalidRoutes(t *testing.T) { "/albjs/extile/(.*)": 123 }}`, "Match /albjs/extile/(.*) has invalid value type. Must be either a string, or an object with 'Target' and 'ValidHosts'") } + +func TestOrigins(t *testing.T) { + jsonString := `{ + "HTTP": { + "Origins": {} + } +}` + routerConf := &Config{} + err := routerConf.LoadString(jsonString) + assert.NilError(t, err) + assert.Equal(t, len(routerConf.HTTP.Origins), 0) + _, ok := routerConf.HTTP.Origins["https://example.com"] + assert.Equal(t, ok, false) + + jsonString = `{ + "HTTP": { + "Origins": { + "https://example.com" : {} + } + } +}` + routerConf = &Config{} + err = routerConf.LoadString(jsonString) + assert.NilError(t, err) + assert.Equal(t, len(routerConf.HTTP.Origins), 1) + _, ok = routerConf.HTTP.Origins["https://example.com"] + assert.Equal(t, ok, true) + _, ok = routerConf.HTTP.Origins["https://example-bad.com"] + assert.Equal(t, ok, false) + + // null test + jsonString = `{ + "HTTP": {} +}` + routerConf = &Config{} + err = routerConf.LoadString(jsonString) + assert.NilError(t, err) + assert.Equal(t, len(routerConf.HTTP.Origins), 0) + _, ok = routerConf.HTTP.Origins["https://example.com"] + assert.Equal(t, ok, false) +} diff --git a/server/server.go b/server/server.go index c108be5..b05aeef 100644 --- a/server/server.go +++ b/server/server.go @@ -457,6 +457,22 @@ the response was sent. This would then result in s.httpTransport.RoundTrip(clean an EOF error when it tried to re-use that TCP connection. */ func (s *Server) forwardHttp(w http.ResponseWriter, req *http.Request, newurl string) { + // Set the Access-Control-Allow-Origin header, based on allow-list + s.errorLog.Infof("Forwarding from \"%v\"", req.Header.Get("Origin")) + _, ok := s.configHttp.Origins[req.Header.Get("Origin")] + if ok { + w.Header().Set("Access-Control-Allow-Origin", req.Header.Get("Origin")) + w.Header().Set("Access-Control-Allow-Credentials", "true") + + // Handle preflight OPTIONS requests + if req.Method == "OPTIONS" { + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + w.WriteHeader(http.StatusOK) + return + } + } + cleaned, err := http.NewRequest(req.Method, newurl, req.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..72ede6a --- /dev/null +++ b/test/README.md @@ -0,0 +1,18 @@ +# Test Folder + +This folder contains utilities for testing CORS (Cross-Origin Resource Sharing) settings on a remote server. + +## Files + +### cors_test_server.go +A minimal Go HTTP server that serves the `cors_test.html` file on `http://localhost:8080`. This allows you to easily load the test page in your browser. + +### cors_test.html +A simple HTML page with JavaScript that lets you specify a remote server URL and make a cross-origin request to it. This helps you verify if your target server's CORS settings are correct by observing the response and any errors. + +## Usage +1. Run `servefile.go` with `go run test/servefile.go`. +2. Open `http://localhost:8080/test/cors_test.html` in your browser. +3. Enter the remote server URL you want to test and click "Test CORS". +4. Observe the results and adjust your target server's CORS configuration as needed. + diff --git a/test/cors_test.html b/test/cors_test.html new file mode 100644 index 0000000..49b8806 --- /dev/null +++ b/test/cors_test.html @@ -0,0 +1,38 @@ + + + + + CORS Test Utility + + + +

CORS Test Utility

+ + + +
+ + + + diff --git a/test/servefile.go b/test/servefile.go new file mode 100644 index 0000000..cb9b16d --- /dev/null +++ b/test/servefile.go @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" + "net/http" +) + +func main() { + fs := http.FileServer(http.Dir(".")) + http.Handle("/", fs) + fmt.Println("Static file server running on http://localhost:8080") + http.ListenAndServe(":8080", nil) +} From 3523c4a4e27757469238841ccae763998115b475 Mon Sep 17 00:00:00 2001 From: jacov Date: Wed, 11 Mar 2026 18:11:05 +0200 Subject: [PATCH 2/2] PR Changes Fixup CORS file server Fixup docs Move cors tests and resources to /test/cors --- .gitignore | 1 + README.md | 2 +- test/README.md | 18 ------------------ test/cors/README.md | 25 +++++++++++++++++++++++++ test/cors/servefile.go | 20 ++++++++++++++++++++ test/{cors_test.html => cors/test.html} | 0 test/servefile.go | 13 ------------- 7 files changed, 47 insertions(+), 32 deletions(-) delete mode 100644 test/README.md create mode 100644 test/cors/README.md create mode 100644 test/cors/servefile.go rename test/{cors_test.html => cors/test.html} (100%) delete mode 100644 test/servefile.go diff --git a/.gitignore b/.gitignore index df529cc..0165143 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ router/router-error-test.log /router.exe router **/*.exe +**/cors \ No newline at end of file diff --git a/README.md b/README.md index 63188d5..79086cd 100644 --- a/README.md +++ b/README.md @@ -22,4 +22,4 @@ Apache to do this job, but we failed to get Apache to robustly forward websocket Testing ------- For instructions on testing CORS settings using the included utilities, -see [test/README.md](test/README.md). +see [test/cors/README.md](test/cors/README.md). diff --git a/test/README.md b/test/README.md deleted file mode 100644 index 72ede6a..0000000 --- a/test/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Test Folder - -This folder contains utilities for testing CORS (Cross-Origin Resource Sharing) settings on a remote server. - -## Files - -### cors_test_server.go -A minimal Go HTTP server that serves the `cors_test.html` file on `http://localhost:8080`. This allows you to easily load the test page in your browser. - -### cors_test.html -A simple HTML page with JavaScript that lets you specify a remote server URL and make a cross-origin request to it. This helps you verify if your target server's CORS settings are correct by observing the response and any errors. - -## Usage -1. Run `servefile.go` with `go run test/servefile.go`. -2. Open `http://localhost:8080/test/cors_test.html` in your browser. -3. Enter the remote server URL you want to test and click "Test CORS". -4. Observe the results and adjust your target server's CORS configuration as needed. - diff --git a/test/cors/README.md b/test/cors/README.md new file mode 100644 index 0000000..0a087eb --- /dev/null +++ b/test/cors/README.md @@ -0,0 +1,25 @@ +# Test Folder + +This folder contains utilities for testing CORS (Cross-Origin Resource Sharing) settings on a remote server. + +## Files + +### servefile.go + +A minimal Go HTTP server that serves the `test.html` file on +`http://localhost:8080`. This allows you to easily load the test page in your +browser. + +### test.html + +A simple HTML page with JavaScript that lets you specify a remote server URL and +make a cross-origin request to it. This helps you verify if your target server's +CORS settings are correct by observing the response and any errors. +Make sure your of your working directory to avoid path errors. + +## Usage +1. Run `servefile.go` with `go run servefile.go`. +2. Open `http://localhost:8080` in your browser. +3. Enter the remote server URL you want to test and click "Test CORS". +4. Observe the results and adjust your target server's CORS configuration as needed. + diff --git a/test/cors/servefile.go b/test/cors/servefile.go new file mode 100644 index 0000000..f6211b3 --- /dev/null +++ b/test/cors/servefile.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "net/http" +) + +type TS struct { +} + +func (TS) ServeHTTP(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, "test.html") +} + +func main() { + fmt.Println("Starting static file server...") + testServe := TS{} + fmt.Println("CORS test web page running on http://localhost:8080") + http.ListenAndServe(":8080", testServe) +} diff --git a/test/cors_test.html b/test/cors/test.html similarity index 100% rename from test/cors_test.html rename to test/cors/test.html diff --git a/test/servefile.go b/test/servefile.go deleted file mode 100644 index cb9b16d..0000000 --- a/test/servefile.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import ( - "fmt" - "net/http" -) - -func main() { - fs := http.FileServer(http.Dir(".")) - http.Handle("/", fs) - fmt.Println("Static file server running on http://localhost:8080") - http.ListenAndServe(":8080", nil) -}