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:
1 parent
f528c2aa9c
commit
cf49df0346
22 files changed
+570
-26
No files matched your search
@@ -40,6 +40,8 @@ type Config struct {
|
||||
Stats *stats.Collector
|
||||
// HTTPReq configures the endpoint for lego's httpreq DNS provider.
|
||||
HTTPReq HTTPReq
|
||||
// Socket is the local unix socket's path ("" for none); see socket.go.
|
||||
Socket string
|
||||
}
|
||||
|
||||
// Server is the HTTP server.
|
||||
@@ -104,7 +106,7 @@ func (s *Server) Serve(ctx context.Context, ready func()) error {
|
||||
s.certs = cr
|
||||
tlsCfg = &tls.Config{MinVersion: tls.VersionTLS12, GetCertificate: cr.get}
|
||||
}
|
||||
errc := make(chan error, len(s.cfg.Listen))
|
||||
errc := make(chan error, len(s.cfg.Listen)+1)
|
||||
var listeners []net.Listener
|
||||
for _, addr := range s.cfg.Listen {
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
@@ -155,6 +157,7 @@ func (s *Server) Serve(ctx context.Context, ready func()) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
s.serveSocket(errc)
|
||||
s.logHTTPReq(tlsCfg != nil)
|
||||
if ready != nil {
|
||||
ready()
|
||||
@@ -170,6 +173,37 @@ func (s *Server) Serve(ctx context.Context, ready func()) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// serveSocket serves the API on the local unix socket. A socket that
|
||||
// can't be created (e.g. /run/jarl missing outside systemd) is logged, not
|
||||
// fatal: the network listeners still work.
|
||||
func (s *Server) serveSocket(errc chan error) {
|
||||
if s.cfg.Socket == "" {
|
||||
return
|
||||
}
|
||||
ln, err := listenSocket(s.cfg.Socket)
|
||||
if err != nil {
|
||||
s.log.Warn("not listening on the local socket", "path", s.cfg.Socket, "err", err)
|
||||
return
|
||||
}
|
||||
srv := &http.Server{
|
||||
Handler: s.Handler(),
|
||||
ConnContext: socketConnContext,
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
WriteTimeout: 5 * time.Minute,
|
||||
IdleTimeout: 2 * time.Minute,
|
||||
ErrorLog: slog.NewLogLogger(s.log.Handler(), slog.LevelWarn),
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.srvs = append(s.srvs, srv)
|
||||
s.mu.Unlock()
|
||||
s.log.Info("listening", "socket", s.cfg.Socket)
|
||||
go func() {
|
||||
if err := srv.Serve(ln); !errors.Is(err, http.ErrServerClosed) {
|
||||
errc <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *Server) shutdown() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
@@ -178,6 +212,9 @@ func (s *Server) shutdown() {
|
||||
for _, srv := range s.srvs {
|
||||
srv.Shutdown(ctx)
|
||||
}
|
||||
if s.cfg.Socket != "" {
|
||||
os.Remove(s.cfg.Socket)
|
||||
}
|
||||
}
|
||||
|
||||
// ReloadTLS rereads the TLS certificate (on SIGHUP).
|
||||
@@ -344,6 +381,9 @@ func (s *Server) logRequests(next http.Handler) http.Handler {
|
||||
// clientIP returns the client's address, honoring X-Forwarded-For from
|
||||
// trusted proxies.
|
||||
func (s *Server) clientIP(r *http.Request) string {
|
||||
if peerOf(r.Context()) != nil {
|
||||
return "local"
|
||||
}
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
host = r.RemoteAddr
|
||||
@@ -413,6 +453,9 @@ func (s *Server) transport(r *http.Request) (https bool, why string) {
|
||||
if r.TLS != nil {
|
||||
return true, "HTTPS to jarld (tls in jarl.yaml)"
|
||||
}
|
||||
if peerOf(r.Context()) != nil {
|
||||
return true, "jarld's local unix socket (never crosses a network)"
|
||||
}
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
host = r.RemoteAddr
|
||||
|
||||
Reference in new issue
Block a user