Add a local unix socket for root on the server

jarld also listens on /run/jarl/jarl.sock (socket in jarl.yaml). Requests
on it from root or jarld's own user need no token and act as an
administrator; the kernel's SO_PEERCRED says who is connecting, and
anyone else must send a token. The jarl CLI uses the socket when no server
is configured, or with -s local, so 'sudo jarl status' works on the server
with nothing set up and a lost token or password never locks root out.
Changes are audited under the system user's name via "socket".

The systemd unit creates /run/jarl (0750). Socket paths over 107 bytes
are refused with a clear message. The docs explain the socket and what
happens when a token is revoked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-08 15:10:57 +00:00
1 parent f528c2aa9c
commit cf49df0346
22 files changed
+570 -26

No files matched your search

+24 -8
View File
@@ -22,14 +22,17 @@ type Config struct {
Listen []string `yaml:"listen"`
TLS TLS `yaml:"tls"`
TrustedProxies []string `yaml:"trusted_proxies"`
Database string `yaml:"database"`
StagingDir string `yaml:"staging_dir"`
Log Log `yaml:"log"`
Session Session `yaml:"session"`
BIND BIND `yaml:"bind"`
Audit Audit `yaml:"audit"`
Stats Stats `yaml:"stats"`
ACME ACME `yaml:"acme"`
// Socket is a local unix socket for the API, where root and jarld's
// own user need no token ("" for none).
Socket string `yaml:"socket"`
Database string `yaml:"database"`
StagingDir string `yaml:"staging_dir"`
Log Log `yaml:"log"`
Session Session `yaml:"session"`
BIND BIND `yaml:"bind"`
Audit Audit `yaml:"audit"`
Stats Stats `yaml:"stats"`
ACME ACME `yaml:"acme"`
}
// ACME configures the ACME helpers.
@@ -168,6 +171,7 @@ func ParseDuration(s string) (time.Duration, error) {
func Default() *Config {
return &Config{
Listen: []string{"127.0.0.1:8053"},
Socket: "/run/jarl/jarl.sock",
Database: "/var/lib/jarl/jarl.db",
StagingDir: "/var/lib/jarl/staging",
Log: Log{Level: "info", Format: "text"},
@@ -220,6 +224,12 @@ func (c *Config) check() error {
if len(c.Listen) == 0 {
return fmt.Errorf("listen: at least one address is needed")
}
if c.Socket != "" && !filepath.IsAbs(c.Socket) {
return fmt.Errorf("socket must be an absolute path (or \"\" for none)")
}
if len(c.Socket) > 107 {
return fmt.Errorf("socket: the path is %d bytes long; unix sockets allow at most 107", len(c.Socket))
}
if (c.TLS.CertFile == "") != (c.TLS.KeyFile == "") {
return fmt.Errorf("tls: set both cert_file and key_file")
}
@@ -334,6 +344,12 @@ tls:
cert_file: ""
key_file: ""
# A local unix socket for the API and the jarl CLI. Requests on it from
# root or jarld's own user need no token and have administrator rights
# ('sudo jarl status' works with nothing configured); anyone else must send
# a token. systemd creates /run/jarl. "" turns the socket off.
socket: /run/jarl/jarl.sock
# Reverse proxies whose X-Forwarded-For / X-Forwarded-Proto headers are
# trusted (addresses or prefixes).
trusted_proxies: []