Index | Thread | Search

From:
Christoph Liebender <christoph@liebender.dev>
Subject:
www/anubis: add unveil(2) restrictions
To:
ports@openbsd.org
Date:
Mon, 19 May 2025 18:26:06 +0200

Download raw body.

Thread
Hi ports@,

this patch adds unveil(2) sandboxing to www/anubis. I think it is 
probably a good idea in this case, as anubis is intended to "take the 
beating" in a reverse-proxy setup. Because of this and the fact that the 
daemon runs as the www user (which is not a daemon user), the things 
that it can do should be restricted.

Initially, I suggested both pledge(2) and unveil(2), but stu@ argued 
that pledge would be too much to support reliably. This is why this 
patch only adds calls to unveil. The call site is before the daemon 
starts listening. So any kind of configuration parsing is already done. 
If I understand correctly, the only things then required are, depending 
on configuration:

- /etc/{resolv.conf, hosts, ssl/{cert.pem, openssl.cnf}} with read
   permissions, when TARGET is set to a hostname
- read-write on TARGET if it points is a unix socket
- create on BIND if BIND_NETWORK is unix (same for METRICS_BIND)

comments, testers, ok?

- Christoph

diff --git a/www/anubis/Makefile b/www/anubis/Makefile
index 8e352641089..0f2d0beac90 100644
--- a/www/anubis/Makefile
+++ b/www/anubis/Makefile
@@ -3,6 +3,7 @@ COMMENT=	proof-of-work proxy to protect web resources from scrapers
 V=		1.18.0
 DISTNAME=	anubis-src-vendor-npm-$V
 PKGNAME=	anubis-$V
+REVISION=	0
 
 CATEGORIES=	www
 
diff --git a/www/anubis/patches/patch-cmd_anubis_main_go b/www/anubis/patches/patch-cmd_anubis_main_go
new file mode 100644
index 00000000000..5cd7aa9c40d
--- /dev/null
+++ b/www/anubis/patches/patch-cmd_anubis_main_go
@@ -0,0 +1,64 @@
+Index: cmd/anubis/main.go
+--- cmd/anubis/main.go.orig
++++ cmd/anubis/main.go
+@@ -37,6 +37,27 @@ import (
+ 	"github.com/prometheus/client_golang/prometheus/promhttp"
+ )
+ 
++// #include <unistd.h>
++// #include <stdlib.h>
++import "C"
++import "unsafe"
++
++func unveil(path string, permissions string) {
++	if path == "" && permissions == "" {
++		C.unveil(nil, nil)
++		slog.Debug("unveiled")
++		return
++	}
++
++	cpath := C.CString(path)
++	cpermissions := C.CString(permissions)
++	defer C.free(unsafe.Pointer(cpath))
++	defer C.free(unsafe.Pointer(cpermissions))
++
++	C.unveil(cpath, cpermissions)
++	slog.Debug("unveil", "path", path, "perms", permissions)
++}
++
+ var (
+ 	basePrefix               = flag.String("base-prefix", "", "base prefix (root URL) the application is served under e.g. /myapp")
+ 	bind                     = flag.String("bind", ":8923", "network address to bind HTTP to")
+@@ -335,6 +356,32 @@ func main() {
+ 			log.Printf("cannot shut down: %v", err)
+ 		}
+ 	}()
++
++	bindUnix := *bindNetwork == "unix"
++	metricsBindUnix := *metricsBindNetwork == "unix"
++	targetUnix := strings.HasPrefix(*target, "unix://")
++	targetIP := net.ParseIP(*target) != nil
++	if bindUnix {
++		unveil(*bind, "c")
++	}
++	if metricsBindUnix {
++		unveil(*metricsBind, "c")
++	}
++	if targetUnix {
++		unveil(strings.TrimPrefix(*target, "unix://"), "rw")
++	}
++	if !targetUnix && !targetIP {
++		rpaths := []string{
++			"/etc/resolv.conf",
++			"/etc/hosts",
++			"/etc/ssl/openssl.cnf",
++			"/etc/ssl/cert.pem",
++		}
++		for _, rpath := range rpaths {
++			unveil(rpath, "r")
++		}
++	}
++	unveil("", "")
+ 
+ 	if err := srv.Serve(listener); !errors.Is(err, http.ErrServerClosed) {
+ 		log.Fatal(err)