diff --git a/.gitignore b/.gitignore index 5a8a606..0165143 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ pkg bin -.hg *.sublime-project *.sublime-workspace *.log @@ -12,3 +11,5 @@ router/router-error-test.log .idea /router.exe router +**/*.exe +**/cors \ No newline at end of file diff --git a/README.md b/README.md index 838b3aa..79086cd 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/cors/README.md](test/cors/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/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 new file mode 100644 index 0000000..49b8806 --- /dev/null +++ b/test/cors/test.html @@ -0,0 +1,38 @@ + + + + + CORS Test Utility + + + +

CORS Test Utility

+ + + +
+ + + +