Additions and fixes for app_sso_platform table (#41048)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #40630 

# Checklist for submitter
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

## fleetd/orbit/Fleet Desktop

- [x] Verified compatibility with the latest released version of Fleet
(see [Must
rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/workflows/fleetd-development-and-release-strategy.md))
- [x] If the change applies to only one platform, confirmed that
`runtime.GOOS` is used as needed to isolate changes
- [x] Verified that fleetd runs on macOS, Linux and Windows (macOS only)
- [ ] Verified auto-update works from the released version of component
to the new version (see [tools/tuf/test](../tools/tuf/test/README.md))
(should not affect updates)
This commit is contained in:
Zach Wasserman 2026-03-06 08:07:20 -08:00 committed by GitHub
parent 97a1abef82
commit 322895c787
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 150 additions and 34 deletions

View file

@ -0,0 +1,2 @@
- Fix a case where `app_sso_platform` does not return any data if Platform SSO is configured but the user has not yet completed registration.
- Add `registration_completed` and `login_type` columns to `app_sso_platform` table.

View file

@ -32,6 +32,10 @@ func Columns() []table.ColumnDefinition {
table.TextColumn("device_id"),
// User principal name of the user that logged in via Platform SSO.
table.TextColumn("user_principal_name"),
// Whether Platform SSO registration is completed. Extracted from "Device Configuration" -> "registrationCompleted" (0 = false, 1 = true).
table.IntegerColumn("registration_completed"),
// Login type extracted from "Device Configuration" -> "loginType" (e.g. "POLoginTypeUserSecureEnclaveKey (2)").
table.TextColumn("login_type"),
}
}
@ -104,11 +108,18 @@ func Generate(ctx context.Context, queryContext table.QueryContext) ([]map[strin
return nil, nil
}
registrationCompleted := "0"
if appSSOPlatform.registrationCompleted {
registrationCompleted = "1"
}
return []map[string]string{{
"extension_identifier": appSSOPlatform.extensionIdentifier,
"realm": appSSOPlatform.realm,
"device_id": appSSOPlatform.deviceID,
"user_principal_name": appSSOPlatform.userPrincipalName,
"extension_identifier": appSSOPlatform.extensionIdentifier,
"realm": appSSOPlatform.realm,
"registration_completed": registrationCompleted,
"device_id": appSSOPlatform.deviceID,
"user_principal_name": appSSOPlatform.userPrincipalName,
"login_type": appSSOPlatform.loginType,
}}, nil
}
@ -150,10 +161,12 @@ func extractJSONSections(s []byte) (deviceConfig string, userConfig string, err
}
type appSSOPlatformData struct {
extensionIdentifier string
deviceID string
realm string
userPrincipalName string
extensionIdentifier string
deviceID string
registrationCompleted bool
loginType string
realm string
userPrincipalName string
}
func parseAppSSOPlatformCommandOutput(output []byte, expectedExtensionIdentifier string, expectedRealm string) (*appSSOPlatformData, error) {
@ -168,6 +181,8 @@ func parseAppSSOPlatformCommandOutput(output []byte, expectedExtensionIdentifier
deviceConfig := struct {
DeviceSigningCertificate string `json:"deviceSigningCertificate"`
ExtensionIdentifier string `json:"extensionIdentifier"`
RegistrationCompleted bool `json:"registrationCompleted"`
LoginType string `json:"loginType"`
}{}
if err := json.Unmarshal([]byte(deviceConfigJSON), &deviceConfig); err != nil {
return nil, fmt.Errorf("could not unmarshal \"Device Configuration\" JSON: %w", err)
@ -176,19 +191,20 @@ func parseAppSSOPlatformCommandOutput(output []byte, expectedExtensionIdentifier
log.Debug().Str("extensionIdentifier", deviceConfig.ExtensionIdentifier).Msg("device registered, but found unmatched extension")
return nil, nil
}
dsc, err := base64.RawURLEncoding.DecodeString(deviceConfig.DeviceSigningCertificate)
if err != nil {
return nil, fmt.Errorf("failed to decode \"deviceSigningCertificate\": %w", err)
}
deviceSigningCertificate, err := x509.ParseCertificate(dsc)
if err != nil {
return nil, fmt.Errorf("failed to parse \"deviceSigningCertificate\": %w", err)
}
if deviceSigningCertificate.Subject.CommonName == "" {
return nil, errors.New("empty subject common name in \"deviceSigningCertificate\"")
var deviceID string
if deviceConfig.DeviceSigningCertificate != "" {
dsc, err := base64.RawURLEncoding.DecodeString(deviceConfig.DeviceSigningCertificate)
if err != nil {
return nil, fmt.Errorf("failed to decode \"deviceSigningCertificate\": %w", err)
}
deviceSigningCertificate, err := x509.ParseCertificate(dsc)
if err != nil {
return nil, fmt.Errorf("failed to parse \"deviceSigningCertificate\": %w", err)
}
deviceID = deviceSigningCertificate.Subject.CommonName
}
log.Debug().Str(
"\"Device Configuration\"", deviceSigningCertificate.Subject.CommonName,
"\"Device Configuration\"", deviceID,
).Msg("found device ID")
userConfig := struct {
KerberosStatus []map[string]any `json:"kerberosStatus"`
@ -196,10 +212,12 @@ func parseAppSSOPlatformCommandOutput(output []byte, expectedExtensionIdentifier
if userConfigJSON == "(null)" {
log.Debug().Msg("user not registered")
return &appSSOPlatformData{
extensionIdentifier: deviceConfig.ExtensionIdentifier,
deviceID: deviceSigningCertificate.Subject.CommonName,
realm: expectedRealm,
userPrincipalName: "",
extensionIdentifier: deviceConfig.ExtensionIdentifier,
deviceID: deviceID,
registrationCompleted: deviceConfig.RegistrationCompleted,
loginType: deviceConfig.LoginType,
realm: expectedRealm,
userPrincipalName: "",
}, nil
}
if err := json.Unmarshal([]byte(userConfigJSON), &userConfig); err != nil {
@ -230,10 +248,12 @@ func parseAppSSOPlatformCommandOutput(output []byte, expectedExtensionIdentifier
if expectedRealm != realm {
log.Debug().Str("realm", realm).Msg("user registered, but found unmatched realm")
return &appSSOPlatformData{
extensionIdentifier: deviceConfig.ExtensionIdentifier,
deviceID: deviceSigningCertificate.Subject.CommonName,
realm: expectedRealm,
userPrincipalName: "",
extensionIdentifier: deviceConfig.ExtensionIdentifier,
deviceID: deviceID,
registrationCompleted: deviceConfig.RegistrationCompleted,
loginType: deviceConfig.LoginType,
realm: expectedRealm,
userPrincipalName: "",
}, nil
}
suffix := fmt.Sprintf("@%s", realm)
@ -242,16 +262,18 @@ func parseAppSSOPlatformCommandOutput(output []byte, expectedExtensionIdentifier
log.Debug().Str(
"extension_identifier", deviceConfig.ExtensionIdentifier,
).Str(
"device_id", deviceSigningCertificate.Subject.CommonName,
"device_id", deviceID,
).Str(
"realm", realm,
).Str(
"user_principal_name", upn,
).Msg("device and user found")
return &appSSOPlatformData{
extensionIdentifier: deviceConfig.ExtensionIdentifier,
deviceID: deviceSigningCertificate.Subject.CommonName,
realm: realm,
userPrincipalName: upn,
extensionIdentifier: deviceConfig.ExtensionIdentifier,
deviceID: deviceID,
registrationCompleted: deviceConfig.RegistrationCompleted,
loginType: deviceConfig.LoginType,
realm: realm,
userPrincipalName: upn,
}, nil
}

View file

@ -24,6 +24,9 @@ var (
//go:embed testdata/app_sso_platform_state_empty_kerberos_status.txt
noKerberosStatus string
//go:embed testdata/app_sso_platform_state_configured_not_registered.txt
configuredNotRegistered string
)
func TestParseAppSSOPlatformCommandOutput(t *testing.T) {
@ -35,13 +38,15 @@ func TestParseAppSSOPlatformCommandOutput(t *testing.T) {
require.Equal(t, "com.microsoft.CompanyPortalMac.ssoextension", data.extensionIdentifier)
require.Equal(t, "KERBEROS.MICROSOFTONLINE.COM", data.realm)
require.Equal(t, "foobar@contoso.onmicrosoft.com", data.userPrincipalName)
require.True(t, data.registrationCompleted)
require.Equal(t, "POLoginTypeUserSecureEnclaveKey (2)", data.loginType)
// Empty, Platform SSO not set yet.
// Platform SSO not set yet.
data, err = parseAppSSOPlatformCommandOutput([]byte(empty), "com.microsoft.CompanyPortalMac.ssoextension", "KERBEROS.MICROSOFTONLINE.COM")
require.NoError(t, err)
require.Nil(t, data)
// No, kerberos status - this could happen if user removes the SSO account via Company portal
// No kerberos status - this could happen if user removes the SSO account via Company portal
data, err = parseAppSSOPlatformCommandOutput([]byte(noKerberosStatus), "com.microsoft.CompanyPortalMac.ssoextension", "KERBEROS.MICROSOFTONLINE.COM")
require.NoError(t, err)
require.Nil(t, data)
@ -59,12 +64,25 @@ func TestParseAppSSOPlatformCommandOutput(t *testing.T) {
require.Equal(t, "com.microsoft.CompanyPortalMac.ssoextension", data.extensionIdentifier)
require.Equal(t, "FOOBAR.OTHER.COM", data.realm)
require.Equal(t, "", data.userPrincipalName)
require.Equal(t, "POLoginTypeUserSecureEnclaveKey (2)", data.loginType)
// None matches.
data, err = parseAppSSOPlatformCommandOutput([]byte(sample1), "com.microsoft.Other.other", "FOOBAR.OTHER.COM")
require.NoError(t, err)
require.Nil(t, data)
// Platform SSO is configured (device configuration present) but registration not completed and user configuration is null.
// This can happen when the configuration profile is deployed but the user hasn't registered with SSO yet.
data, err = parseAppSSOPlatformCommandOutput([]byte(configuredNotRegistered), "com.microsoft.CompanyPortalMac.ssoextension", "KERBEROS.MICROSOFTONLINE.COM")
require.NoError(t, err)
require.NotNil(t, data)
require.Equal(t, "", data.deviceID)
require.Equal(t, "com.microsoft.CompanyPortalMac.ssoextension", data.extensionIdentifier)
require.Equal(t, "KERBEROS.MICROSOFTONLINE.COM", data.realm)
require.Equal(t, "", data.userPrincipalName)
require.False(t, data.registrationCompleted)
require.Equal(t, "POLoginTypeUserSecureEnclaveKey (2)", data.loginType)
// Platform SSO extension identifier matches, but user is not registered yet (null).
// Can happen if Platform SSO configuration profile was deployed and this is a workstation with two users,
// and one user registered but not the other one.
@ -75,6 +93,7 @@ func TestParseAppSSOPlatformCommandOutput(t *testing.T) {
require.Equal(t, "com.microsoft.CompanyPortalMac.ssoextension", data.extensionIdentifier)
require.Equal(t, "KERBEROS.MICROSOFTONLINE.COM", data.realm)
require.Equal(t, "", data.userPrincipalName)
require.Equal(t, "POLoginTypeUserSecureEnclaveKey (2)", data.loginType)
}
func TestGenerateErrors(t *testing.T) {

View file

@ -0,0 +1,53 @@
Time: 2026-03-05 16:36:57 +0000
Device Configuration:
{
"_accessTokenTerminalIdentityKeySEP" : false,
"_accessTokenTerminalIdentityKeySystem" : false,
"_deviceEncryptionKeyData" : "wI3a/2hAEPpukuFsD/Vi29mtPiay3Qgr+dMVW2ejoA8=",
"_deviceSigningKeyData" : "Qt3aUwTLbX/bFehpl/DBthLhTgXfnuCKgnXF/nqNWNw=",
"allowAccessTokenExpressMode" : false,
"allowDeviceIdentifiersInAttestation" : false,
"authGracePeriodStart" : "2026-03-04T21:01:56Z",
"authorizationEnabled" : false,
"created" : "2026-03-05T16:36:57Z",
"createFirstUserDuringSetupEnabled" : true,
"createUserLoginTypes" : [
1,
3
],
"createUsersEnabled" : false,
"encryptionAlgorithm" : "ECDHE-A256GCM",
"extensionIdentifier" : "com.microsoft.CompanyPortalMac.ssoextension",
"fileVaultPolicy" : "None (0)",
"lastEncryptionKeyChange" : "2026-03-04T21:01:56Z",
"loginFrequency" : 64800,
"loginPolicy" : "None (0)",
"loginType" : "POLoginTypeUserSecureEnclaveKey (2)",
"newUserAuthorizationMode" : "None",
"offlineGracePeriod" : "0 hours",
"pendingEncryptionAlgorithm" : "none",
"pendingSigningAlgorithm" : "none",
"protocolVersion" : 1,
"registrationCompleted" : false,
"requireAuthGracePeriod" : "0 hours",
"sdkVersionString" : 15.199999999999999,
"sharedDeviceKeys" : true,
"signingAlgorithm" : "ES256",
"synchronizeProfilePicture" : false,
"temporarySessionQuickLogin" : false,
"tokenToUserMapping" : {
"AccountName" : "preferred_username",
"FullName" : "name"
},
"unlockPolicy" : "None (0)",
"userAuthorizationMode" : "None",
"version" : 1
}
Login Configuration:
(null)
User Configuration:
(null)

View file

@ -592,6 +592,18 @@
"type": "text",
"required": false,
"description": "User principal name of the user that logged in via Platform SSO."
},
{
"name": "registration_completed",
"type": "integer",
"required": false,
"description": "Whether Platform SSO registration is completed (0 = false, 1 = true)."
},
{
"name": "login_type",
"type": "text",
"required": false,
"description": "Login type extracted from device configuration (e.g. \"POLoginTypeUserSecureEnclaveKey (2)\")."
}
],
"notes": "This table is not a core osquery table. It is included as part of Fleet's agent ([fleetd](https://fleetdm.com/docs/get-started/anatomy#fleetd)).",

View file

@ -25,5 +25,13 @@ columns:
type: text
required: false
description: User principal name of the user that logged in via Platform SSO.
- name: registration_completed
type: integer
required: false
description: Whether Platform SSO registration is completed (0 = false, 1 = true).
- name: login_type
type: text
required: false
description: Login type extracted from device configuration (e.g. "POLoginTypeUserSecureEnclaveKey (2)").
notes: This table is not a core osquery table. It is included as part of Fleet's agent ([fleetd](https://fleetdm.com/docs/get-started/anatomy#fleetd)).
evented: false