Deploy with Microsoft Entra ID
Use Microsoft Entra ID users and groups with DefenseClaw on Entra-joined Windows, on Linux (Azure Entra SSH sign-in, Himmelblau, or Microsoft Entra Domain Services with SSSD) and on macOS with Platform SSO. Get Entra groups to the operating system, map users and groups to guardrail profiles, and check what DefenseClaw records. Starter scripts and configs included.
DefenseClaw never calls Microsoft Entra ID or Microsoft Graph. A user signs in
to the computer with Entra ID, the computer gives that user an operating system
account, and DefenseClaw reads the account the way it reads any other: the SID
and sign-in token on Windows, NSS on Linux, Open Directory on macOS. So
deploying DefenseClaw for Entra users is first an Entra and operating system
task, and a groups assignment can name an Entra group only where the
operating system knows the membership. This guide gives the steps in
order, the starter scripts that do them, and what DefenseClaw records at the
end. The background on what is read from where is in
How DefenseClaw knows who a user is.
Guardrail profiles apply to per-user installs and to the standalone enterprise profile. The Secure Client profile rejects them and keeps its behavior as it was, so nothing here applies to a Secure Client computer.
What was run live
On live Entra tenants with Claude Code (Haiku 4.5 on Amazon Bedrock), each as the real user in an interactive session:
- Entra-joined Windows 11: a per-user install signed in over remote desktop as
an Entra user, and the standalone enterprise profile with a
groupsassignment by Entra group SID. - Ubuntu 22.04 on Azure with Entra SSH sign-in (
AADSSHLoginForLinux), per user. - Ubuntu 24.04 with Himmelblau 5.0.0 (nightly), per user and with the standalone enterprise package (deb): a group member got the group profile.
- RHEL 9.8 joined to a Microsoft Entra Domain Services managed domain with realmd and SSSD, per user and with the standalone enterprise package (rpm): a group member got the group profile, with qualified and with short names.
- macOS 15.8 enrolled in Intune with Company Portal and Platform SSO, per user and with the standalone enterprise package (pkg): no Entra group reached the Mac; a local group that carried the membership selected the profile.
Not tested: a users assignment on the standalone profile on Windows,
delivering the local group membership through Intune (Windows) or the bridge
script through Intune (macOS), hybrid-joined Windows, Entra Kerberos,
Himmelblau 4.x stable, Himmelblau's offline break-glass, and an Entra outage on
Windows or macOS. Each is marked where it comes up.
Pick your setup
| Setup | Users sign in with | DefenseClaw sees | A groups assignment can name |
|---|---|---|---|
| Entra-joined Windows 10 or 11 | Entra ID at the Windows sign-in screen | Directory entra_id, the user SID, the UPN and the tenant ID | Standalone enterprise profile: an Entra group by SID, once a built-in local group lists it (step 2). Per-user install: no groups, use users |
| Azure Linux VM with Entra SSH sign-in | az ssh vm, with the aad module | Directory entra_id, source nss_aad, the UPN | No Entra group: the module gives the account only a private group. Use users, or one of the two routes below |
| Linux with Himmelblau | ssh with the Entra password, an MFA code and a Hello PIN | Directory entra_id, source himmelblau; the UPN when accounts are named by it | The user's Entra groups, by name (Himmelblau) |
| Linux joined to Microsoft Entra Domain Services | ssh with the Entra password, through SSSD | Directory active_directory, source sssd (sssd_infopipe on standalone enterprise), the UPN, the domain | The synced Entra groups, as name@domain unless SSSD uses short names (Entra Domain Services) |
| Mac with Platform SSO (Company Portal) | Entra ID at the macOS login window | Standalone enterprise: directory entra_id, source macos_platform_sso, the UPN. Per-user: directory local | No Entra group: Platform SSO gives none. A local group that carries the membership (step 4) |
| Linux desktop enrolled in Intune | A local account; Intune enrolls the device and does not sign users in | Directory local, expected. Not observed with DefenseClaw installed | Local groups |
| Hybrid-joined Windows, Entra Kerberos | The readers exist (identity sources) | Not run on a live tenant |
Why a group assignment matches no one
The usual cause is that the operating system does not know the Entra group, so
DefenseClaw does not either. Check with the OS's own tools as the user: whoami /groups on Windows, id on Linux and macOS. If the Entra group is not listed
there, no DefenseClaw setting can match it: use the route for your platform
below, or a users assignment.
Starter kit
The scripts and example configs are in
packaging/identity/entra.
Every script has --help (or Get-Help), reads credentials from the
environment and never from arguments, and changes nothing until you say so:
the Python and shell scripts print a plan unless you pass --apply, and the
PowerShell script supports -WhatIf.
| File | Use |
|---|---|
entra_setup.py | Microsoft Graph helper: create groups and users from a JSON file, read the Windows SID of a group or user, compute a SID from an object id offline |
setup-entra-ssh-linux.sh | Azure CLI wrapper: enable Entra SSH sign-in on an Azure Linux VM and grant the sign-in role |
Add-EntraGroupToLocalGroup.ps1 | Put an Entra group's SID into a built-in local group, the step that makes the group appear in sign-in tokens |
Get-DefenseClawEntraIdentity.ps1 | Read-only: show the join state, the Entra accounts, the token's groups and what DefenseClaw answers |
setup-himmelblau.sh | Install Himmelblau on Ubuntu 24.04, write himmelblau.conf (UPN account names, the groups allowed to sign in) and restart it safely; check is read-only |
join-entra-domain-services.sh | Join a Linux host to an Entra Domain Services managed domain with realmd and SSSD, switch between qualified and short names, clear the SSSD cache; check is read-only |
macos-entra-group-bridge.sh | Add a Mac user to, or remove them from, the local group that stands for an Entra group (an Intune shell script that names the account, or by hand) |
macos-platform-sso.settings-catalog.example.json | The Intune settings catalog body for Platform SSO with Company Portal that the live test used |
admin-config.windows.example.yaml, per-user-profiles.example.yaml, tenant.example.json | Starting points for the standalone config, a per-user config and the tenant setup file |
1. Prepare the tenant
DefenseClaw needs nothing special from the tenant. You need the groups and users your profiles will name, and the SID of each group.
The live tests ran on a tenant with no paid licence. They turned security defaults off so a scripted password sign-in works; that is a test convenience, not a DefenseClaw requirement.
Create an app registration with the application permissions Group.ReadWrite.All,
User.ReadWrite.All and Organization.Read.All (read-only check needs
Organization.Read.All and Policy.Read.All; sids needs Group.Read.All and
User.Read.All), grant admin consent, and give the script its credentials
through the environment:
export AZURE_TENANT_ID=00000000-0000-0000-0000-000000000000
export AZURE_CLIENT_ID=00000000-0000-0000-0000-000000000000
read -rs AZURE_CLIENT_SECRET && export AZURE_CLIENT_SECRET # type it; it stays out of your history
python3 entra_setup.py check # tenant, domains, security defaults
cp tenant.example.json tenant.json # edit the domain, groups and users
python3 entra_setup.py apply --config tenant.json # a preview; changes nothing
python3 entra_setup.py apply --config tenant.json --apply --password-file users.txt
python3 entra_setup.py sids --group defenseclaw-ml-teamA token in GRAPH_ACCESS_TOKEN works instead of the app registration. The
script creates objects that do not exist and leaves the others alone, so it is
safe to run again. New users get a generated password that goes only to the
--password-file (mode 0600), never to the screen.
sids prints the object ID and the Windows SID of the group:
group defenseclaw-ml-team
object id 6f1c9a0e-3b2d-4c58-9e71-a4b5c6d7e8f9
SID S-1-12-1-1864145422-1280850733-3047453086-4192786374Windows shows an Entra group only by this SID. The SID is four 32-bit words of
the object ID, so entra_setup.py sid-from-object-id 6f1c9a0e-... computes it
without a tenant. On a live tenant the Graph securityIdentifier of two groups
and a user matched the computed value, and the SIDs of that user and of her
group matched the ones in her Windows sign-in token.
2. Windows: Entra-joined computers
Join and sign in
Join the computer to Entra ID in Settings > Accounts > Access work or
school > Join this device to Microsoft Entra ID. The user who joins is a
local administrator by default, so remove that account from Administrators if
your users should be standard users. On an Azure VM, the AADLoginForWindows
extension joins it, and each user needs the Virtual Machine User Login
role.
Users sign in with their Entra ID; over remote desktop the live test signed in
as AzureAD\alice@contoso.onmicrosoft.com. Windows gives the account a name of
its own, not the UPN (AzureAD\EntraAlice for entra-alice@... in the live
test), and a SID that starts with S-1-12-1-.
See what the computer exposes
Run the read-only diagnostic as the user you want to check. Run it again as
SYSTEM to read the identity store, which the live test read as SYSTEM. If the
script came from the Internet, run Unblock-File .\Get-DefenseClawEntraIdentity.ps1
first, or launch a trusted copy with powershell.exe -ExecutionPolicy Bypass -File .\Get-DefenseClawEntraIdentity.ps1. An unattended run of an Internet-zone
script can hang at a security prompt.
.\Get-DefenseClawEntraIdentity.ps1
.\Get-DefenseClawEntraIdentity.ps1 -GroupSid S-1-12-1-1864145422-1280850733-3047453086-4192786374It prints the dsregcmd /status join state, the Entra accounts in the
identity store (SID, UPN, provider), the tenant, the groups in the sign-in
token with the Entra SIDs marked, the built-in local groups that list an Entra
SID, and, when DefenseClaw is installed, the answer of guardrail profile explain. With -GroupSid it says whether each group is in this token.
Put an Entra group into the sign-in token
An Entra-joined computer puts an Entra group's SID into a sign-in token only
when a built-in local group lists it. Without that, a groups assignment or an
enrollment group filter that names the group matches no one. The rule and its
limits are in
Entra groups on Windows.
For a fleet, use an Intune policy; see
Prepare your Intune tenant.
Delivery through Intune has not been tested. To make the membership on one
computer, run the script elevated. Unblock a downloaded copy with
Unblock-File .\Add-EntraGroupToLocalGroup.ps1 or launch it with
powershell.exe -ExecutionPolicy Bypass -File .\Add-EntraGroupToLocalGroup.ps1 -GroupSid SID. An unattended Internet-zone script can hang at a security
prompt. Intune platform scripts receive no arguments; use Account protection
for fleet delivery:
.\Add-EntraGroupToLocalGroup.ps1 -GroupSid S-1-12-1-1864145422-1280850733-3047453086-4192786374 -WhatIf
.\Add-EntraGroupToLocalGroup.ps1 -GroupSid S-1-12-1-1864145422-1280850733-3047453086-4192786374The script adds the SID to the built-in Users group with the Windows API
NetLocalGroupAddMembers. It is idempotent: it
reports Added, AlreadyMember, Removed or NotMember. -Remove undoes it.
It warns when you name a group other than the six Windows reads, and when you
name Administrators, because that makes every member of the Entra group a local
administrator.
The script ran on Windows Server 2025 in Windows PowerShell 5.1 and PowerShell
7 against a throwaway local group. The same API call put an Entra group into
the sign-in token of an Entra-joined Windows 11 user in the live test. After
the membership exists, the user signs out and back in. net localgroup may not
list an Entra SID the computer cannot resolve; use the diagnostic's -GroupSid
check, which reads the token.
Install DefenseClaw
Install per user as in Install, or deploy the standalone enterprise profile with Setup or your MDM (Enterprise on Windows, Intune on Windows).
A per-user install has no group list on Windows, so only users, connectors
and agents assignments can match there. The standalone enterprise profile
reads each user's token groups through the SYSTEM enumerator.
3. Linux
Linux has three ways to sign Entra users in, and only two of them give the host the user's Entra groups:
| Route | Entra groups on the host | Tested with DefenseClaw |
|---|---|---|
Azure Entra SSH sign-in (aad module) | None: each account has only its private group | Ubuntu 22.04, per user |
| Himmelblau | The user's Entra groups, named in id and initgroups | Ubuntu 24.04, Himmelblau 5.0.0 nightly, per user and standalone enterprise |
| Entra Domain Services with SSSD | The synced Entra groups, as Active Directory groups | RHEL 9.8, per user and standalone enterprise |
Azure VMs with Entra SSH sign-in
Enable the sign-in on the VM. The script enables the VM's system-assigned
identity, installs the AADSSHLoginForLinux extension and grants the
Virtual Machine User Login role (--admin grants Virtual Machine
Administrator Login, which carries sudo rights). It prints a plan until you
add --apply:
./setup-entra-ssh-linux.sh -g my-rg -n my-vm --user alice@contoso.onmicrosoft.com --group defenseclaw-ml-team
./setup-entra-ssh-linux.sh -g my-rg -n my-vm --user alice@contoso.onmicrosoft.com --group defenseclaw-ml-team --applyUsers sign in with the Azure CLI:
az extension add --name ssh
az ssh vm -g my-rg -n my-vmThe aad module adds itself to passwd and group in
/etc/nsswitch.conf, and the account is named by the UPN:
$ id
uid=9246366(alice@contoso.onmicrosoft.com) gid=9246366(alice@contoso.onmicrosoft.com) groups=9246366(alice@contoso.onmicrosoft.com)
$ getent group defenseclaw-ml-team
$The module derives the uid from the user. The account has only its private group: getent group finds no Entra group.
So a groups assignment cannot name an Entra group on this setup. Assign
profiles by users, or add a local group of your own.
Install DefenseClaw per user as that user, as in Install. The Entra role is only for sign-in; DefenseClaw does not use it. The standalone enterprise package on an Entra Linux VM was not tested.
Enrolling a Linux desktop in Intune is a different path: the device gets an Entra identity for compliance, but the accounts on it stay local. See Prepare your Intune tenant.
Himmelblau
Himmelblau is an open-source
(GPL-3.0) NSS, PAM and SSH module that signs Entra users in to Linux and reads
their Entra groups. It needs no app registration. The live test used the 5.0.0
nightly build (5.0.0-ubuntu24.04~20261007) on Ubuntu 24.04, the only free
channel at the time; the 4.0.5 stable packages need a paid entitlement token
(or a build from source) and were not tested.
Before users sign in. Entra refuses remote sign-in through Himmelblau for a
user who has no MFA method registered (AADSTS50072, "Remote session with
unenrolled MFA user denied"), even with security defaults off and no
Conditional Access policy. Have each user register an authenticator app at
Security info. The first sign-in then asks for the Entra password, the
code and a new Windows Hello PIN of at least six characters (an empty PIN is
refused), and joins the computer to Entra: it creates a device object, so your
users need permission to join devices.
Install and configure (as root; the script prints its plan first):
GROUP_OBJECT_ID=00000000-0000-0000-0000-000000000000 # the Entra group object id
sudo ./setup-himmelblau.sh install --domain contoso.onmicrosoft.com --allow-group "$GROUP_OBJECT_ID"
sudo ./setup-himmelblau.sh install --domain contoso.onmicrosoft.com --allow-group "$GROUP_OBJECT_ID" --apply
./setup-himmelblau.sh check --user alice@contoso.onmicrosoft.comThe script's configure, restart and check ran live on the test VM;
its install step does what the live test did by hand (repository, key and
packages) and was run in plan mode only. The packages (himmelblau nss-himmelblau pam-himmelblau himmelblau-sshd-config, from the signed repository at
packages.himmelblau-idm.org) add himmelblau to the passwd, group and
shadow lines of nsswitch.conf and to PAM, but write no
/etc/himmelblau/himmelblau.conf, and the daemon does not start without its
domain. The script writes these keys:
| Key | Why |
|---|---|
domain = contoso.onmicrosoft.com | The tenant |
cn_name_mapping = false | Names accounts by their UPN. With Himmelblau's default (true) accounts are named alice, carry no UPN or domain, and a DefenseClaw users entry written as a UPN never matches; explain says so. Groups match either way |
pam_allow_groups = <object id>,... | Only members of these Entra groups may sign in. Without it every user of the tenant can, and the daemon warns. It takes object IDs, not names |
home_attr, home_alias = CN, use_etc_skel = true | Home directories named by the account |
What the host shows. id names the Entra groups
(groups=1608906209(ml-team) style ids and names), but getent group ml-team
answers nothing: Himmelblau finds a group only by gid or object ID, by design.
DefenseClaw matches the names id reports exactly, including case, spaces and
accents. Quote a name with spaces in YAML, for example
groups: ["Research ML Team"]. A groups: [ml-team] assignment works, and DefenseClaw does not warn that the group is unknown on a
host whose group line lists Himmelblau. The records carry directory
entra_id, source himmelblau, assurance verified, a posix_uid, the
private group plus the Entra groups in group_count, and the SSH session.
Group changes. Himmelblau caches an account for 300 seconds
(cache_timeout) and refreshes it at the next lookup after that. In the live
test a removal showed in id after 1 minute 46 seconds and an addition after
4 minutes 58 seconds; a new sign-in, or sudo aad-tool cache-clear, shows it
at once. DefenseClaw then picks it up within its 15-minute cache, so a removal
can take about 21 minutes to reach hook decisions.
When Entra is unreachable. Cached accounts and groups keep answering, past
the cache time and across a daemon restart. A new SSH sign-in of an MFA user
is refused (PAM_ABORT: Network outage detected) unless Himmelblau's
offline_breakglass is set up, which is off by default and was not tested.
Sign-in works again as soon as Entra answers.
When the daemon is down or restarting, id shows the Entra groups as bare
numbers. DefenseClaw keeps such an answer for 2 minutes instead of 15, and the
next request after that waits for a fresh lookup, so a group member gets the
group profile again within about 2 minutes of the daemon's return.
Restart the daemon with the script. On the 5.0.0 nightly, a second
systemctl restart himmelblaud himmelblaud-tasks left the units stuck
(inactive (dead-resources-pinned)), and the host answered no Entra account
until systemctl clean --what=fdstore himmelblaud.service. setup-himmelblau.sh restart --apply stops the sockets and daemons, clears the stored descriptors and
starts them again; configure restarts the same way.
Other notes from the live test: a VM without a TPM uses a software HSM;
debug = true is very verbose; the test VM (Standard_B2als_v2) cost about
US$0.04 an hour while running.
A configure --apply run writes the keys above and removes any other
himmelblau.conf settings. Its preview names the unmanaged keys, and each
apply keeps a timestamped backup. An omitted --allow-group preserves an
existing pam_allow_groups sign-in restriction; use --allow-all to remove
it explicitly. Review the backup for site-specific settings
before running it again.
Remove Himmelblau
Before removing a host, use Entra admin center to find and delete that host's Entra device object. Keep user homes until their owners have copied any needed data. On the host, stop the Himmelblau services and remove all five packages:
sudo systemctl stop himmelblaud.service himmelblaud-tasks.service
sudo apt-get purge himmelblau nss-himmelblau pam-himmelblau himmelblau-sshd-config himmelblau-apparmor
sudo rm /etc/apt/sources.list.d/himmelblau.list /etc/apt/keyrings/himmelblau.gpg
sudo rm -r /etc/himmelblau /var/lib/himmelblaud
sudo apt-get updateReview /etc/nsswitch.conf and PAM for any remaining Himmelblau entries before
the next sign-in. Remove an Entra user's home only after checking its contents.
Microsoft Entra Domain Services with SSSD
Microsoft Entra Domain Services
runs a managed Active Directory domain that syncs the tenant's users and
groups. A Linux host joins it with realm join like any AD domain, SSSD reads
the Entra groups as AD groups, and DefenseClaw reports the account as
active_directory.
Cost. The managed domain bills while it exists: about US$0.15 an hour (roughly US$110 a month) for the Standard SKU in the live test, plus the VMs.
Create the managed domain (once per tenant, in the Azure portal or with
ARM; the live test used the ARM API, Microsoft.AAD/domainServices version
2022-12-01):
- Register the
Microsoft.AADresource provider. Create the Domain Controller Services service principal, the AAD DC Administrators group and an admin user in it. The live test's automation identity needed the Entra roles Application Administrator and Groups Administrator for this. - Create a VNet with a subnet for the domain and one for the VMs.
- Create the managed domain with TLS 1.0 disabled
(
domainSecuritySettings.tlsV1: Disabled): with the default the create failed after 19 seconds. It took about 67 minutes to finish; a second domain controller appeared later. - Point the VNet's DNS servers at both domain controllers.
- Reset or change each user's password once the domain exists, so it gets the
NTLM and Kerberos hashes. A self-service change worked within 5 minutes; an
administrator reset took about 11 minutes. Repeated failed
kinitattempts lock the account for 30 minutes.
Join a host (as root; RHEL 9.8 in the live test):
sudo ./join-entra-domain-services.sh join --domain contoso.onmicrosoft.com --admin dsadmin@contoso.onmicrosoft.com --short-names
sudo ./join-entra-domain-services.sh join --domain contoso.onmicrosoft.com --admin dsadmin@contoso.onmicrosoft.com --short-names --apply
./join-entra-domain-services.sh check --user alice --group ml-teamIt installs realmd, SSSD and adcli, and runs
realm join --membership-software=adcli -U dsadmin@CONTOSO.ONMICROSOFT.COM contoso.onmicrosoft.com,
which asks for the password. The computer account lands in
OU=AADDC Computers, and SSSD gets id_provider = ad. The live test joined
with that realm join command typed by hand; the script's check and
names ran live, and its join was run in plan mode only.
Qualified or short names. realm join sets use_fully_qualified_names = True, so the host knows the group only as ml-team@contoso.onmicrosoft.com
and the user as alice@contoso.onmicrosoft.com. A groups: [ml-team]
assignment then selects nobody; explain names the qualified group the host
knows. Either write the qualified name in the assignment, or switch to short
names (--short-names, or join-entra-domain-services.sh names short --apply). Both were tested live and selected the group profile.
What DefenseClaw records. Directory active_directory, source sssd, the
domain, the bare account name and the uid SSSD's ID mapping assigns. On
standalone enterprise the root guardian reads the UPN from SSSD InfoPipe after
the reconcile that enrolls the account, and the source becomes
sssd_infopipe. Until then the principal is alice@CONTOSO.ONMICROSOFT.COM;
DefenseClaw refreshes such an account every 2 minutes, so the UPN replaces it
within about 2 minutes of the guardian's record. A per-user install has no
InfoPipe access and keeps alice@CONTOSO.ONMICROSOFT.COM.
Group changes go through three caches. Entra to the managed domain took
about 20 seconds for an addition and 37 seconds for a removal. SSSD keeps an
entry for entry_cache_timeout, 5,400 seconds by default: with the defaults
the change had not shown after 10 minutes. After sudo sss_cache -E
(join-entra-domain-services.sh flush --apply), id showed it 172 seconds
after the Entra change. DefenseClaw then picks it up within its 15-minute cache.
End to end: about 15 minutes with a flush, and up to about 105 minutes with
SSSD's defaults. A shorter entry_cache_timeout shortens the SSSD part; that
was not measured.
4. macOS: Platform SSO
Platform SSO with the Microsoft Enterprise SSO plug-in (Company Portal) signs Entra users in at the login window and links each local account to its Entra user. It does not put the user's Entra groups into the macOS group database.
The live test used macOS 15.8, Company Portal 5.2608.0 and user-driven
enrollment in Intune, with the settings catalog policy in
macos-platform-sso.settings-catalog.example.json (password method, shared
device keys, a local account created at the login window, the account name
from the UPN's short name), assigned to a device group. An existing local
account was linked by registration, and a new one was created at the login
window. In both cases:
id -Gnlisted only local groups, anddsmemberutil checkmembershipdid not find the Entra group.- With the Platform SSO settings
UserAuthorizationModeandNewUserAuthorizationModeset to Groups andAdditionalGroupsnaming the Entra groups (by name and by object ID), or withAdministratorGroups, Platform SSO created empty local groups (RealName"Platform SSO: ml-team", gid 5000 and up) and never added the user. No Entra group claim reached the Mac, and the user did not become an administrator.
So a groups assignment can match only a local group that carries the
membership. macos-entra-group-bridge.sh adds the signed-in user to such a
group, or removes them; it uses the group Platform SSO created when one of
that name exists:
sudo ./macos-entra-group-bridge.sh add --group ml-team --user alice
sudo ./macos-entra-group-bridge.sh add --group ml-team --user alice --apply
./macos-entra-group-bridge.sh check --group ml-team --user aliceFor a fleet, let Intune decide who is in the group, with one signed-in Entra user per Mac, who is also the Mac's primary user. Intune evaluates a user assignment for the primary user and runs the script as root, not as that user, so the script cannot tell who the assignment is for:
- Fill in the settings block of a copy (
ACTION=add,GROUP_NAME=ml-team,USER_NAME=<the Mac account of the Entra user>,APPLY=yes) and assign it to the Entra group, and a copy withACTION=removeto the users who are not in it (all users, with the Entra group excluded). - An Intune copy without
USER_NAMEstops with exit 2. Anaddcopy changes the named account only while that account is in front at the console; with another user in front (fast user switching) or the login window showing it changes nothing and exits 1. In Intune, set Script frequency for both copies (for example, daily) and Max number of times to retry if script fails for theaddcopy (for example, 3). The defaults run each copy once and never retry a failure. Check the script result and local group membership after sign-in; if the user was never in front during a run, rerun the copy. Aremovecopy acts on the named account at any time. See Microsoft's macOS shell script settings. - The script refuses built-in and privileged groups (
admin,wheel,staff,com.apple.*, any group with an id below 500) unless--allow-system-group(ALLOW_SYSTEM_GROUP=yes) is given, and names that start with a dash or exceed 64 characters.removeleaves the group in place; delete it withdseditgroup -o delete <group>when no assignment uses it.
That delivery was not tested; the membership itself was: the member got the
group profile on a per-user install and on standalone enterprise, the other
user got default_profile, and removing the membership moved her back to the
default.
What DefenseClaw records. Platform SSO stores each registered account's
Entra UPN in its record (AltSecurityIdentities, PlatformSSO:<upn>), which
root can read. The standalone enterprise enumerator reads it: the account is
directory entra_id, source macos_platform_sso, with the UPN as its
principal and domain, so a users assignment by UPN works there. Local
accounts on the same Mac that never registered stay local. A per-user
install cannot read the record and reports local.
5. Map users and groups to profiles
Write the assignments with the UPN for users and the SID for groups. Starter
files: per-user-profiles.example.yaml for a user's own config and
admin-config.windows.example.yaml for the standalone profile. The keys are
the ones in User- and group-based policies;
a UPN compares without regard to case.
guardrail:
profiles:
strict: {mode: action, block_at: MEDIUM}
ml-team: {mode: observe}
profile_assignments:
- profile: ml-team
match:
users: ["alice@contoso.onmicrosoft.com"]
- profile: ml-team # standalone enterprise on Windows only
match:
groups: ["S-1-12-1-1864145422-1280850733-3047453086-4192786374"]
default_profile: strict
ai_discovery:
include_user_principal: true # put the UPN in records; off by defaultA per-user config belongs to the user
A per-user install reads ~/.defenseclaw/config.yaml (Windows:
%USERPROFILE%\.defenseclaw\config.yaml) from the user's own profile, so the user
can change their profile. To enforce a policy the user cannot change, use the
standalone enterprise profile, which reads the machine config.
| Where | users by UPN | groups by Entra group |
|---|---|---|
| Windows, per-user | Matched in the live test | No: a per-user install has no group list |
| Windows, standalone enterprise | Matched before a lane-owned account's UPN rename and stopped matching after it; the account SID stayed the same | By SID, after the local group step. Matched in the live test |
Linux aad, per-user | Matched in the live test | No: the module gives no Entra groups |
| Linux Himmelblau, per-user and standalone | With cn_name_mapping = false | By name, as id shows it. Matched in the live test |
| Linux Entra Domain Services, per-user and standalone | By the UPN on standalone enterprise | ml-team@domain, or ml-team with short names. Matched in the live test |
| macOS Platform SSO, standalone | By the UPN | Only a local group that carries the membership. Matched in the live test |
| macOS Platform SSO, per-user | No: the account reports local without a UPN | Only a local group, as above |
Set a strict default_profile: a failed lookup lands on it
(why).
6. Check
Ask the gateway which profile a user gets. On Windows, name an Entra user by
SID, as AzureAD\Name, by the bare name or by the UPN.
# Standalone enterprise: run in an elevated Administrator prompt.
& 'C:\Program Files\Cisco\DefenseClaw\bin\defenseclaw.exe' enterprise windows profile-explain --user 'AzureAD\Alice' --connector claudecode
# Per-user install:
defenseclaw guardrail profile explain --connector claudecodedefenseclaw guardrail profile explain --connector claudecode
# standalone enterprise, as root:
sudo /opt/defenseclaw/bin/defenseclaw-gateway enterprise linux profile-explain --user alice --connector claudecode
sudo /opt/cisco/defenseclaw/bin/defenseclaw-gateway enterprise macos profile-explain --user alice --connector claudecodeIn the per-user live tests, profile named the profile from the users
assignment and match said user. On the standalone profile use
defenseclaw.exe enterprise windows profile-explain --user <SID> --connector claudecode
from an elevated prompt. In the live test it named the group profile with
match group for a member of the group, and default_profile for another
Entra user.
These are the fields the records carried for an Entra user (the complete table is Entra ID fields, as seen live):
| Field | Windows | Linux aad |
|---|---|---|
defenseclaw.user.name | The account name Windows assigns (EntraAlice), not the UPN | The UPN's account part |
defenseclaw.user.principal | The UPN | The UPN, in lower case |
defenseclaw.user.directory, .identity.source | entra_id, windows_identity_store | entra_id, nss_aad |
defenseclaw.user.tenant_id | The tenant ID | Not reported |
defenseclaw.user.group_count | 0 on a per-user install | 1, the private group |
7. What to expect in operation
On a Mac, a new Platform SSO account can enter the full macOS Setup Assistant at first login before it reaches a desktop. Sign demonstration users in once before a timed run, or deploy an approved Setup Assistant skip payload.
- A group change on Windows reaches DefenseClaw when the user signs in
again: the token is fixed at sign-in. The enumerator decides a signed-out
user from the token it saw at the last sign-in, and
profile explainthen says that the profile is not final. After a sign-in the enumerator reads the new token about 15 seconds later.profile-explaincan already show the new profile while live hooks still use directory facts cached from the previous lookup. That gateway cache lasts up to 15 minutes. In live Entra add and removal tests, the token changed at the next sign-in (two to three minutes after the tenant change), while the hook decision changed about 13 minutes after sign-in. An AD group addition first changed a live hook decision about 14 minutes after the new record. Checkprofile-explainfor the cached fact age and remaining time before deciding a rollout has failed. Microsoft documents that a new token can take up to 4 hours to carry a change; that was not measured here. - A group change on Linux takes the Himmelblau or SSSD cache time plus DefenseClaw's 15 minutes; the measured times are under Himmelblau and Entra Domain Services. On macOS a change reaches DefenseClaw when the bridge script runs again.
- A user who has not signed in yet. An Entra user who has never signed in to
a Windows computer has no account there yet, so there is no SID to match. On
the Azure Linux VM, the module resolved such a user by name but not by uid,
and
profile explainsaidunixidentity: not found. - An Entra outage or a computer with no network. In a nine-minute Windows
sign-in endpoint outage test, open sessions kept the same hook decisions,
including after a gateway restart: DefenseClaw reads the identity store,
join state and token locally. Windows refused a new password sign-in while
the endpoint was unavailable, so no fresh offline token was tested. On Linux,
a lookup that fails falls back to
default_profilewith the reasondefault_lookup_failed, after the gateway's cached answer expires (up to an hour); see the lookup rules.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
A groups assignment with an Entra group never matches on Windows | The group is not in the token. Name it by SID (the computer cannot resolve names), have a built-in local group list it, verify the account belongs to that Entra security group, then sign out and in if membership was recently added. Check with Get-DefenseClawEntraIdentity.ps1 -GroupSid |
profile explain --user <name> on Windows says "Windows has no account by this name" | The user has not signed in to this computer yet, or the name is misspelled. Pass the SID, AzureAD\Name or the UPN |
group_count is 0 and groups never match on a per-user Windows install | Expected: use users, or the standalone enterprise profile |
getent group finds no Entra group on the Linux VM | Expected: the aad module gives no Entra groups. Use users, Himmelblau or Entra Domain Services |
getent group ml-team finds nothing on a Himmelblau host, but id lists the group | Expected: Himmelblau finds groups only by gid or object ID. DefenseClaw matches the name id shows |
A user outside pam_allow_groups completes password, MFA and Hello PIN enrollment but SSH refuses them; the log says "User account has expired" | Check that the user belongs to an allowed Entra group before enrollment, then verify the group object ID in pam_allow_groups. |
| SSH sign-in through Himmelblau asks for the password again; the log says "Remote session with unenrolled MFA user denied" | The user has no MFA method. Register one at Security info, then sign in: password, code, then a new PIN of six or more characters |
himmelblaud is inactive (dead-resources-pinned) after a restart, and no Entra account resolves | The 5.0.0 nightly restart issue. Run setup-himmelblau.sh restart --apply |
A users entry by UPN never matches on a Himmelblau host; explain says the account has no UPN | cn_name_mapping is true (the default). Set it to false (setup-himmelblau.sh configure ... --apply), or name the account by its short name |
explain says a group is not known to this host by that name and names ml-team@domain | SSSD uses qualified names. Write the qualified name, or switch to short names |
id on a Mac shows no Entra group after Platform SSO registration | Expected: Platform SSO gives no groups. Use the bridge script, or users on standalone enterprise |
The records of a Platform SSO user say local | A per-user install cannot read the Platform SSO link; standalone enterprise reports entra_id. An account that never registered stays local |
profile explain says unixidentity: not found for an Entra user on Linux | The OS names the account but cannot map its uid back to it, as seen for users who had not signed in. Have the user sign in, then ask again |
| Records have no principal | ai_discovery.include_user_principal is off, the default |
az ssh vm is refused | Microsoft requires the AADSSHLoginForLinux extension on the VM and a VM login role for the user. setup-entra-ssh-linux.sh without --apply lists what is missing |
Add-EntraGroupToLocalGroup.ps1 says to run elevated | It changes a local group. Run it as an administrator or as SYSTEM, or use -WhatIf |
Next steps
Entra groups on Windows
The token rule, the Intune recipe and the limits.
User- and group-based policies
Write assignments, read the audit record, ship to managed hosts.
Prepare your Intune tenant
Licences, enrollment, groups and the scripts that set them up.
Identity and authentication
Supported identity types and the other guides.
Okta on Linux
Read Okta users and groups on a Linux host through the Okta LDAP Interface and SSSD, choose DefenseClaw guardrail profiles by Okta group, and check the result. Covers the Okta setup, the SSSD build that can bind to Okta, the starter scripts, what was tested, and troubleshooting.
Foreign-hook guard
How the standalone profile stops user and project hooks that DefenseClaw has not approved from changing an AI agent's tool call after inspection, which agents and files it covers on each OS, and how to approve or restore a hook.