From 76c8ea788f494a80dbcea450b92b728fe64d3134 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 23 Jul 2026 14:19:30 -0700 Subject: [PATCH] fix(landjail): allow TCP bind under Landlock Landlock V4 handles both bind_tcp and connect_tcp. Restricting with only connect rules denied all binds, breaking local servers like opencode web. Handle connect only so egress stays proxy-only while bind/listen remains unrestricted. --- docs/architecture.md | 2 +- landjail/child.go | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index df75a05..48903e4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -200,7 +200,7 @@ Unlike `nsjail`, it does not rely on transparent iptables redirection. Instead, It also clears `NO_PROXY` and `no_proxy` so the target command cannot bypass Boundary through proxy bypass lists. -Landlock restricts the child so it can connect only to the Boundary proxy port. This means the backend depends on clients honoring proxy environment variables. A client that ignores those variables will generally fail to connect rather than bypass Boundary. +Landlock restricts the child so it can connect only to the Boundary proxy port. Binding/listening on TCP ports is allowed (any port) so local servers inside the jail still work. This means the backend depends on clients honoring proxy environment variables. A client that ignores those variables will generally fail to connect rather than bypass Boundary. ## TLS and certificate trust diff --git a/landjail/child.go b/landjail/child.go index ad9c9dc..91aeee2 100644 --- a/landjail/child.go +++ b/landjail/child.go @@ -9,34 +9,29 @@ import ( "github.com/coder/boundary/config" "github.com/coder/boundary/util" "github.com/landlock-lsm/go-landlock/landlock" + ll "github.com/landlock-lsm/go-landlock/landlock/syscall" ) type LandlockConfig struct { // TODO(yevhenii): - // - should it be able to bind to any port? // - should it be able to connect to any port on localhost? - // BindTCPPorts []int ConnectTCPPorts []int } func ApplyLandlockRestrictions(logger *slog.Logger, cfg LandlockConfig) error { - // Get the Landlock version which works for Kernel 6.7+ - llCfg := landlock.V4 + // Restrict connect only. Including bind_tcp in handled rights without + // explicit allow rules denies all binds (breaks local servers). + llCfg := landlock.MustConfig(landlock.AccessNetSet(ll.AccessNetConnectTCP)) - // Collect our rules var netRules []landlock.Rule - - // Add rules for TCP connections for _, port := range cfg.ConnectTCPPorts { logger.Debug("Adding TCP connect port", "port", port) netRules = append(netRules, landlock.ConnectTCP(uint16(port))) } - err := llCfg.RestrictNet(netRules...) - if err != nil { + if err := llCfg.RestrictNet(netRules...); err != nil { return fmt.Errorf("failed to apply Landlock network restrictions: %w", err) } - return nil }