Weighs the soul of incoming HTTP requests to stop AI crawlers

feat: more elaborate XFF compute (#350)

* feat: more elaborate XFF compute

#328 followup

now featuring configuration and
defaults that shouldn't break most
setups.

fixes #344

* refactor: obvious condition eval order optimization

* feat: add StripLLU implementation

* chore: I'm sorry it's 7 AM

* test: add test environment for unix socket serving

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(unix-socket-xff): comment out the shell script more

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(internal): fix logic bug in XFF computation, add tests

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(internal): prevent panic in local testing

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(internal): shuffle around return values to flow better

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Xe Iaso <me@xeiaso.net>

authored by

Aurelia
Xe Iaso
and committed by
GitHub
a420db8b 5a4f68d3

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