[client] Profile ownership console user tofu (#7529)

* 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
This commit is contained in:
Theodor Midtlien
2026-09-17 11:20:13 +02:00
committed by GitHub
parent 52b16e7a5c
commit 90052cbefb
12 changed files with 712 additions and 13 deletions
+71 -6
View File
@@ -291,24 +291,30 @@ func TestListProfiles_PrivilegedResolvesUnfiltered(t *testing.T) {
})
}
func TestListProfiles_OnlyTheDefaultFailsOpenWhenUnowned(t *testing.T) {
func TestListProfiles_UnownedProfilesArePrivilegedOnly(t *testing.T) {
withTestSM(t, func(sm *ServiceManager, _ ipcauth.Identity) {
// Nobody at the console, so the claim cannot stamp an owner partway
// through and change what the assertions below are looking at, whatever
// the machine running the test happens to look like.
stubConsoleUser(t, false)
unowned, err := sm.AddProfile("unowned", nil)
require.NoError(t, err)
alice := ipcauth.KnownForTest(ipcauth.Identity{UID: 4242})
got, err := sm.ListProfiles(alice)
require.NoError(t, err)
assert.Contains(t, profileIDs(got), defaultProfileName,
"a fresh install has to be usable before anything is claimed")
assert.NotContains(t, profileIDs(got), defaultProfileName,
"the default profile has no exemption, being claimed is what opens it")
assert.NotContains(t, profileIDs(got), unowned.ID.String(),
"every other profile needs an owner before anyone can address it")
"every profile needs an owner before anyone can address it")
root := ipcauth.KnownForTest(ipcauth.Identity{UID: 0})
got, err = sm.ListProfiles(root)
require.NoError(t, err)
assert.Contains(t, profileIDs(got), unowned.ID.String(),
"root still reaches it, which is how it gets assigned")
assert.Contains(t, profileIDs(got), defaultProfileName,
"root still reaches both, which is how an unowned profile gets assigned")
assert.Contains(t, profileIDs(got), unowned.ID.String())
nobody, err := sm.ListProfiles(ipcauth.Identity{})
require.NoError(t, err)
@@ -689,6 +695,27 @@ func TestListProfiles_ClaimKeepsFieldsThisVersionDoesNotModel(t *testing.T) {
})
}
// stubConsoleUser replaces the console lookup, so the default-profile claim can
// be exercised without the machine running the test having a seat of its own.
func stubConsoleUser(t *testing.T, atConsole bool) {
t.Helper()
orig := isConsoleUser
isConsoleUser = func(ipcauth.Identity) bool { return atConsole }
t.Cleanup(func() { isConsoleUser = orig })
}
func TestClaimDefaultProfile_ConsoleUserClaimsIt(t *testing.T) {
withLegacyLayout(t, func(sm *ServiceManager, _ string) {
stubConsoleUser(t, true)
alice := ipcauth.KnownForTest(ipcauth.Identity{UID: 4242})
_, err := sm.ListProfiles(alice)
require.NoError(t, err)
assert.Equal(t, []string{"uid:4242"}, readOwners(t, DefaultConfigPath),
"the first caller at the console closes the window the default profile is open in")
})
}
func TestSetProfileField_ReplacesAKeySpelledInAnotherCase(t *testing.T) {
withLegacyLayout(t, func(sm *ServiceManager, configDir string) {
path := writeLegacyProfile(t, configDir, "alice", "work", map[string]any{
@@ -760,3 +787,41 @@ func TestSetProfileField_KeepsKeysItWasNotAskedToWrite(t *testing.T) {
assert.Len(t, doc, len(unknown)+3, "with nothing else added")
})
}
func TestClaimDefaultProfile_CallerAwayFromTheConsoleDoesNotClaimIt(t *testing.T) {
withLegacyLayout(t, func(sm *ServiceManager, _ string) {
stubConsoleUser(t, false)
alice := ipcauth.KnownForTest(ipcauth.Identity{UID: 4242})
_, err := sm.ListProfiles(alice)
require.NoError(t, err)
assert.Empty(t, readOwners(t, DefaultConfigPath),
"a local caller who is not at the console must not take the machine's profile")
})
}
func TestClaimDefaultProfile_DisableEnvWithholdsTheClaim(t *testing.T) {
withLegacyLayout(t, func(sm *ServiceManager, _ string) {
stubConsoleUser(t, true)
t.Setenv(EnvDisableDefaultProfileClaim, "true")
alice := ipcauth.KnownForTest(ipcauth.Identity{UID: 4242})
_, err := sm.ListProfiles(alice)
require.NoError(t, err)
assert.Empty(t, readOwners(t, DefaultConfigPath),
"the flag withholds the claim even from a caller who would otherwise get it")
})
}
func TestClaimDefaultProfile_UnparseableDisableEnvLeavesTheClaimOn(t *testing.T) {
withLegacyLayout(t, func(sm *ServiceManager, _ string) {
stubConsoleUser(t, true)
t.Setenv(EnvDisableDefaultProfileClaim, "yes please")
alice := ipcauth.KnownForTest(ipcauth.Identity{UID: 4242})
_, err := sm.ListProfiles(alice)
require.NoError(t, err)
assert.Equal(t, []string{"uid:4242"}, readOwners(t, DefaultConfigPath),
"a typo must not be what turns a safety mechanism off")
})
}