Browse Source

Change how to make Anubis work without a reverse proxy (#86)

* Change how to make Anubis work without a reverse proxy

* Apply suggestions from code review

Co-authored-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: Henri Vasserman <henv@hot.ee>

* add support for unix sockets.

* add env var docs

* lib: fix tests

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

---------

Signed-off-by: Henri Vasserman <henv@hot.ee>
Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Xe Iaso <me@xeiaso.net>
Henri Vasserman 2 months ago
parent
commit
57c3e9f1b2
5 changed files with 26 additions and 10 deletions
  1. 3 3
      cmd/anubis/main.go
  2. 1 0
      docs/docs/CHANGELOG.md
  3. 1 0
      docs/docs/admin/installation.mdx
  4. 20 6
      internal/headers.go
  5. 1 1
      lib/anubis_test.go

+ 3 - 3
cmd/anubis/main.go

@@ -45,7 +45,7 @@ var (
 	slogLevel            = flag.String("slog-level", "INFO", "logging level (see https://pkg.go.dev/log/slog#hdr-Levels)")
 	target               = flag.String("target", "http://localhost:3923", "target to reverse proxy to")
 	healthcheck          = flag.Bool("healthcheck", false, "run a health check against Anubis")
-	debugXRealIPDefault  = flag.String("debug-x-real-ip-default", "", "If set, replace empty X-Real-Ip headers with this value, useful only for debugging Anubis and running it locally")
+	useRemoteAddress     = flag.Bool("use-remote-address", false, "read the client's IP address from the network request, useful for debugging and running Anubis on bare metal")
 )
 
 func keyFromHex(value string) (ed25519.PrivateKey, error) {
@@ -214,7 +214,7 @@ func main() {
 
 	var h http.Handler
 	h = s
-	h = internal.DefaultXRealIP(*debugXRealIPDefault, h)
+	h = internal.RemoteXRealIP(*useRemoteAddress, *bindNetwork, h)
 	h = internal.XForwardedForToXRealIP(h)
 
 	srv := http.Server{Handler: h}
@@ -226,7 +226,7 @@ func main() {
 		"serveRobotsTXT", *robotsTxt,
 		"target", *target,
 		"version", anubis.Version,
-		"debug-x-real-ip-default", *debugXRealIPDefault,
+		"use-remote-address", *useRemoteAddress,
 	)
 
 	go func() {

+ 1 - 0
docs/docs/CHANGELOG.md

@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+- Changed `--debug-x-real-ip-default` to `--use-remote-address`, getting the IP address from the request's socket address instead.
 - DroneBL lookups have been disabled by default
 
 ## v1.15.0

+ 1 - 0
docs/docs/admin/installation.mdx

@@ -55,6 +55,7 @@ Anubis uses these environment variables for configuration:
 | `POLICY_FNAME`            | unset                   | The file containing [bot policy configuration](./policies.md). See the bot policy documentation for more details. If unset, the default bot policy configuration is used.                                                                                                                |
 | `SERVE_ROBOTS_TXT`        | `false`                 | If set `true`, Anubis will serve a default `robots.txt` file that disallows all known AI scrapers by name and then additionally disallows every scraper. This is useful if facts and circumstances make it difficult to change the underlying service to serve such a `robots.txt` file. |
 | `TARGET`                  | `http://localhost:3923` | The URL of the service that Anubis should forward valid requests to. Supports Unix domain sockets, set this to a URI like so: `unix:///path/to/socket.sock`.                                                                                                                             |
+| `USE_REMOTE_ADDRESS`      | unset                   | If set to `true`, Anubis will take the client's IP from the network socket. For production deployments, it is expected that a reverse proxy is used in front of Anubis, which pass the IP using headers, instead.                                                                        |
 
 ### Key generation
 

+ 20 - 6
internal/headers.go

@@ -2,6 +2,7 @@ package internal
 
 import (
 	"log/slog"
+	"net"
 	"net/http"
 
 	"github.com/TecharoHQ/anubis"
@@ -21,16 +22,29 @@ func UnchangingCache(next http.Handler) http.Handler {
 	})
 }
 
-// DefaultXRealIP sets the X-Real-Ip header to the given value if and only if
-// it is not an empty string.
-func DefaultXRealIP(defaultIP string, next http.Handler) http.Handler {
-	if defaultIP == "" {
-		slog.Debug("skipping middleware, defaultIP is empty")
+// RemoteXRealIP sets the X-Real-Ip header to the request's real IP if
+// the setting is enabled by the user.
+func RemoteXRealIP(useRemoteAddress bool, bindNetwork string, next http.Handler) http.Handler {
+	if useRemoteAddress == false {
+		slog.Debug("skipping middleware, useRemoteAddress is empty")
 		return next
 	}
 
+	if bindNetwork == "unix" {
+		// For local sockets there is no real remote address but the localhost
+		// address should be sensible.
+		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			r.Header.Set("X-Real-Ip", "127.0.0.1")
+			next.ServeHTTP(w, r)
+		})
+	}
+
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		r.Header.Set("X-Real-Ip", defaultIP)
+		host, _, err := net.SplitHostPort(r.RemoteAddr)
+		if err != nil {
+			panic(err) // this should never happen
+		}
+		r.Header.Set("X-Real-Ip", host)
 		next.ServeHTTP(w, r)
 	})
 }

+ 1 - 1
lib/anubis_test.go

@@ -47,7 +47,7 @@ func TestCookieSettings(t *testing.T) {
 		CookieName:        t.Name(),
 	})
 
-	ts := httptest.NewServer(internal.DefaultXRealIP("127.0.0.1", srv))
+	ts := httptest.NewServer(internal.RemoteXRealIP(true, "tcp", srv))
 	defer ts.Close()
 
 	cli := &http.Client{