mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-29 18:19:07 +02:00
Add an optional --allow-group flag restricting the daemon sockets (#7478)
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/daemonaddr"
|
||||
"github.com/netbirdio/netbird/client/internal/ipcauth"
|
||||
"github.com/netbirdio/netbird/client/mdm"
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
"github.com/netbirdio/netbird/client/server"
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
@@ -64,9 +65,6 @@ func (p *program) Start(svc service.Service) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Collect static system and platform information
|
||||
system.UpdateStaticInfoAsync()
|
||||
|
||||
// A daemon installed before named-pipe support has the loopback TCP address
|
||||
// persisted. Move it to the named pipe so an upgraded daemon can identify
|
||||
// its callers instead of silently serving an unauthenticated socket.
|
||||
@@ -89,11 +87,22 @@ func (p *program) Start(svc service.Service) error {
|
||||
)
|
||||
p.serv = grpc.NewServer(opts...)
|
||||
|
||||
daemonListener, jsonListener, err := listenDaemonSockets()
|
||||
daemonListener, jsonListener, err := p.listenRestricted()
|
||||
if err != nil {
|
||||
// Logged as well as returned: the service manager is the only other
|
||||
// place this surfaces, and it reports a service that will not start
|
||||
// without saying why. Refusing to serve on a host whose lockdown
|
||||
// cannot be applied is deliberate, so the reason has to be findable.
|
||||
log.Errorf("failed to apply the daemon socket restriction, not serving: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Started only once the sockets exist. Binding them adjusts the process
|
||||
// umask for the length of the bind, and a goroutine creating a file in that
|
||||
// window would inherit it, so nothing asynchronous may be in flight before
|
||||
// this point. Keep any future background work below the listeners too.
|
||||
system.UpdateStaticInfoAsync()
|
||||
|
||||
go func() {
|
||||
// Fatal here rather than inside serve, so serve's deferred listener
|
||||
// closes run before the process exits.
|
||||
@@ -104,12 +113,73 @@ func (p *program) Start(svc service.Service) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// listenRestricted opens the daemon sockets and applies the configured access
|
||||
// restriction to them before returning, so no caller can reach a socket that is
|
||||
// still open to everybody. Both listeners are closed again if the restriction
|
||||
// cannot be applied, and the error is returned rather than handled later, so the
|
||||
// service manager sees a start that failed instead of one that succeeded and
|
||||
// then died.
|
||||
//
|
||||
// An unreadable MDM source is an error here for the same reason a bad value is:
|
||||
// on a managed host it may be carrying the restriction, and treating it as
|
||||
// absent would serve every local account instead.
|
||||
func (p *program) listenRestricted() (*socketListener, *socketListener, error) {
|
||||
// A nil fetcher leaves the platform-native source authoritative, which is
|
||||
// what a desktop daemon wants. This runs before the Server exists, so it
|
||||
// cannot borrow the Loader the Server owns.
|
||||
policy, err := mdm.NewLoader(nil).LoadWithError()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
allowed, source, err := daemonSocketPrincipals(policy)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(allowed) > 0 {
|
||||
log.Infof("daemon sockets are restricted to %v by %s", allowed, source)
|
||||
}
|
||||
|
||||
daemonListener, jsonListener, err := listenDaemonSockets(allowed)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := restrictListeners(daemonListener, jsonListener, allowed); err != nil {
|
||||
closeListeners(daemonListener, jsonListener)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return daemonListener, jsonListener, nil
|
||||
}
|
||||
|
||||
// restrictListeners applies the access restriction to both sockets. restrict is
|
||||
// a no-op for a nil listener, which is what a disabled JSON socket is.
|
||||
func restrictListeners(daemonListener, jsonListener *socketListener, allowed []string) error {
|
||||
if err := daemonListener.restrict("daemon", allowed); err != nil {
|
||||
return err
|
||||
}
|
||||
return jsonListener.restrict("daemon JSON", allowed)
|
||||
}
|
||||
|
||||
func closeListeners(listeners ...*socketListener) {
|
||||
for _, l := range listeners {
|
||||
if l == nil {
|
||||
continue
|
||||
}
|
||||
if err := l.Close(); err != nil {
|
||||
log.Debugf("close daemon listener: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// listenDaemonSockets opens the daemon control socket and, when it is enabled, the
|
||||
// JSON gateway socket. The control socket is closed again if the second one fails,
|
||||
// so a failed start leaves nothing listening. The returned JSON listener is nil
|
||||
// when the socket is disabled.
|
||||
func listenDaemonSockets() (*socketListener, *socketListener, error) {
|
||||
daemonListener, err := listenOnAddress(daemonAddr)
|
||||
// when the socket is disabled. allowed holds the resolved --allow-group
|
||||
// principals, empty when both sockets are left open to every local account.
|
||||
func listenDaemonSockets(allowed []string) (*socketListener, *socketListener, error) {
|
||||
daemonListener, err := listenOnAddress(daemonAddr, allowed)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("listen daemon interface: %w", err)
|
||||
}
|
||||
@@ -119,7 +189,7 @@ func listenDaemonSockets() (*socketListener, *socketListener, error) {
|
||||
return daemonListener, nil, nil
|
||||
}
|
||||
|
||||
jsonListener, err := listenOnAddress(jsonSocket)
|
||||
jsonListener, err := listenOnAddress(jsonSocket, allowed)
|
||||
if err != nil {
|
||||
if cerr := daemonListener.Close(); cerr != nil {
|
||||
log.Debugf("close daemon listener: %v", cerr)
|
||||
@@ -130,26 +200,17 @@ func listenDaemonSockets() (*socketListener, *socketListener, error) {
|
||||
return daemonListener, jsonListener, nil
|
||||
}
|
||||
|
||||
// serve brings up the daemon server on an already-open control socket and blocks
|
||||
// until it stops. jsonListener is nil when the JSON socket is disabled. A returned
|
||||
// error means the daemon cannot run at all and the caller is expected to exit; the
|
||||
// failures it recovers from on its own are logged here.
|
||||
// serve brings up the daemon server on listeners that are already open and
|
||||
// already restricted, and blocks until it stops. jsonListener is nil when the
|
||||
// JSON socket is disabled. A returned error means the daemon cannot run at all
|
||||
// and the caller is expected to exit; the failures it recovers from on its own
|
||||
// are logged here.
|
||||
func (p *program) serve(daemonListener, jsonListener *socketListener) error {
|
||||
defer daemonListener.Close()
|
||||
if jsonListener != nil {
|
||||
defer jsonListener.Close()
|
||||
}
|
||||
|
||||
// chmodUnixSocket is a no-op for a nil listener and for a non-unix one.
|
||||
if err := daemonListener.chmodUnixSocket("daemon"); err != nil {
|
||||
log.Error(err)
|
||||
return nil
|
||||
}
|
||||
if err := jsonListener.chmodUnixSocket("daemon JSON"); err != nil {
|
||||
log.Error(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
serverInstance := server.New(p.ctx, util.FindFirstLogPath(logFiles), configPath, profilesDisabled, updateSettingsDisabled, captureEnabled, networksDisabled)
|
||||
p.authzGate.SetState(serverInstance)
|
||||
if err := serverInstance.Start(); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user