mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-18 04:39:06 +02:00
* Add consoleuser and stamp default profile on known username in migration * Refactor consoleuser to verify Id, fix seats on linux and default stamp * Add default profile claim * Add disable auto-claim of default profile and always fail close * Add disable auto-claim flag to migration * Adding timeout to console user on Linux and close library load on darwin * Fixed failed close test * Close both Dlopen for darwin * Replace RegisterFunc with purego.Dlsym to avoid possible panic * Fix freebsd tty enumeration * Fix active profile migration logic and add test * Log defaultClaimDisabled error once * Guard against panicking console user lookup. * Fix merge conflict * Fix broken tests
174 lines
5.5 KiB
Go
174 lines
5.5 KiB
Go
package ipcauth
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/godbus/dbus/v5"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
loginDest = "org.freedesktop.login1"
|
|
loginPath = dbus.ObjectPath("/org/freedesktop/login1")
|
|
loginInterface = "org.freedesktop.login1.Manager"
|
|
listSeats = loginInterface + ".ListSeats"
|
|
|
|
seatInterface = "org.freedesktop.login1.Seat"
|
|
seatActiveSession = seatInterface + ".ActiveSession"
|
|
|
|
sessionInterface = "org.freedesktop.login1.Session"
|
|
sessionActive = sessionInterface + ".Active"
|
|
sessionClass = sessionInterface + ".Class"
|
|
sessionRemote = sessionInterface + ".Remote"
|
|
sessionUser = sessionInterface + ".User"
|
|
|
|
// propertiesGet is the standard property reader. godbus offers no
|
|
// context-aware GetProperty, and GetProperty is only this call underneath,
|
|
// so a bounded read has to make it directly.
|
|
propertiesGet = "org.freedesktop.DBus.Properties.Get"
|
|
|
|
// nullObjectPath is what logind puts in an object path field that refers to
|
|
// nothing, a seat with no session in the foreground being the one that
|
|
// matters here.
|
|
nullObjectPath = dbus.ObjectPath("/")
|
|
|
|
// consoleLookupTimeout bounds the whole seat walk, not each call in it, so
|
|
// a machine with several seats cannot multiply what an unanswering bus
|
|
// costs. A healthy lookup is well under a millisecond.
|
|
consoleLookupTimeout = 1 * time.Second
|
|
)
|
|
|
|
// isConsoleUser reports whether id holds the foreground session of one of this
|
|
// machine's seats.
|
|
//
|
|
// No seats means no console, which is the honest answer for a headless machine
|
|
// and the one that keeps its profiles to the privileged caller.
|
|
func isConsoleUser(id Identity) bool {
|
|
// A SID belongs to a Windows principal and has no uid to compare. Nothing
|
|
// on Linux produces one, and comparing anyway would match uid 0.
|
|
if id.IsWindows() {
|
|
return false
|
|
}
|
|
|
|
conn, err := dbus.SystemBus()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), consoleLookupTimeout)
|
|
defer cancel()
|
|
|
|
// ListSeats returns a(so): seat id and object path.
|
|
var seats []struct {
|
|
ID string
|
|
Path dbus.ObjectPath
|
|
}
|
|
if err := conn.Object(loginDest, loginPath).CallWithContext(ctx, listSeats, 0).Store(&seats); err != nil {
|
|
log.Debugf("cannot list seats, treating the caller as not at the console: %v", err)
|
|
return false
|
|
}
|
|
|
|
for _, seat := range seats {
|
|
uid, ok := consoleSessionUID(ctx, conn, seat.Path)
|
|
if ok && uid == id.UID {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// consoleSessionUID returns who holds a seat's foreground session, and false
|
|
// unless that session is a person logged in locally.
|
|
func consoleSessionUID(ctx context.Context, conn *dbus.Conn, seatPath dbus.ObjectPath) (uint32, bool) {
|
|
// ActiveSession is (so): session id and object path. It names nothing on a
|
|
// seat whose VT has been switched away from, and on one whose display
|
|
// manager has not started a session yet.
|
|
prop, err := property(ctx, conn.Object(loginDest, seatPath), seatActiveSession)
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
var active struct {
|
|
ID string
|
|
Path dbus.ObjectPath
|
|
}
|
|
if err := prop.Store(&active); err != nil {
|
|
return 0, false
|
|
}
|
|
if active.ID == "" || active.Path == "" || active.Path == nullObjectPath {
|
|
return 0, false
|
|
}
|
|
|
|
session := conn.Object(loginDest, active.Path)
|
|
|
|
// Only "user" sessions count: a greeter or a lock screen is the display
|
|
// manager sitting at the seat, not somebody to hand a profile to.
|
|
if class, ok := stringProperty(ctx, session, sessionClass); !ok || class != "user" {
|
|
return 0, false
|
|
}
|
|
|
|
// A remote session can hold a seat, and unlike the session's class and
|
|
// type, which pam_systemd takes from the environment of whoever opened the
|
|
// session, remoteness is set by the thing that accepted the connection. So
|
|
// it is worth asking even once the seat is established.
|
|
if remote, ok := boolProperty(ctx, session, sessionRemote); !ok || remote {
|
|
return 0, false
|
|
}
|
|
|
|
// Implied by the seat having named this session, and kept as a cross-check
|
|
// against a foreground that moved between the two calls.
|
|
if isActive, ok := boolProperty(ctx, session, sessionActive); !ok || !isActive {
|
|
return 0, false
|
|
}
|
|
|
|
// User is (uo): uid and the user object's path. Read from the session
|
|
// object rather than carried over from a listing, so the uid returned and
|
|
// the checks above are known to describe the same session.
|
|
prop, err = property(ctx, session, sessionUser)
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
var owner struct {
|
|
UID uint32
|
|
Path dbus.ObjectPath
|
|
}
|
|
if err := prop.Store(&owner); err != nil {
|
|
return 0, false
|
|
}
|
|
|
|
return owner.UID, true
|
|
}
|
|
|
|
// property reads one property under ctx, name being the interface.member form
|
|
// godbus takes. It is what GetProperty does, with a deadline the caller owns.
|
|
func property(ctx context.Context, obj dbus.BusObject, name string) (dbus.Variant, error) {
|
|
idx := strings.LastIndex(name, ".")
|
|
if idx < 0 || idx+1 == len(name) {
|
|
return dbus.Variant{}, fmt.Errorf("invalid property name %q", name)
|
|
}
|
|
var v dbus.Variant
|
|
err := obj.CallWithContext(ctx, propertiesGet, 0, name[:idx], name[idx+1:]).Store(&v)
|
|
return v, err
|
|
}
|
|
|
|
func stringProperty(ctx context.Context, obj dbus.BusObject, name string) (string, bool) {
|
|
prop, err := property(ctx, obj, name)
|
|
if err != nil {
|
|
return "", false
|
|
}
|
|
s, ok := prop.Value().(string)
|
|
return s, ok
|
|
}
|
|
|
|
func boolProperty(ctx context.Context, obj dbus.BusObject, name string) (bool, bool) {
|
|
prop, err := property(ctx, obj, name)
|
|
if err != nil {
|
|
return false, false
|
|
}
|
|
b, ok := prop.Value().(bool)
|
|
return b, ok
|
|
}
|