Delegation model and AD rights¶
Steward lets operators change Active Directory only where you delegated it. Two independent checks stand between an operator and an AD object, and both must agree:
flowchart LR
op["Operator<br/>(AD account or Teleport user)"] --> c{"1. Steward decides<br/>role → delegation:<br/>action + attribute + OU"}
c -- denied --> x1["Refused, audited"]
c -- allowed --> ad{"2. AD decides<br/>ACEs of the service account<br/>(Grant-StewardDelegation.ps1)"}
ad -- denied --> x2["Refused by AD, reported"]
ad -- allowed --> ok["Change made, audited"]
- Steward decides. A request is allowed only when one single delegation covers the action, the attribute (for attribute changes) and the target's OU, all at once. Everything else is denied, and any doubt (unreadable object, unresolved group, internal error) is a denial too.
- AD decides. Steward never writes with an administrator account. It uses dedicated, non-privileged service accounts whose AD rights (ACEs) cover exactly what your roles need. A domain administrator applies those rights with a script; Steward itself can never change permissions in AD. If the two disagree, AD refuses and the console says so, and the diagnostics show the gap before anyone tries.
So even a stolen administrator session of the console cannot reach more than the AD rights you granted, and a mistake in the AD rights cannot be used without a matching delegation.
Building blocks¶
| Concept | What it is | Where |
|---|---|---|
| Managed OUs | The outer boundary of the instance, set in the setup wizard. Nothing outside them is ever reachable. | Configuration › Directory |
| Role | A named set of application rights and delegations, assigned to operators. | Delegations › Roles |
| Delegation | One self-contained grant: actions + attributes (if any) + scope (OUs). Never merged with another delegation. | Inside a role |
| Operator | A person allowed to sign in: an AD account (first.last@domain or DOMAIN\user) or a Teleport user name. A new operator has no role, so no rights. |
Delegations › Operators |
| Teleport mapping | Gives a role automatically to the Teleport users who have a given Teleport role (and optional trait). | Delegations › Teleport (Teleport) |
Scope¶
A delegation's scope is a list of OUs from the managed OUs (up to 100), each one:
| Option | Covers |
|---|---|
| This OU only | Objects directly in the OU. |
| OU and sub-OUs | The whole subtree. |
| Exclude this OU / Exclude OU and sub-OUs | Removes those objects from this delegation only. |
OUs are stored by GUID: renaming or moving an OU in AD does not break a delegation. For group actions, the scope is where the groups live; the members added must also be users the operator may view.
Exclusions exist only in Steward
An exclusion is enforced by the console. The AD rights are set on the OU and cover all of it: AD cannot express "this OU except that sub-OU" for inherited rights.
Actions¶
Directory actions are granted per delegation:
| Action (code) | Label in the console | Notes |
|---|---|---|
users.read |
Search and view | Basis of every user action. |
users.attributes.read |
Read attributes | Choose the attributes. |
users.attributes.write |
Change attributes | Choose the attributes. Write account. |
users.password.reset |
Reset password | Helpdesk only (password service). |
users.unlock |
Unlock account | Helpdesk only. |
users.disable / users.enable |
Disable / Enable an account | Helpdesk only. |
users.create |
Create an account (template) | Needs users.read and the helpdesk (first password, enable). See Onboarding. |
users.move |
Move an account to another OU | Both the account's OU and the destination must be in scope. |
users.rename |
Rename an account (name, display name) | |
users.rename.login |
Change the login (sAMAccountName, UPN) | Only together with users.rename. |
vip.manage |
VIP override | Lets the other actions of the same delegation reach VIP accounts. Never alone; never reaches privileged accounts. |
groups.read |
View groups | |
groups.members.write |
Manage group members | Needs groups.read. |
groups.owned.members.write |
Change members of groups the operator manages (managedBy) | Only groups of the scope whose managedBy is the operator's own AD account. |
groups.create / groups.delete |
Create / Delete a group | Never a privileged, protected or built-in group. |
groups.attributes.write |
Edit group properties | description, managedBy, mail, info. |
computers.read |
View computers | Never the password. |
computers.laps.read / computers.laps.expire |
Reveal / Expire the LAPS password | Need computers.read and the password service. See LAPS. |
Attributes come from a fixed catalog: givenName, sn, displayName, title, department, company, description, mail,
telephoneNumber, manager, accountExpires, thumbnailPhoto (photo), extensionAttribute1 to 15 (when the schema has them),
and the read-only lastLogonTimestamp. There is deliberately no "write everything" action.
A delegation can also grant some actions with approval (reset, unlock, disable, enable, attribute changes, group members): the operator then files a request that another operator approves. See Approval requests.
Application rights¶
Granted per role, not tied to OUs: audit.read (View audit), delegations.read (View delegations), delegations.manage
(Manage delegations), settings.manage (Manage configuration), accessreviews.manage (access reviews).
The Administrators role created by the setup wizard has exactly settings.manage, delegations.manage, delegations.read and
audit.read, and no delegation: administering the console gives no rights on AD objects. The last delegation administrator
can never be removed or disabled, and changing your own roles always asks for confirmation.
The Super administrator role¶
A built-in, read-only role: every application right and every directory action (VIP override included) on every managed OU and its sub-OUs. It is computed at each decision, so new managed OUs, attributes and actions are covered at once. Privileged accounts stay out of reach like for any role. Granting or removing it always asks for confirmation (step-up) and is audited; the setup wizard offers it to the first administrator as an unchecked option. Keep it for one or two named people; give narrow roles for daily work.
What is never reachable¶
Whatever the roles say, the engine refuses:
- Privileged accounts (Tier 0/1): members (direct or nested) of the built-in privileged groups (Domain Admins, Enterprise
Admins, Schema Admins, Administrators, Account/Server/Backup/Print Operators, Replicator, Group Policy Creator Owners, the
domain controller groups, Key Admins, Enterprise Key Admins, Cert Publishers, Incoming Forest Trust Builders, Hyper-V
Administrators, Storage Replica Administrators, DnsAdmins and, when present, the Exchange groups Exchange Windows Permissions,
Exchange Trusted Subsystem and Organization Management) or of the additional privileged groups,
accounts with
adminCount=1, service accounts (with an SPN), accounts below a Tier 0/1 OU, and anything whose classification is uncertain. Manage the lists in Configuration › Account protection. - VIP accounts (members of VIP groups), unless the delegation includes the VIP override.
- Objects outside the managed OUs, read-only attributes, and the operator's own AD account for group membership, reset, unlock, disable and enable.
The target's facts are read from AD at every sensitive operation; nothing sent by the browser is trusted. Privileged accounts also cannot sign in to the console.
Designing roles¶
- Delegations › Roles › New role. Name it after a job (
Helpdesk L1,HR,Client power users). - Add one delegation per coherent grant: for example Search and view + Change attributes (title, department, telephoneNumber)
on
OU=Usersand its sub-OUs, excludingOU=Board. - Delegations › Operators › Add an operator, then Roles… to assign roles (AD login or Teleport user name; privileged accounts are refused). Teleport users can also get roles from mappings.
- Delegations › Simulator: choose an operator, a target user (or group), an action and an attribute, and see the real engine's answer: allowed or denied, the reason, the role that grants it, the target's classification, and whether AD would allow the service account too. "The application would allow it, but Active Directory would refuse" means the AD delegation must be exported and applied again. Simulations change nothing and are audited.
Roles can also be imported from a JSON file on the host (automation), with the same validation as the console:
docker exec <container> dotnet Steward.Api.dll import-roles /tmp/roles.json [--replace] (see Operations).
Apply the AD rights¶
The console computes, from the current roles, the exact AD rights its service accounts need, and exports them. A domain
administrator applies them with deploy/delegation/Grant-StewardDelegation.ps1.
- In the console: Configuration › Diagnostics › Export AD delegation. This downloads
steward-delegation.json(no passwords; only identities, OUs, rights and attributes). -
Give the file and the
deploy/delegation/folder to a domain administrator. On a machine with Windows PowerShell 5.1 and the RSAT ActiveDirectory module, preview:$ou = 'OU=Staff,DC=corp,DC=example' # every managed OU (Configuration > Directory) $ids = @{ read = 'svc-steward-read@corp.example' # every service account of the instance write = 'svc-steward@corp.example' reset = 'svc-steward-reset@corp.example' } # + laps = '...' when LAPS is used .\Grant-StewardDelegation.ps1 -ModelPath .\steward-delegation.json -ManagedOu $ou -Identity $ids -TierOu 'OU=Tier0,DC=corp,DC=example' -WhatIf -
Apply (the script prints the resolved plan and asks for confirmation first):
.\Grant-StewardDelegation.ps1 -ModelPath .\steward-delegation.json -ManagedOu $ou -Identity $ids -TierOu 'OU=Tier0,DC=corp,DC=example'Parameters:
-ModelPath(required, the exported file),-ManagedOu(required: the instance's managed OUs, DNs or objectGUIDs),-Identity(required: role → account, rolesread,write,reset,laps; UPN,sAMAccountNameorDOMAIN\sAMAccountName),-TierOu(optional: your administrative-tier OUs, refused with everything above and below them),-ReportPath(CSV report; default.\steward-delegation-report-<timestamp>.csv),-Server(a DC to use; default: the AD module's choice), and the standard-WhatIf/-Confirm(ConfirmImpact=High: it asks before the plan and before each OU;-Confirm:$falsefor automation).The file is not trusted. Type
-ManagedOuand-Identityfrom what you configured, not from the file: they must match the model exactly (same OUs by objectGUID, same roles, same accounts by SID, both ways), otherwise nothing changes. A tampered file that adds an OU or swaps an account is refused. -
Back in the console, Configuration › Diagnostics › Run diagnostics: every line should be green. The diagnostics read the effective rights from AD and change nothing.
The script writes a CSV report every time, also with -WhatIf. Each line names the OU, the identity, the right, the attribute and
an action: Added, WouldAdd (preview or declined), Present (already there, left alone), Info (for example an OU renamed
since the export), Skipped (OU not found).
What the script grants¶
Only what the roles use, on the OUs of their scopes, inherited by the right kind of object:
| Service account | Rights | When a delegation has |
|---|---|---|
| Write account | Write property on each delegated attribute (user objects) | users.attributes.write |
| Write account | Write property on member (group objects) |
groups.members.write, groups.owned.members.write |
| Write account | Create user objects on the OU + an OWNER RIGHTS ACE (read permissions only) | users.create |
| Write account | Create user objects, Delete on user objects, Write property on cn and name |
users.move |
| Write account | Write property on cn, name, displayName, givenName, sn (+ sAMAccountName, userPrincipalName) |
users.rename (+ users.rename.login) |
| Write account | Create group objects (+ OWNER RIGHTS), Delete on group objects, Write property on description, managedBy, mail, info |
groups.create, groups.delete, groups.attributes.write |
| Reset account | Reset Password and Write property on pwdLastSet; lockoutTime; userAccountControl |
helpdesk on, and users.password.reset; users.unlock; users.disable/users.enable (also users.create) |
| LAPS account | Read of msLAPS-Password (and legacy ms-Mcs-AdmPwd); Write property on the LAPS expiration time (computer objects) |
computers.laps.read; computers.laps.expire |
The read account gets nothing: authenticated users can read user objects by default. On a hardened domain, grant read access to
the read account on the managed OUs yourself. The full reference, with the reasoning behind each right, is in
deploy/delegation/README.md.
What the script never does¶
- Remove or change existing ACEs: it only adds the missing ones, so running it twice changes nothing.
- Delegate on an OU holding privileged objects: a managed OU whose sub-tree holds an
adminCount=1object, a domain controller or RODC account, a sensitive group or a (nested) member of one is refused, as is a managed OU at, above or below the Domain Controllers OU, the System container (AdminSDHolder) or any-TierOu. Delete is granted on the objects (never Delete child on the OU), so an object protected later can never be deleted or moved by the write account. - Accept a privileged service account: every identity is checked first, and an account with
adminCount=1, a non-emptysIDHistory, or a member (even nested, or through its primary group) of any group the console classifies as sensitive (Domain/Enterprise/Schema Admins, Administrators, Account/Server/Print/Backup Operators, Replicator, Cert Publishers, Key Admins, DC groups, DnsAdmins and any principal that can write the DNS containers, the Exchange groups found by name and by the Exchange well-known objects, ...) is refused. The reset account must differ from the write account; the LAPS account must differ from the read, write and reset accounts. - Grant outside the instance: the model (
steward.delegation/3) lists the managed OUs (which must match-ManagedOu), and a grant on any other OU is refused. Whether an OU is below another is decided by AD (objectGUID subtree search) and by parsed RDNs, never by comparing DN text. Older/1and/2files are refused: export again. - Grant an attribute outside its hard allow-list, whatever the file says: the write account only the console's catalog
attributes plus
cn,name,sAMAccountName,userPrincipalName(users) andmember,description,managedBy,mail,info(groups); the reset account onlypwdLastSet,lockoutTime,userAccountControland Reset Password. SomsDS-KeyCredentialLink,servicePrincipalName,altSecurityIdentities,scriptPath... are never granted. - Run on Samba: it needs Windows Server and the ActiveDirectory module.
When to export and apply again¶
The diagnostics compare the model of the last export with the model the current settings produce, and say "Roles changed since the export…: export and apply the AD delegation again" as soon as they differ. Typical cases:
- a role gains an action, an attribute or an OU. A removal also changes the model, but applying it is harmless: the script only adds rights, and Steward stops using a removed right at once. Remove the now-unused ACE in AD yourself if you want it gone;
- the helpdesk is enabled (reset, unlock, disable, enable, account creation);
- LAPS actions are delegated;
- the managed OUs change, or someone receives the Super administrator role.
Until the new file is applied, AD refuses the changes the service accounts lack, the simulator shows "AD would refuse", and the diagnostics list each missing right per OU (for example "AD refuses writing title (apply the AD delegation)").