diff --git a/internal/headers.go b/internal/headers.go index 4516b40..639d2b1 100644 --- a/internal/headers.go +++ b/internal/headers.go @@ -1,15 +1,29 @@ package internal import ( + "errors" + "fmt" "log/slog" "net" "net/http" + "net/netip" "strings" "github.com/TecharoHQ/anubis" "github.com/sebest/xff" ) +// TODO: move into config +type XFFComputePreferences struct { + StripPrivate bool + StripLoopback bool + StripCGNAT bool + StripLLU bool + Flatten bool +} + +var CGNat = netip.MustParsePrefix("100.64.0.0/10") + // UnchangingCache sets the Cache-Control header to cache a response for 1 year if // and only if the application is compiled in "release" mode by Docker. func UnchangingCache(next http.Handler) http.Handler { @@ -71,40 +85,106 @@ func XForwardedForUpdate(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer next.ServeHTTP(w, r) - remoteIP, _, err := net.SplitHostPort(r.RemoteAddr) + pref := XFFComputePreferences{ + StripPrivate: true, + StripLoopback: true, + StripCGNAT: true, + Flatten: true, + StripLLU: true, + } - if parsedRemoteIP := net.ParseIP(remoteIP); parsedRemoteIP != nil && parsedRemoteIP.IsLoopback() { - // anubis is likely deployed behind a local reverse proxy - // pass header as-is to not break existing applications + remoteAddr := r.RemoteAddr + origXFFHeader := r.Header.Get("X-Forwarded-For") + + if remoteAddr == "@" { + // remote is a unix socket + // do not touch chain return } + xffHeaderString, err := computeXFFHeader(remoteAddr, origXFFHeader, pref) if err != nil { - slog.Warn("The default format of request.RemoteAddr should be IP:Port", "remoteAddr", r.RemoteAddr) + slog.Debug("computing X-Forwarded-For header failed", "err", err) return } - if xff := r.Header.Get("X-Forwarded-For"); xff != "" { - forwardedList := strings.Split(",", xff) - forwardedList = append(forwardedList, remoteIP) - // this behavior is equivalent to - // ingress-nginx "compute-full-forwarded-for" - // https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#compute-full-forwarded-for - // - // this would be the correct place to strip and/or flatten this list - // - // strip - iterate backwards and eliminate configured trusted IPs - // flatten - only return the last element to avoid spoofing confusion - // - // many applications handle this in different ways, but - // generally they'd be expected to do these two things on - // their own end to find the first non-spoofed IP - r.Header.Set("X-Forwarded-For", strings.Join(forwardedList, ",")) + + if len(xffHeaderString) == 0 { + r.Header.Del("X-Forwarded-For") } else { - r.Header.Set("X-Forwarded-For", remoteIP) + r.Header.Set("X-Forwarded-For", xffHeaderString) } }) } +var ( + ErrCantSplitHostParse = errors.New("internal: unable to net.SplitHostParse") + ErrCantParseRemoteIP = errors.New("internal: unable to parse remote IP") +) + +func computeXFFHeader(remoteAddr string, origXFFHeader string, pref XFFComputePreferences) (string, error) { + remoteIP, _, err := net.SplitHostPort(remoteAddr) + if err != nil { + return "", fmt.Errorf("%w: %w", ErrCantSplitHostParse, err) + } + parsedRemoteIP, err := netip.ParseAddr(remoteIP) + if err != nil { + return "", fmt.Errorf("%w: %w", ErrCantParseRemoteIP, err) + } + + origForwardedList := make([]string, 0, 4) + if origXFFHeader != "" { + origForwardedList = strings.Split(origXFFHeader, ",") + } + origForwardedList = append(origForwardedList, parsedRemoteIP.String()) + forwardedList := make([]string, 0, len(origForwardedList)) + // this behavior is equivalent to + // ingress-nginx "compute-full-forwarded-for" + // https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#compute-full-forwarded-for + // + // this would be the correct place to strip and/or flatten this list + // + // strip - iterate backwards and eliminate configured trusted IPs + // flatten - only return the last element to avoid spoofing confusion + // + // many applications handle this in different ways, but + // generally they'd be expected to do these two things on + // their own end to find the first non-spoofed IP + for i := len(origForwardedList) - 1; i >= 0; i-- { + segmentIP, err := netip.ParseAddr(origForwardedList[i]) + if err != nil { + // can't assess this element, so the remainder of the chain + // can't be trusted. not a fatal error, since anyone can + // spoof an XFF header + slog.Debug("failed to parse XFF segment", "err", err) + break + } + if pref.StripPrivate && segmentIP.IsPrivate() { + continue + } + if pref.StripLoopback && segmentIP.IsLoopback() { + continue + } + if pref.StripLLU && segmentIP.IsLinkLocalUnicast() { + continue + } + if pref.StripCGNAT && CGNat.Contains(segmentIP) { + continue + } + forwardedList = append([]string{segmentIP.String()}, forwardedList...) + } + var xffHeaderString string + if len(forwardedList) == 0 { + xffHeaderString = "" + return xffHeaderString, nil + } + if pref.Flatten { + xffHeaderString = forwardedList[len(forwardedList)-1] + } else { + xffHeaderString = strings.Join(forwardedList, ",") + } + return xffHeaderString, nil +} + // NoStoreCache sets the Cache-Control header to no-store for the response. func NoStoreCache(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/internal/xff_test.go b/internal/xff_test.go new file mode 100644 index 0000000..26bb0e6 --- /dev/null +++ b/internal/xff_test.go @@ -0,0 +1,166 @@ +package internal + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" +) + +func TestXForwardedForUpdateIgnoreUnix(t *testing.T) { + var remoteAddr = "" + var xff = "" + + h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + remoteAddr = r.RemoteAddr + xff = r.Header.Get("X-Forwarded-For") + w.WriteHeader(http.StatusOK) + }) + + r := httptest.NewRequest(http.MethodGet, "/", nil) + + r.RemoteAddr = "@" + + w := httptest.NewRecorder() + + XForwardedForUpdate(h).ServeHTTP(w, r) + + if r.RemoteAddr != remoteAddr { + t.Errorf("wanted remoteAddr to be %s, got: %s", r.RemoteAddr, remoteAddr) + } + + if xff != "" { + t.Error("handler added X-Forwarded-For when it should not have") + } +} + +func TestXForwardedForUpdateAddToChain(t *testing.T) { + var xff = "" + const expected = "1.1.1.1" + + h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + xff = r.Header.Get("X-Forwarded-For") + w.WriteHeader(http.StatusOK) + }) + + srv := httptest.NewServer(XForwardedForUpdate(h)) + + r, err := http.NewRequest(http.MethodGet, srv.URL, nil) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("X-Forwarded-For", "1.1.1.1,10.20.30.40") + + if _, err := srv.Client().Do(r); err != nil { + t.Fatal(err) + } + + if xff != expected { + t.Logf("expected: %s", expected) + t.Logf("got: %s", xff) + t.Error("X-Forwarded-For header was not what was expected") + } +} + +func TestComputeXFFHeader(t *testing.T) { + for _, tt := range []struct { + name string + remoteAddr string + origXFFHeader string + pref XFFComputePreferences + result string + err error + }{ + { + name: "StripPrivate", + remoteAddr: "127.0.0.1:80", + origXFFHeader: "1.1.1.1,10.0.0.1", + pref: XFFComputePreferences{ + StripPrivate: true, + }, + result: "1.1.1.1,127.0.0.1", + }, + { + name: "StripLoopback", + remoteAddr: "127.0.0.1:80", + origXFFHeader: "1.1.1.1,10.0.0.1,127.0.0.1", + pref: XFFComputePreferences{ + StripLoopback: true, + }, + result: "1.1.1.1,10.0.0.1", + }, + { + name: "StripCGNAT", + remoteAddr: "100.64.0.1:80", + origXFFHeader: "1.1.1.1,10.0.0.1,100.64.0.1", + pref: XFFComputePreferences{ + StripCGNAT: true, + }, + result: "1.1.1.1,10.0.0.1", + }, + { + name: "StripLinkLocalUnicastIPv4", + remoteAddr: "169.254.0.1:80", + origXFFHeader: "1.1.1.1,10.0.0.1,169.254.0.1", + pref: XFFComputePreferences{ + StripLLU: true, + }, + result: "1.1.1.1,10.0.0.1", + }, + { + name: "StripLinkLocalUnicastIPv6", + remoteAddr: "169.254.0.1:80", + origXFFHeader: "1.1.1.1,10.0.0.1,fe80::", + pref: XFFComputePreferences{ + StripLLU: true, + }, + result: "1.1.1.1,10.0.0.1", + }, + { + name: "Flatten", + remoteAddr: "127.0.0.1:80", + origXFFHeader: "1.1.1.1,10.0.0.1,fe80::,100.64.0.1,169.254.0.1", + pref: XFFComputePreferences{ + StripPrivate: true, + StripLoopback: true, + StripCGNAT: true, + StripLLU: true, + Flatten: true, + }, + result: "1.1.1.1", + }, + { + name: "invalid-ip-port", + remoteAddr: "fe80::", + err: ErrCantSplitHostParse, + }, + { + name: "invalid-remote-ip", + remoteAddr: "anubis:80", + err: ErrCantParseRemoteIP, + }, + { + name: "no-xff-dont-panic", + remoteAddr: "127.0.0.1:80", + pref: XFFComputePreferences{ + StripPrivate: true, + StripLoopback: true, + StripCGNAT: true, + StripLLU: true, + Flatten: true, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + result, err := computeXFFHeader(tt.remoteAddr, tt.origXFFHeader, tt.pref) + if err != nil && !errors.Is(err, tt.err) { + t.Errorf("computeXFFHeader got the wrong error, wanted %v but got: %v", tt.err, err) + } + + if result != tt.result { + t.Errorf("computeXFFHeader returned the wrong result, wanted %q but got: %q", tt.result, result) + } + }) + } +} diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..f3324c6 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,2 @@ +*.sock +*.pem diff --git a/test/cmd/relayd/main.go b/test/cmd/relayd/main.go new file mode 100644 index 0000000..26700d7 --- /dev/null +++ b/test/cmd/relayd/main.go @@ -0,0 +1,124 @@ +package main + +import ( + "context" + "flag" + "log" + "log/slog" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + "path/filepath" + "strings" + "time" + + "github.com/TecharoHQ/anubis/internal" + "github.com/facebookgo/flagenv" + "github.com/google/uuid" +) + +var ( + bind = flag.String("bind", ":3004", "port to listen on") + certDir = flag.String("cert-dir", "/xe/pki", "where to read mounted certificates from") + certFname = flag.String("cert-fname", "cert.pem", "certificate filename") + keyFname = flag.String("key-fname", "key.pem", "key filename") + proxyTo = flag.String("proxy-to", "http://localhost:5000", "where to reverse proxy to") + slogLevel = flag.String("slog-level", "info", "logging level") +) + +func main() { + flagenv.Parse() + flag.Parse() + + internal.InitSlog(*slogLevel) + + slog.Info("starting", + "bind", *bind, + "cert-dir", *certDir, + "cert-fname", *certFname, + "key-fname", *keyFname, + "proxy-to", *proxyTo, + ) + + cert := filepath.Join(*certDir, *certFname) + key := filepath.Join(*certDir, *keyFname) + + st, err := os.Stat(cert) + + if err != nil { + slog.Error("can't stat cert file", "certFname", cert) + os.Exit(1) + } + + lastModified := st.ModTime() + + go func(lm time.Time) { + t := time.NewTicker(time.Hour) + defer t.Stop() + + for range t.C { + st, err := os.Stat(cert) + if err != nil { + slog.Error("can't stat file", "fname", cert, "err", err) + continue + } + + if st.ModTime().After(lm) { + slog.Info("new cert detected", "oldTime", lm.Format(time.RFC3339), "newTime", st.ModTime().Format(time.RFC3339)) + os.Exit(0) + } + } + }(lastModified) + + u, err := url.Parse(*proxyTo) + if err != nil { + log.Fatal(err) + } + + h := httputil.NewSingleHostReverseProxy(u) + + if u.Scheme == "unix" { + slog.Info("using unix socket proxy") + + h = &httputil.ReverseProxy{ + Director: func(r *http.Request) { + r.URL.Scheme = "http" + r.URL.Host = r.Host + + r.Header.Set("X-Forwarded-Proto", "https") + r.Header.Set("X-Forwarded-Scheme", "https") + r.Header.Set("X-Request-Id", uuid.NewString()) + r.Header.Set("X-Scheme", "https") + + remoteHost, remotePort, err := net.SplitHostPort(r.Host) + if err == nil { + r.Header.Set("X-Forwarded-Host", remoteHost) + r.Header.Set("X-Forwarded-Port", remotePort) + } else { + r.Header.Set("X-Forwarded-Host", r.Host) + } + + host, _, err := net.SplitHostPort(r.RemoteAddr) + if err == nil { + r.Header.Set("X-Real-Ip", host) + } + }, + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", strings.TrimPrefix(*proxyTo, "unix://")) + }, + }, + } + } + + log.Fatal( + http.ListenAndServeTLS( + *bind, + cert, + key, + h, + ), + ) +} diff --git a/test/cmd/unixhttpd/main.go b/test/cmd/unixhttpd/main.go new file mode 100644 index 0000000..a57c202 --- /dev/null +++ b/test/cmd/unixhttpd/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "flag" + "fmt" + "log" + "log/slog" + "net" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/TecharoHQ/anubis/internal" + "github.com/facebookgo/flagenv" +) + +var ( + dir = flag.String("dir", ".", "directory to serve") + slogLevel = flag.String("slog-level", "info", "logging level") + socketPath = flag.String("socket-path", "./unixhttpd.sock", "unix socket path to use") +) + +func init() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", filepath.Base(os.Args[0])) + fmt.Fprintf(os.Stderr, " %s [--dir=.] [--socket-path=./unixhttpd.sock]\n\n", filepath.Base(os.Args[0])) + flag.PrintDefaults() + os.Exit(2) + } +} + +func main() { + flagenv.Parse() + flag.Parse() + + internal.InitSlog(*slogLevel) + + if *dir == "" && *socketPath == "" { + flag.Usage() + } + + slog.Info("starting up", "dir", *dir, "socketPath", *socketPath) + + os.Remove(*socketPath) + + mux := http.NewServeMux() + + mux.HandleFunc("/reqmeta", func(w http.ResponseWriter, r *http.Request) { + contains := strings.Contains(r.Header.Get("Accept"), "text/html") + + if contains { + w.Header().Add("Content-Type", "text/html") + fmt.Fprint(w, "
")
+		}
+
+		r.Write(w)
+
+		if contains {
+			fmt.Fprintln(w, "
") + } + }) + + mux.Handle("/", http.FileServer(http.Dir(*dir))) + + server := http.Server{ + Handler: mux, + } + + unixListener, err := net.Listen("unix", *socketPath) + if err != nil { + panic(err) + } + log.Fatal(server.Serve(unixListener)) +} diff --git a/test/go.mod b/test/go.mod new file mode 100644 index 0000000..ca08313 --- /dev/null +++ b/test/go.mod @@ -0,0 +1,41 @@ +module github.com/TecharoHQ/anubis/test + +go 1.24.2 + +replace github.com/TecharoHQ/anubis => .. + +require ( + github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456 + github.com/google/uuid v1.6.0 +) + +require ( + github.com/TecharoHQ/anubis v1.16.0 // indirect + github.com/a-h/templ v0.3.857 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect + github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect + github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/jsha/minica v1.1.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect + github.com/yl2chen/cidranger v1.0.2 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sys v0.32.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect + k8s.io/apimachinery v0.32.3 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) + +tool ( + github.com/TecharoHQ/anubis/cmd/anubis + github.com/jsha/minica +) diff --git a/test/go.sum b/test/go.sum new file mode 100644 index 0000000..f7914fa --- /dev/null +++ b/test/go.sum @@ -0,0 +1,55 @@ +github.com/a-h/templ v0.3.857 h1:6EqcJuGZW4OL+2iZ3MD+NnIcG7nGkaQeF2Zq5kf9ZGg= +github.com/a-h/templ v0.3.857/go.mod h1:qhrhAkRFubE7khxLZHsBFHfX+gWwVNKbzKeF9GlPV4M= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= +github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456 h1:CkmB2l68uhvRlwOTPrwnuitSxi/S3Cg4L5QYOcL9MBc= +github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456/go.mod h1:zFhibDvPDWmtk4dAQ05sRobtyoffEHygEt3wSNuAzz8= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jsha/minica v1.1.0 h1:O2ZbzAN75w4RTB+5+HfjIEvY5nxRqDlwj3ZlLVG5JD8= +github.com/jsha/minica v1.1.0/go.mod h1:dxC3wNmD+gU1ewXo/R8jB2ihB6wNpyXrG8aUk5Iuf/k= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM= +github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= +github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/test/unix-socket-xff/start.sh b/test/unix-socket-xff/start.sh new file mode 100755 index 0000000..2d13fba --- /dev/null +++ b/test/unix-socket-xff/start.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Remove lingering .sock files, relayd and unixhttpd will do that too but +# measure twice, cut once. +rm *.sock ||: + +# If the transient local TLS certificate doesn't exist, mint a new one +if [ ! -f ../pki/relayd.local.cetacean.club/cert.pem ]; then + # Subshell to contain the directory change + ( + cd ../pki \ + && mkdir -p relayd.local.cetacean.club \ + && \ + # Try using https://github.com/FiloSottile/mkcert for better DevEx, + # but fall back to using https://github.com/jsha/minica in case + # you don't have that installed. + ( + mkcert \ + --cert-file ./relayd.local.cetacean.club/cert.pem \ + --key-file ./relayd.local.cetacean.club/key.pem relayd.local.cetacean.club \ + || go tool minica -domains relayd.local.cetacean.club + ) + ) +fi + +# Build static assets +(cd ../.. && npm ci && npm run assets) + +# Spawn three jobs: + +# HTTP daemon that listens over a unix socket (implicitly ./unixhttpd.sock) +go run ../cmd/unixhttpd & + +# A copy of Anubis, specifically for the current Git checkout +go tool anubis \ + --bind=./anubis.sock \ + --bind-network=unix \ + --target=unix://$(pwd)/unixhttpd.sock & + +# A simple TLS terminator that forwards to Anubis, which will forward to +# unixhttpd +go run ../cmd/relayd \ + --proxy-to=unix://./anubis.sock \ + --cert-dir=../pki/relayd.local.cetacean.club & + +# When you press control c, kill all the child processes to clean things up +trap 'echo signal received!; kill $(jobs -p); wait' SIGINT SIGTERM + +echo "open https://relayd.local.cetacean.club:3004/reqmeta" + +# Wait for all child processes to exit +wait