mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-25 13:59:08 +01:00
Read user certificates only from the session of the active profile's owner
This commit is contained in:
@@ -2,6 +2,7 @@ package certproof
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -10,11 +11,14 @@ import (
|
||||
|
||||
const (
|
||||
noActiveSession = 0xFFFFFFFF
|
||||
servicesSession = 0
|
||||
|
||||
// wtsCurrentServer is WTS_CURRENT_SERVER_HANDLE and wtsActive is WTSActive of
|
||||
// WTS_CONNECTSTATE_CLASS. Neither is exported by x/sys/windows.
|
||||
// wtsCurrentServer is WTS_CURRENT_SERVER_HANDLE; wtsActive and wtsDisconnected are
|
||||
// WTSActive and WTSDisconnected of WTS_CONNECTSTATE_CLASS. None is exported by
|
||||
// x/sys/windows.
|
||||
wtsCurrentServer = windows.Handle(0)
|
||||
wtsActive = 0
|
||||
wtsDisconnected = 4
|
||||
)
|
||||
|
||||
// DesktopUser is an interactive session and the account signed into it. The user's
|
||||
@@ -33,37 +37,91 @@ func (u DesktopUser) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
// CurrentDesktopUser returns a token for the interactive user whose certificate store
|
||||
// should be asked. The physical console comes first, and an active remote desktop
|
||||
// session is used when nobody is at the console, which is how servers and VDI hosts are
|
||||
// normally reached. The second return is false at the sign-in screen, where no
|
||||
// interactive session exists and only machine certificates can be proven.
|
||||
// CurrentDesktopUser returns a token for the session whose certificate store should be
|
||||
// asked: a session of owner, the account the active profile belongs to, with the
|
||||
// physical console preferred over remote sessions. With no owner only the console user
|
||||
// counts. Picking any signed-in user instead would let whoever else is logged in to a
|
||||
// terminal server or VDI host decide the result. The second return is false when no
|
||||
// such session exists, and only machine certificates can then be proven.
|
||||
//
|
||||
// Obtaining the token needs SE_TCB_NAME, which the LocalSystem service has and an
|
||||
// ordinary process does not.
|
||||
func CurrentDesktopUser() (DesktopUser, bool) {
|
||||
if session := windows.WTSGetActiveConsoleSessionId(); session != noActiveSession {
|
||||
if user, ok := desktopUser(session); ok {
|
||||
return user, true
|
||||
}
|
||||
log.Debugf("console session %d has nobody signed in, looking for an active remote session", session)
|
||||
func CurrentDesktopUser(owner string) (DesktopUser, bool) {
|
||||
console := windows.WTSGetActiveConsoleSessionId()
|
||||
if owner == "" {
|
||||
return consoleUser(console)
|
||||
}
|
||||
|
||||
sessions, err := activeSessions()
|
||||
match, err := ownerMatcher(owner)
|
||||
if err != nil {
|
||||
log.Debugf("certificate posture: %v", err)
|
||||
return DesktopUser{}, false
|
||||
}
|
||||
|
||||
sessions, err := userSessions(console)
|
||||
if err != nil {
|
||||
log.Debugf("cannot enumerate terminal sessions: %v", err)
|
||||
return DesktopUser{}, false
|
||||
}
|
||||
for _, session := range sessions {
|
||||
if user, ok := desktopUser(session); ok {
|
||||
user, ok := desktopUser(session)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if match(user) {
|
||||
return user, true
|
||||
}
|
||||
user.Close()
|
||||
}
|
||||
|
||||
log.Debug("no interactive session is signed in, no user certificate store is reachable")
|
||||
log.Debugf("certificate posture: profile owner %s has no signed-in session, no user certificate store is reachable", owner)
|
||||
return DesktopUser{}, false
|
||||
}
|
||||
|
||||
func consoleUser(console uint32) (DesktopUser, bool) {
|
||||
if console == noActiveSession || console == servicesSession {
|
||||
log.Debug("no console session, no user certificate store is reachable")
|
||||
return DesktopUser{}, false
|
||||
}
|
||||
user, ok := desktopUser(console)
|
||||
if !ok {
|
||||
log.Debugf("console session %d has nobody signed in, no user certificate store is reachable", console)
|
||||
}
|
||||
return user, ok
|
||||
}
|
||||
|
||||
// ownerMatcher reports whether a session user is owner. Accounts are compared by SID,
|
||||
// which is what identifies a Windows account; the name comparison is a fallback for an
|
||||
// owner name that no longer resolves, and is case-insensitive like Windows account names.
|
||||
func ownerMatcher(owner string) (func(DesktopUser) bool, error) {
|
||||
ownerSID, _, _, err := windows.LookupSID("", owner)
|
||||
if err != nil {
|
||||
log.Debugf("certificate posture: resolving profile owner %s: %v, matching by name", owner, err)
|
||||
return func(user DesktopUser) bool { return sameAccountName(user.Name, owner) }, nil
|
||||
}
|
||||
return func(user DesktopUser) bool {
|
||||
tokenUser, err := user.Token.GetTokenUser()
|
||||
if err != nil {
|
||||
log.Debugf("failed reading token user of session %d: %v", user.Session, err)
|
||||
return false
|
||||
}
|
||||
return tokenUser.User.Sid.Equals(ownerSID)
|
||||
}, nil
|
||||
}
|
||||
|
||||
// sameAccountName compares DOMAIN\account names case-insensitively, and an owner given
|
||||
// without a domain against the account part alone.
|
||||
func sameAccountName(sessionName, owner string) bool {
|
||||
if strings.EqualFold(sessionName, owner) {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(owner, `\`) {
|
||||
return false
|
||||
}
|
||||
_, account, found := strings.Cut(sessionName, `\`)
|
||||
return found && strings.EqualFold(account, owner)
|
||||
}
|
||||
|
||||
func desktopUser(session uint32) (DesktopUser, bool) {
|
||||
var token windows.Token
|
||||
if err := windows.WTSQueryUserToken(session, &token); err != nil {
|
||||
@@ -97,7 +155,10 @@ func tokenAccount(token windows.Token) (string, error) {
|
||||
return domain + `\` + account, nil
|
||||
}
|
||||
|
||||
func activeSessions() ([]uint32, error) {
|
||||
// userSessions lists the sessions a user can be signed in to, console first, then
|
||||
// active remote sessions, then disconnected ones, whose user is still signed in. Session
|
||||
// 0 is skipped: it hosts services and never belongs to an interactive user.
|
||||
func userSessions(console uint32) ([]uint32, error) {
|
||||
var info *windows.WTS_SESSION_INFO
|
||||
var count uint32
|
||||
if err := windows.WTSEnumerateSessions(wtsCurrentServer, 0, 1, &info, &count); err != nil {
|
||||
@@ -105,13 +166,19 @@ func activeSessions() ([]uint32, error) {
|
||||
}
|
||||
defer windows.WTSFreeMemory(uintptr(unsafe.Pointer(info)))
|
||||
|
||||
var active []uint32
|
||||
for _, session := range unsafe.Slice(info, count) {
|
||||
if session.State == wtsActive {
|
||||
active = append(active, session.SessionID)
|
||||
var sessions []uint32
|
||||
if console != noActiveSession && console != servicesSession {
|
||||
sessions = append(sessions, console)
|
||||
}
|
||||
for _, state := range []uint32{wtsActive, wtsDisconnected} {
|
||||
for _, session := range unsafe.Slice(info, count) {
|
||||
if session.SessionID == servicesSession || session.SessionID == console || session.State != state {
|
||||
continue
|
||||
}
|
||||
sessions = append(sessions, session.SessionID)
|
||||
}
|
||||
}
|
||||
return active, nil
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
// runningAsLocalSystem reports whether this process is the service. The helper runs as
|
||||
|
||||
Reference in New Issue
Block a user