mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-18 12:49:07 +02:00
WIP
This commit is contained in:
+26
-6
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
@@ -79,6 +80,8 @@ var (
|
||||
updateSettingsDisabled bool
|
||||
captureEnabled bool
|
||||
networksDisabled bool
|
||||
socketOwner string
|
||||
strictSocketDisabled bool
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "netbird",
|
||||
@@ -143,10 +146,12 @@ func init() {
|
||||
|
||||
defaultDaemonAddr := "unix:///var/run/netbird.sock"
|
||||
if runtime.GOOS == "windows" {
|
||||
defaultDaemonAddr = "tcp://127.0.0.1:41731"
|
||||
// Named pipe (not loopback TCP): the pipe SDDL gates who may connect and
|
||||
// the pipe client token carries the caller's SID for per-RPC authorization.
|
||||
defaultDaemonAddr = "npipe://netbird"
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
|
||||
rootCmd.PersistentFlags().StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp|npipe]://[path|host:port|name]")
|
||||
rootCmd.PersistentFlags().StringVarP(&managementURL, "management-url", "m", "", fmt.Sprintf("Management Service URL [http|https]://[host]:[port] (default \"%s\")", profilemanager.DefaultManagementURL))
|
||||
rootCmd.PersistentFlags().StringVar(&adminURL, "admin-url", "", fmt.Sprintf("Admin Panel URL [http|https]://[host]:[port] (default \"%s\")", profilemanager.DefaultAdminURL))
|
||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "info", "sets NetBird log level")
|
||||
@@ -265,16 +270,31 @@ func FlagNameToEnvVar(cmdFlag string, prefix string) string {
|
||||
}
|
||||
|
||||
// DialClientGRPCServer returns client connection to the daemon server.
|
||||
//
|
||||
// The daemon reads the caller's kernel identity from the transport (SO_PEERCRED
|
||||
// on a Unix socket, the client token on a Windows named pipe), so the client
|
||||
// side uses insecure (plaintext) credentials — it needs no cooperation to be
|
||||
// identified. For npipe addresses we install a context dialer since gRPC's
|
||||
// resolver does not understand Windows named pipes.
|
||||
func DialClientGRPCServer(ctx context.Context, addr string) (*grpc.ClientConn, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
return grpc.DialContext(
|
||||
ctx,
|
||||
strings.TrimPrefix(addr, "tcp://"),
|
||||
opts := []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithBlock(),
|
||||
)
|
||||
}
|
||||
|
||||
target := strings.TrimPrefix(addr, "tcp://")
|
||||
if strings.HasPrefix(addr, "npipe://") {
|
||||
path := pipePath(strings.TrimPrefix(addr, "npipe://"))
|
||||
opts = append(opts, grpc.WithContextDialer(func(dialCtx context.Context, _ string) (net.Conn, error) {
|
||||
return dialNamedPipe(dialCtx, path)
|
||||
}))
|
||||
target = "passthrough:///netbird-daemon-pipe"
|
||||
}
|
||||
|
||||
return grpc.DialContext(ctx, target, opts...)
|
||||
}
|
||||
|
||||
// WithBackOff execute function in backoff cycle.
|
||||
|
||||
@@ -56,6 +56,9 @@ func init() {
|
||||
serviceCmd.PersistentFlags().BoolVar(&enableJSONSocket, "enable-json-socket", false, "Enables the HTTP/JSON API socket served by grpc-gateway. To persist, use: netbird service install --enable-json-socket")
|
||||
serviceCmd.PersistentFlags().StringVar(&jsonSocket, "json-socket", defaultJSONSocket, "HTTP/JSON API socket address [unix|tcp]://[path|host:port]. Requires --enable-json-socket to serve. To persist, use: netbird service install --enable-json-socket --json-socket")
|
||||
|
||||
serviceCmd.PersistentFlags().StringVar(&socketOwner, "socket-owner", "", "user to own the daemon control socket; restricts it to that user plus the netbird group (0660). If unset, the first client to connect claims ownership (trust-on-first-use). Persisted via: netbird service install --socket-owner")
|
||||
serviceCmd.PersistentFlags().BoolVar(&strictSocketDisabled, "disable-strict-socket", false, "leave the daemon control socket world-writable (0666) instead of restricting it (root-only, discouraged). Persisted via: netbird service install --disable-strict-socket")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&serviceName, "service", "s", defaultServiceName, "Netbird system service name")
|
||||
serviceEnvDesc := `Sets extra environment variables for the service. ` +
|
||||
`You can specify a comma-separated list of KEY=VALUE pairs. ` +
|
||||
|
||||
@@ -5,6 +5,7 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
@@ -13,12 +14,31 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/ipcauth"
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
"github.com/netbirdio/netbird/client/server"
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
)
|
||||
|
||||
// daemonServerOptions returns the gRPC server options that install peer-identity
|
||||
// transport credentials on the daemon control channel. Identity extraction is
|
||||
// only possible over a Unix socket (SO_PEERCRED) or Windows named pipe (client
|
||||
// token); over TCP, or on platforms without a peer-credential primitive, the
|
||||
// daemon runs without per-caller authorization and logs a warning.
|
||||
func daemonServerOptions(network string) []grpc.ServerOption {
|
||||
creds := ipcauth.NewTransportCredentials()
|
||||
if creds == nil {
|
||||
log.Warnf("daemon control channel has no peer-identity primitive on %s; per-caller authorization is disabled", runtime.GOOS)
|
||||
return nil
|
||||
}
|
||||
if network == "tcp" {
|
||||
log.Warnf("daemon is listening on TCP (%s); peer identity cannot be authenticated over TCP, per-caller authorization is disabled", daemonAddr)
|
||||
return nil
|
||||
}
|
||||
return []grpc.ServerOption{grpc.Creds(creds)}
|
||||
}
|
||||
|
||||
func validateJSONSocketFlags() error {
|
||||
if serviceCmd.PersistentFlags().Changed("json-socket") && !enableJSONSocket {
|
||||
return fmt.Errorf("--json-socket requires --enable-json-socket to configure the daemon JSON gateway")
|
||||
@@ -37,8 +57,13 @@ func (p *program) Start(svc service.Service) error {
|
||||
// Collect static system and platform information
|
||||
system.UpdateStaticInfoAsync()
|
||||
|
||||
// in any case, even if configuration does not exists we run daemon to serve CLI gRPC API.
|
||||
p.serv = grpc.NewServer()
|
||||
network, _, err := parseListenAddress(daemonAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse daemon address: %w", err)
|
||||
}
|
||||
|
||||
// in any case, even if configuration does not exist we run daemon to serve the CLI gRPC API.
|
||||
p.serv = grpc.NewServer(daemonServerOptions(network)...)
|
||||
|
||||
daemonListener, err := listenOnAddress(daemonAddr)
|
||||
if err != nil {
|
||||
@@ -62,7 +87,8 @@ func (p *program) Start(svc service.Service) error {
|
||||
defer jsonListener.Close()
|
||||
}
|
||||
|
||||
if err := daemonListener.chmodUnixSocket("daemon"); err != nil {
|
||||
serveListener, err := secureDaemonListener(daemonListener)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
@@ -84,6 +110,7 @@ func (p *program) Start(svc service.Service) error {
|
||||
p.serverInstanceMu.Unlock()
|
||||
|
||||
if jsonListener != nil {
|
||||
log.Warnf("JSON gateway (--enable-json-socket) re-dials the daemon locally as the daemon's own identity and BYPASSES per-caller authorization; restrict access to %s separately", jsonSocket)
|
||||
if err := p.startJSONGateway(jsonListener, daemonAddr); err != nil {
|
||||
log.Fatalf("failed to start daemon JSON server: %v", err)
|
||||
}
|
||||
@@ -92,7 +119,7 @@ func (p *program) Start(svc service.Service) error {
|
||||
}
|
||||
|
||||
log.Printf("started daemon server: %v", daemonListener.address)
|
||||
if err := p.serv.Serve(daemonListener.Listener); err != nil {
|
||||
if err := p.serv.Serve(serveListener); err != nil {
|
||||
log.Errorf("failed to serve daemon requests: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -71,6 +71,14 @@ func buildServiceArguments() []string {
|
||||
args = append(args, "--enable-json-socket", "--json-socket", jsonSocket)
|
||||
}
|
||||
|
||||
if socketOwner != "" {
|
||||
args = append(args, "--socket-owner", socketOwner)
|
||||
}
|
||||
|
||||
if strictSocketDisabled {
|
||||
args = append(args, "--disable-strict-socket")
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ type serviceParams struct {
|
||||
EnableCapture bool `json:"enable_capture,omitempty"`
|
||||
DisableNetworks bool `json:"disable_networks,omitempty"`
|
||||
EnableJSONSocket bool `json:"enable_json_socket,omitempty"`
|
||||
SocketOwner string `json:"socket_owner,omitempty"`
|
||||
DisableStrictSocket bool `json:"disable_strict_socket,omitempty"`
|
||||
ServiceEnvVars map[string]string `json:"service_env_vars,omitempty"`
|
||||
}
|
||||
|
||||
@@ -86,6 +88,8 @@ func currentServiceParams() *serviceParams {
|
||||
EnableCapture: captureEnabled,
|
||||
DisableNetworks: networksDisabled,
|
||||
EnableJSONSocket: enableJSONSocket,
|
||||
SocketOwner: socketOwner,
|
||||
DisableStrictSocket: strictSocketDisabled,
|
||||
}
|
||||
|
||||
if len(serviceEnvVars) > 0 {
|
||||
@@ -165,6 +169,14 @@ func applyServiceParams(cmd *cobra.Command, params *serviceParams) {
|
||||
networksDisabled = params.DisableNetworks
|
||||
}
|
||||
|
||||
if !serviceCmd.PersistentFlags().Changed("socket-owner") {
|
||||
socketOwner = params.SocketOwner
|
||||
}
|
||||
|
||||
if !serviceCmd.PersistentFlags().Changed("disable-strict-socket") {
|
||||
strictSocketDisabled = params.DisableStrictSocket
|
||||
}
|
||||
|
||||
applyServiceEnvParams(cmd, params)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
//go:build !windows
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// listenNamedPipe is unsupported off Windows; named pipes are a Windows-only transport.
|
||||
func listenNamedPipe(string) (net.Listener, error) {
|
||||
return nil, fmt.Errorf("named pipe daemon socket is only supported on Windows, not %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
// dialNamedPipe is unsupported off Windows.
|
||||
func dialNamedPipe(context.Context, string) (net.Conn, error) {
|
||||
return nil, fmt.Errorf("named pipe daemon socket is only supported on Windows, not %s", runtime.GOOS)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//go:build windows
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/ipcauth"
|
||||
)
|
||||
|
||||
// listenNamedPipe creates the daemon control named pipe with a tight SDDL
|
||||
// (SYSTEM + Administrators + interactive users). ListenPipe fails if the pipe
|
||||
// already exists (first-instance semantics), which prevents a squatting process
|
||||
// from pre-creating it — we surface that error loudly rather than falling back.
|
||||
func listenNamedPipe(path string) (net.Listener, error) {
|
||||
return winio.ListenPipe(path, &winio.PipeConfig{
|
||||
SecurityDescriptor: ipcauth.DefaultPipeSDDL(),
|
||||
})
|
||||
}
|
||||
|
||||
// dialNamedPipe connects to the daemon control named pipe.
|
||||
func dialNamedPipe(ctx context.Context, path string) (net.Conn, error) {
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
timeout := time.Until(deadline)
|
||||
return winio.DialPipe(path, &timeout)
|
||||
}
|
||||
return winio.DialPipeContext(ctx, path)
|
||||
}
|
||||
@@ -26,6 +26,15 @@ func listenOnAddress(addr string) (*socketListener, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if network == "npipe" {
|
||||
path := pipePath(address)
|
||||
listener, err := listenNamedPipe(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &socketListener{Listener: listener, network: network, address: path}, nil
|
||||
}
|
||||
|
||||
if network == "unix" {
|
||||
removeStaleUnixSocket(address)
|
||||
}
|
||||
@@ -45,13 +54,23 @@ func parseListenAddress(addr string) (string, string, error) {
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "unix", "tcp":
|
||||
case "unix", "tcp", "npipe":
|
||||
return network, address, nil
|
||||
default:
|
||||
return "", "", fmt.Errorf("unsupported daemon address protocol: %v", network)
|
||||
}
|
||||
}
|
||||
|
||||
// pipePath maps a daemon-addr npipe name (e.g. "netbird" from "npipe://netbird")
|
||||
// to a Windows named-pipe path (\\.\pipe\netbird). A caller may also pass a full
|
||||
// \\.\pipe\ path, which is returned unchanged.
|
||||
func pipePath(name string) string {
|
||||
if strings.HasPrefix(name, `\\`) {
|
||||
return name
|
||||
}
|
||||
return `\\.\pipe\` + name
|
||||
}
|
||||
|
||||
func removeStaleUnixSocket(path string) {
|
||||
stat, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
//go:build windows
|
||||
|
||||
package cmd
|
||||
|
||||
import "net"
|
||||
|
||||
// secureDaemonListener is a no-op on Windows: the named-pipe SDDL gates who may
|
||||
// connect (Layer 1), and the pipe client token supplies per-RPC identity.
|
||||
func secureDaemonListener(l *socketListener) (net.Listener, error) {
|
||||
return l.Listener, nil
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
//go:build !windows && !ios && !android
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/ipcauth"
|
||||
"github.com/netbirdio/netbird/client/internal/shell"
|
||||
)
|
||||
|
||||
// secureDaemonListener applies the Layer-1 access control to the daemon control
|
||||
// socket and returns the listener to serve on. For a Unix socket this restricts
|
||||
// the socket to an owner (plus the netbird group); for anything else it is a
|
||||
// no-op (TCP is legacy/unauthenticated; named pipes are gated by their SDDL).
|
||||
func secureDaemonListener(l *socketListener) (net.Listener, error) {
|
||||
if l.network != "unix" {
|
||||
return l.Listener, nil
|
||||
}
|
||||
|
||||
owner := effectiveSocketOwner()
|
||||
switch {
|
||||
case strictSocketDisabled:
|
||||
// Root-only opt-out (via service.json): leave it world-writable.
|
||||
if err := os.Chmod(l.address, 0666); err != nil {
|
||||
return nil, fmt.Errorf("set daemon socket permissions: %w", err)
|
||||
}
|
||||
log.Warnf("daemon control socket left world-writable (0666) by --disable-strict-socket")
|
||||
return l.Listener, nil
|
||||
|
||||
case owner != "":
|
||||
// Seeded owner (flag, MDM, or persisted TOFU result): restrict before
|
||||
// serving so there is no open window.
|
||||
uid, err := lookupUser(owner)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lookup socket owner %q: %w", owner, err)
|
||||
}
|
||||
if err := restrictSocket(l.address, uid); err != nil {
|
||||
return nil, fmt.Errorf("restrict socket to %q: %w", owner, err)
|
||||
}
|
||||
return l.Listener, nil
|
||||
|
||||
default:
|
||||
// Trust-on-first-use: open the socket now; tofuListener locks it to the
|
||||
// first caller's uid on the first connection.
|
||||
if err := os.Chmod(l.address, 0666); err != nil {
|
||||
return nil, fmt.Errorf("set daemon socket permissions: %w", err)
|
||||
}
|
||||
return &tofuListener{Listener: l.Listener, path: l.address, owner: -1}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func lookupUser(username string) (int, error) {
|
||||
u, err := shell.LookupWithGetent(username)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("lookup user %s: %w", username, err)
|
||||
}
|
||||
uid, err := strconv.Atoi(u.Uid)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("parse uid %s: %w", u.Uid, err)
|
||||
}
|
||||
return uid, nil
|
||||
}
|
||||
|
||||
// addGroup creates a system group if it doesn't already exist and returns the gid.
|
||||
// Must run as root.
|
||||
func addGroup(name string) (int, error) {
|
||||
group, err := shell.LookupGroupWithGetent(name)
|
||||
if err == nil {
|
||||
gid, err := strconv.ParseInt(group.Gid, 10, 64)
|
||||
return int(gid), err
|
||||
}
|
||||
|
||||
groupadd, err := exec.LookPath("groupadd")
|
||||
if err != nil {
|
||||
// Fallback for Alpine/BusyBox systems.
|
||||
if groupadd, err = exec.LookPath("addgroup"); err != nil {
|
||||
return -1, errors.New("neither groupadd nor addgroup found")
|
||||
}
|
||||
}
|
||||
|
||||
// Use --system for a service/daemon group (no login, low GID).
|
||||
out, err := exec.Command(groupadd, "--system", name).CombinedOutput()
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("create group %q: %w: %s", name, err, out)
|
||||
}
|
||||
if group, err := shell.LookupGroupWithGetent(name); err == nil {
|
||||
gid, err := strconv.ParseInt(group.Gid, 10, 64)
|
||||
return int(gid), err
|
||||
}
|
||||
return -1, fmt.Errorf("lookup group %q: %w", name, err)
|
||||
}
|
||||
|
||||
// restrictSocket locks the unix socket down to the owner uid plus the netbird
|
||||
// group (0660). If the group cannot be created or applied, it fails closed to
|
||||
// owner-only 0600 — it never leaves the socket world-writable.
|
||||
func restrictSocket(path string, uid int) error {
|
||||
gid, err := addGroup("netbird")
|
||||
if err != nil {
|
||||
log.Errorf("create netbird group, failing closed to owner-only 0600: %v", err)
|
||||
return chownChmod(path, uid, -1, 0600)
|
||||
}
|
||||
if err := chownChmod(path, uid, gid, 0660); err != nil {
|
||||
log.Errorf("apply netbird group to socket, failing closed to owner-only 0600: %v", err)
|
||||
return chownChmod(path, uid, -1, 0600)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// chownChmod sets ownership and mode on the socket. A gid of -1 leaves the
|
||||
// group unchanged.
|
||||
func chownChmod(path string, uid, gid int, mode os.FileMode) error {
|
||||
if err := os.Chown(path, uid, gid); err != nil {
|
||||
return fmt.Errorf("chown socket %s: %w", path, err)
|
||||
}
|
||||
if err := os.Chmod(path, mode); err != nil {
|
||||
return fmt.Errorf("chmod socket %s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// tofuListener implements trust-on-first-use for the daemon control socket.
|
||||
// The socket starts world-writable; the first caller's uid (read via SO_PEERCRED)
|
||||
// becomes the owner. On that first connection the socket is restricted and the
|
||||
// owner persisted so the open window never reopens on later starts. Connections
|
||||
// that raced in during the open window and are neither the owner nor root are
|
||||
// dropped. Changing the socket mode does not disturb the already-open
|
||||
// connection, so the first caller's request is served normally.
|
||||
type tofuListener struct {
|
||||
net.Listener
|
||||
path string
|
||||
mu sync.Mutex
|
||||
owner int // -1 until claimed
|
||||
}
|
||||
|
||||
func (l *tofuListener) Accept() (net.Conn, error) {
|
||||
for {
|
||||
c, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := ipcauth.PeerIdentity(c)
|
||||
if err != nil {
|
||||
log.Errorf("read peer credentials, dropping connection: %v", err)
|
||||
_ = c.Close()
|
||||
continue
|
||||
}
|
||||
uid := int(id.UID)
|
||||
|
||||
l.mu.Lock()
|
||||
if l.owner == -1 {
|
||||
if err := restrictSocket(l.path, uid); err != nil {
|
||||
l.mu.Unlock()
|
||||
_ = c.Close()
|
||||
// Refuse to serve on a socket we could not lock down.
|
||||
return nil, fmt.Errorf("restrict socket on first connection: %w", err)
|
||||
}
|
||||
l.owner = uid
|
||||
persistSocketOwner(uid)
|
||||
log.Infof("control socket restricted to first caller (uid %d)", uid)
|
||||
l.mu.Unlock()
|
||||
return c, nil
|
||||
}
|
||||
owner := l.owner
|
||||
l.mu.Unlock()
|
||||
|
||||
// New connects are already gated by the 0660 perms set above; this only
|
||||
// drops anything that slipped in during the brief open window.
|
||||
if uid != owner && uid != 0 {
|
||||
log.Warnf("dropping non-owner connection (uid %d) during socket bootstrap", uid)
|
||||
_ = c.Close()
|
||||
continue
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
// effectiveSocketOwner returns the configured socket owner: the --socket-owner
|
||||
// flag when set, otherwise the owner persisted by a previous TOFU migration.
|
||||
func effectiveSocketOwner() string {
|
||||
if socketOwner != "" {
|
||||
return socketOwner
|
||||
}
|
||||
params, err := loadServiceParams()
|
||||
if err != nil {
|
||||
log.Errorf("load service params for socket owner: %v", err)
|
||||
return ""
|
||||
}
|
||||
if params != nil {
|
||||
return params.SocketOwner
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// persistSocketOwner records the TOFU-selected owner (by username) so the next
|
||||
// daemon start restricts the socket immediately, with no open window.
|
||||
func persistSocketOwner(uid int) {
|
||||
u, err := user.LookupId(strconv.Itoa(uid))
|
||||
if err != nil {
|
||||
log.Errorf("resolve uid %d to username for persistence: %v", uid, err)
|
||||
return
|
||||
}
|
||||
params, err := loadServiceParams()
|
||||
if err != nil {
|
||||
log.Errorf("load service params to persist socket owner: %v", err)
|
||||
return
|
||||
}
|
||||
if params == nil {
|
||||
params = currentServiceParams()
|
||||
}
|
||||
params.SocketOwner = u.Username
|
||||
if err := saveServiceParams(params); err != nil {
|
||||
log.Errorf("persist socket owner: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user