Azure AD Sync
This guide covers everything an operator needs to set up, configure, and run the Azure AD (Microsoft Entra ID) synchronization in the RegScale CLI.
Overview
The Azure AD sync provisions RegScale users and group memberships from Microsoft
Entra ID. It is built around the App Management model: each RegScale App
gets its set of admins and AppGroup members from a small number of
naming-convention-based AD groups, with optional per-App overrides.
The sync was redesigned for App Management. The redesign:
- Replaced the legacy admin/general/read-only model with per-App sync.
- Introduced a deterministic naming convention with override support.
- Added a cascade-remove safety threshold to prevent accidental mass removals.
- Added per-App locking, dry-run mode, JSON reports, and structured exit codes.
- Deprecated the legacy
adAuthUrl/adClientId/adGraphUrl/etc. config keys
in favor of theazureEntra*family. The old keys are still read for one
release cycle so an un-migratedinit.yamlkeeps working — see Migration
from Legacy.
There are exactly four commands:
| Command | Purpose |
|---|---|
regscale ad authenticate | Acquire a token; verify required Graph scopes. |
regscale ad list_groups | Discover RegScale-* groups in Entra. |
regscale ad sync_app --app-id=N | Reconcile one App's admins and AppGroup members. |
regscale ad sync_tenant_admins --tenant-id=N | Reconcile tenant-wide Administrator role. |
The legacy commands (sync_admins, sync_general, sync_readonly) are gone.
See the Migration section at the end of this document if you are upgrading.
Concepts
Understanding the data model is the single most important part of a clean
operations experience.
| Concept | What it is | Where it lives |
|---|---|---|
| Atlas user | The platform-wide user record. Identified by externalId (Entra id) and tied to a single tenant. | User table in RegScale. |
| AppUser | A row that grants a specific Atlas user access to a specific App, with a status. | AppUser table; one row per (App, Atlas user). |
| AppGroup | A named group inside an App, e.g. Editors, Reviewers. AppUsers can belong to one or more AppGroups within their App. | AppGroup table per App. |
| Tenant Admin | A user with the platform-wide Administrator role in a tenant. Independent of any App. | User + role assignment. |
AppUser status values
The sync drives the AppUser record into one of these states:
| Status | Meaning |
|---|---|
Administrator | Full control of the App. Sourced from RegScale-{AppName}-Administrator. |
User | Normal access via AppGroup membership. Sourced from RegScale-{AppName}-{AppGroupName}. |
Pending | An open access request (or manually set to shield a user from the sync); the sync will not touch them. If an AD group references a Pending user, the sync does not approve the request — the user is listed in the report's requiresReview for a human decision. |
Rejected | Explicitly denied; the sync will not touch them. AD-requested membership lands in requiresReview. |
Deleted | Soft-deleted by cascade-remove. The user is preserved for audit but is not active in the App. Reviving an audit-preserved account is a human decision: if an AD group references a Deleted user, the sync lists them in requiresReview rather than re-activating them. |
How users are matched
Entra group members arrive as Entra object ids; RegScale App state is keyed
by atlas user ids. The sync joins the two through User.externalId: a tenant
user whose externalId equals the Entra object id is recognized as that
person, so they are added to the App or re-grouped — never re-created.
Two operational consequences:
- A RegScale account created manually (no
externalId) cannot be matched to
its Entra identity. If that person is in a synced AD group, the sync plans a
new user creation rather than reusing the manual account. Either set the
account'sexternalIdto the user's Entra object id, or keep them out of
synced AD groups. - Users the sync creates always carry
externalId(andldapUser: true), so
every subsequent run matches them correctly.
Cascade-remove
When an Atlas user is no longer present in any of the App's source AD
groups, the sync removes them from the App. To prevent runaway removals (e.g. a
Graph API outage returning empty membership), the cascade is gated on the
adCascadeRemoveThresholdPct config value. If the planned cascade exceeds the
threshold (as a percent of current AppUsers), the run halts with exit code 6
unless --force is passed.
Entra App Registration
Why: the sync uses application (daemon) permissions to read groups, group
members, and user details from Microsoft Graph. You need a registered
application in Entra ID with the correct API permissions and a valid client
secret.
Step-by-step
- Sign in to https://portal.azure.com as a user with permission to register
apps in your tenant. - Navigate to Azure Active Directory → App registrations → New
registration. - Name it something recognizable (e.g.
regscale-cli-ad-sync). Leave the
default account type (single tenant) unless you have a multi-tenant need.
No redirect URI is required. - Click Register. From the Overview page, note the Application (client)
ID and Directory (tenant) ID. - Open API permissions → Add a permission → Microsoft Graph →
Application permissions (NOT Delegated). Add:Group.Read.AllGroupMember.Read.AllUser.Read.All
- Click Grant admin consent for . The status column should
show a green check next to each permission. - Open Certificates & secrets → New client secret. Choose an
expiration appropriate for your rotation policy (recommended: 12 or 24
months). Click Add and immediately copy the Value column — it is
only displayed once. - Store the secret in your secrets manager. Schedule a calendar reminder for
secret rotation at least 30 days before expiry.
You now have three values:
- Client ID (a.k.a. Application ID)
- Tenant ID (a.k.a. Directory ID)
- Client Secret (the Value, not the Secret ID)
init.yaml Configuration Reference
init.yaml Configuration ReferenceAll AD-related config keys live in the same init.yaml that the rest of the
CLI uses. Run regscale init once to scaffold the file.
Required keys
| Key | Type | Default | Purpose |
|---|---|---|---|
azureEntraClientId | string | none | Application (client) ID from the App Registration. |
azureEntraSecret | string | none | Client secret value (NOT the secret ID). |
azureEntraTenantId | string | none | Directory (tenant) ID from the App Registration. |
The azureEntra* keys are shared with the Microsoft Defender Entra evidence
commands (regscale defender collect_entra_evidence); both features
authenticate with the same Entra app registration, so grant it the union of
the Graph permissions each needs.
If any required key is missing or still holds the scaffolded placeholder value
(YOUR_CLIENT_ID, YOUR_SECRET, YOUR_TENANT_ID), the CLI exits with code
2 and a clear message.
Managed keys
| Key | Type | Default | Purpose |
|---|---|---|---|
azureEntraAccessToken | string | empty | Cached access token. The CLI manages this automatically; you do not set it manually. The sync acquires a fresh token at every invocation and never persists it. |
Behavior keys
| Key | Type | Default | Purpose |
|---|---|---|---|
adIncludeDisabledUsers | bool | false | Include users whose Entra accountEnabled=false. Set to true if you intentionally provision disabled accounts. |
adFollowNestedGroups | bool | true | Resolve nested group membership via Graph transitiveMembers. Set to false only if your tenant has flat groups and you want a small perf win. |
adCascadeRemoveThresholdPct | int (0-100) | 25 | Cascade-remove safety gate. If the planned removal percent meets or exceeds this, the sync halts and demands --force. |
Mapping keys
| Key | Type | Default | Purpose |
|---|---|---|---|
adGroupOverrides | list of mappings | [] | Explicit AD-group → (App, AppGroup) mappings for groups that don't follow the convention. |
adTenantAdminGroups | mapping (int → string) | {} | Per-tenant override for the AD group used by sync_tenant_admins. The scaffolded YOUR_TENANT_ID: YOUR_AD_GROUP_NAME example entry is ignored until replaced with real values. |
Full example
# Required Entra credentials.
azureEntraClientId: "11111111-2222-3333-4444-555555555555"
azureEntraSecret: "your-client-secret-value"
azureEntraTenantId: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
# Managed by the CLI. Leave blank.
azureEntraAccessToken: ""
# Behavior.
adIncludeDisabledUsers: false
adFollowNestedGroups: true
adCascadeRemoveThresholdPct: 25
# Optional override map.
adGroupOverrides:
- adGroupId: "00000000-0000-0000-0000-000000000001"
appId: 42
appGroupId: 7
- adGroupId: "00000000-0000-0000-0000-000000000002"
appId: 42
# appGroupId omitted means "App admin"
# Optional tenant-admin source group per tenant.
adTenantAdminGroups:
1: "RegScale-Administrators"
2: "RegScale-FedRAMP-Administrators"
Naming Convention
The sync resolves Entra groups to RegScale targets using a strict convention.
Stick to the convention and you do not need any overrides.
Format
| AD group display name | Resolves to |
|---|---|
RegScale-{AppName}-Administrator | App admin (sets AppUser status to Administrator). |
RegScale-{AppName}-{AppGroupName} | AppGroup membership (status User). |
{AppName} matches the App's name field exactly (case-insensitive).
{AppGroupName} matches an AppGroup's name within that App (case-insensitive
exact match).
Concrete examples
For an App named FedRAMP with AppGroups Editors and Reviewers:
RegScale-FedRAMP-Administrator -> Sets these users as App admins.
RegScale-FedRAMP-Editors -> Adds users to the "Editors" AppGroup.
RegScale-FedRAMP-Reviewers -> Adds users to the "Reviewers" AppGroup.
RegScale-FedRAMP-Auditors -> UNRESOLVED (no "Auditors" AppGroup).
For an App named My Awesome App (note the space) with AppGroup Reviewers:
RegScale-My Awesome App-Reviewers -> Adds users to "Reviewers".
If your AD provider does not allow spaces, create an adGroupOverrides entry
that maps the dashed-name AD group to the correct App.
Resolution order
The resolver follows this exact precedence for each Entra group it encounters:
- Override map. If
adGroupOverridescontains an entry with a matching
adGroupId, the override wins. IfappGroupIdis omitted, the group is
treated as the App admin source. - Convention prefix. Names not starting with
RegScale-are skipped. - App name match. The body must start with
{AppName}-(case-insensitive)
for the App being synced. Otherwise skipped. - Administrator sentinel. If the remainder is
Administrator
(case-insensitive), the group is the App admin source. - AppGroup name lookup. Otherwise the remainder is matched against the
App's AppGroups. A hit becomes that AppGroup's membership source. - Unresolved. No match — written to the report's
unresolvedlist.
Action: create the AppGroup in App Manager, or add an override.
Override Map
When to use it:
- The AD provider can't represent the convention (e.g. spaces, max-length
limits). - You inherited legacy AD groups that you don't want to rename.
- One AD group needs to feed two different Apps (use two override entries with
differentappId).
Schema
Each entry in adGroupOverrides is a mapping with:
| Field | Type | Required | Purpose |
|---|---|---|---|
adGroupId | string | yes | Entra group objectId (GUID). |
appId | int | yes | Target App ID. |
appGroupId | int | no | Target AppGroup ID. Omit to mark the override as the App admin source. |
adGroupId must be unique across all entries.
Examples
App admin override (the AD group called Legacy-FedRAMP-Admins should drive
admins for App 42):
adGroupOverrides:
- adGroupId: "00000000-0000-0000-0000-000000000001"
appId: 42
# appGroupId omitted -> App admin
AppGroup override (a flat AD group LegacyEditors should populate AppGroup
ID 7 in App 42):
adGroupOverrides:
- adGroupId: "00000000-0000-0000-0000-000000000002"
appId: 42
appGroupId: 7
If an override targets a different appId than the one currently being
synced, the resolver returns a SKIP for that group on this run — no error,
just a quiet skip.
Per-App App.CliConfig
App.CliConfigWhy: in multi-App deployments, different Apps may need different Entra
tenants, different cascade thresholds, or different override maps.
The RegScale App entity carries an encrypted cliConfig field (a YAML
string). When regscale ad sync_app --app-id=N runs, it loads the global
init.yaml, then merges the App's cliConfig on top.
Merge semantics
- Per-App values override global values key-by-key.
- Lists (
adGroupOverrides) replace — they do not append. - Maps (
adTenantAdminGroups) replace at the top level. - Empty or unset
cliConfigreturns the global config unchanged. - Invalid YAML in
cliConfigraisesConfigError(exit 2).
Example per-App YAML
A cliConfig for App ID 42 that overrides only the cascade threshold and the
override list:
adCascadeRemoveThresholdPct: 50
adGroupOverrides:
- adGroupId: "00000000-0000-0000-0000-000000000099"
appId: 42
appGroupId: 12
Everything else (Entra credentials, follow-nested, etc.) inherits from
init.yaml.
Tenant Admin Sync
sync_tenant_admins is independent from sync_app. It manages the tenant-wide
Administrator role only — it does not touch any App or AppGroup.
When to use which
| Scenario | Command |
|---|---|
| User needs admin rights inside one App. | sync_app --app-id=N, source group RegScale-{AppName}-Administrator. |
| User needs platform-wide admin in a tenant. | sync_tenant_admins --tenant-id=N. |
Multi-tenant deployments
--tenant-id is required because the same RegScale instance may host multiple
tenants and each tenant has its own admin pool. The adTenantAdminGroups
mapping in init.yaml declares the source AD group per tenant.
Source AD group resolution
The source group name resolves in this order:
--ad-group "<name>"flag (highest precedence).adTenantAdminGroups[<tenant_id>]from config.- Default:
RegScale-Administrators.
--deactivate-removed
--deactivate-removedBy default, removing the admin role only revokes the role — the user record is
preserved. Pass --deactivate-removed to also deactivate the user account
when the admin role is removed. Use this only if your policy is "no admin
group membership = no platform access".
Example
# Tenant 1, default AD group, dry-run first.
regscale ad sync_tenant_admins --tenant-id=1 --dry-run
# Apply, deactivating users whose admin role was revoked.
regscale ad sync_tenant_admins --tenant-id=1 --deactivate-removed
# Tenant 2, explicit AD group override.
regscale ad sync_tenant_admins --tenant-id=2 --ad-group "RegScale-FedRAMP-Administrators"
Worked End-to-End Example
A complete walkthrough for a brand-new App.
1. Pre-requisites
In RegScale:
- Create the App. Name it
FedRAMP(or whatever; the name matters because of
the convention). - Inside the App, create the AppGroups you want:
Editors,Reviewers,
Auditors.
In Entra:
- Complete the App Registration steps above.
- Create AD groups using the convention:
RegScale-FedRAMP-AdministratorRegScale-FedRAMP-EditorsRegScale-FedRAMP-ReviewersRegScale-FedRAMP-Auditors
- Populate them with members. Nested groups are fine.
In init.yaml:
azureEntraClientId: "<your-client-id>"
azureEntraSecret: "<your-secret>"
azureEntraTenantId: "<your-tenant-id>"
adFollowNestedGroups: true
adCascadeRemoveThresholdPct: 25
2. Authenticate
regscale ad authenticate
Expected output:
Azure AD authentication successful.
Required Graph scopes verified: Group.Read.All, GroupMember.Read.All, User.Read.All.
If this fails, see the Troubleshooting section.
3. Discover groups
regscale ad list_groups
This writes artifacts/azure_ad_sync/groups.json and lists every group whose
display name starts with RegScale-. Confirm your four groups are listed and
have the expected objectId values.
You can scope the discovery:
regscale ad list_groups --prefix "RegScale-FedRAMP-"
4. Dry-run sync
Always dry-run first on production. Assume the App ID is 42:
regscale ad sync_app --app-id=42 --dry-run
The command:
- Acquires a Graph token.
- Snapshots RegScale state from four reads: the App's group list, each
group's membership (GET /api/apps/groups/{groupId}— the only route that
carries members), the App's pending/active users, and the App's
administrators (GET /api/apps/{id}/detailed). - Fetches the tenant's user list to map Entra object ids to atlas user ids
viaUser.externalId(see "How users are matched"). - Snapshots
RegScale-FedRAMP-*group members from Entra. - Computes a plan of creates / promotions / demotions / group adds / group
removes / cascade-removes. - Writes
artifacts/azure_ad_sync/FedRAMP-<timestamp>.jsonwith the full
plan. - Prints summary counts and exits 0.
Inspect the report. The counts object should match your expectations. Pay
attention to:
counts.cascadeRemoves— should be near zero on a healthy run.unresolved— should be empty if your AppGroups are correctly named.requiresReview— anything the sync flagged for human attention.
5. Apply
If the plan looks correct:
regscale ad sync_app --app-id=42
The CLI takes a per-App lock, applies the plan, writes a fresh report, and
exits.
6. Verify idempotency
Re-run immediately:
regscale ad sync_app --app-id=42
The new report should show all counts at zero (or very low — accounting for
genuine changes since the last run). Idempotency is the basic health check
for the sync.
7. Schedule
Once dry-runs and live runs both look clean, schedule the sync. Examples:
- A cron job on a sidecar host.
- An ROH DAG (see next section).
- A serverless function fired daily.
ROH (Orchestration Hub) Pattern
The ROH model expects one DAG per App. The DAG passes the App ID to the CLI
and the per-App cliConfig provides any deviations from global defaults.
Why per-App DAGs
- Independent failure domains: one App's Graph rate limit doesn't break
another App's sync. - Independent schedules: a fast-moving project App syncs hourly; a stable
archive App syncs weekly. - Independent on-call: each App's owner sees only their reports and alerts.
Minimal task snippet
Any scheduler that can run a shell command works. The task for App 42 is just:
regscale ad sync_app --app-id=42
The task itself does not need the Entra credentials. They live in init.yaml
on the worker; secret values can come from your usual secret store
(environment variable, AWS Secrets Manager, etc.).
If App 42 needs custom config, set its App.cliConfig field via the
RegScale API:
adCascadeRemoveThresholdPct: 40
The CLI will merge that on top of init.yaml for this App's runs only.
Operations Runbook
First run on production
Always:
regscale ad authenticateto confirm token + scopes.regscale ad list_groupsto confirm Entra discovery sees what you expect.regscale ad sync_app --app-id=N --dry-runand read the report.- Only then run without
--dry-run.
Cascade-remove gate triggered
Symptom: the run exits with code 6 and the message:
Cascade-remove gate triggered: 137 users would be removed (>= 25%).
Pass --force to proceed.
Investigation:
- Open the JSON report at
artifacts/azure_ad_sync/<app>-<ts>.json. - Inspect
counts.cascadeRemoves. Are these legitimate departures? - Check the AD groups directly in Entra. Did someone accidentally empty a
group or change its name? - If the change is legitimate (e.g. a real reorg), re-run with
--force. - If not, fix the AD-side problem first, then re-run without
--force.
Unmanaged (manually added) members
The sync emits no dedicated drift log line. An AppGroup member who is not
sourced from any AD group shows up in the plan instead: they are counted as
cascade-remove candidates (counts.cascadeRemoves in the report) unless
their AppUser status is Pending or Rejected, which the sync never
touches. Common causes:
- Manually added in App Manager.
- Imported from a previous tool.
Action: decide policy. If everything must be AD-sourced, let the cascade
remove them (or remove them manually). If manual additions are allowed, set
their AppUser status to Pending so the sync ignores them.
Lock file held
Symptom: exit 7 with message:
Sync for App 42 is already running (lock held by PID 1234 since 2026-05-09T08:00:00Z).
Real concurrent run: another sidecar / DAG is currently syncing App 42. Wait
for it.
Stale lock: if PID 1234 is dead or the timestamp is more than 4 hours old,
the lock auto-evicts on the next invocation. If you need to force-clear it
sooner, delete the file:
rm artifacts/azure_ad_sync/.lock-app-42
Only do this if you have confirmed no real run is in flight.
Report files
Location: artifacts/azure_ad_sync/<app-name>-<ISO-timestamp>.json.
Written by sync_app only. sync_tenant_admins prints its plan summary and
error count to stdout and does not write a JSON report.
Top-level fields:
| Field | Meaning |
|---|---|
kind | Always azure_ad_sync_app. |
appId, appName, tenantId | Identity of the target. |
correlationId | UUID for this run; include in support tickets. |
startedAt, completedAt | UTC timestamps. |
dryRun, force | Run-mode flags. |
counts | Map of phase → count: usersCreated, usersAddedToApp, promotedToAdmin, demotedToUser, groupAdds, groupRemoves, cascadeRemoves, unresolved, errors. |
unresolved | AD groups that did not resolve (adGroupId, displayName, reason). |
requiresReview | Items that need manual attention (atlasUserId, reason). |
errors | Per-item apply failures (always empty in dry-run). |
The --redact flag hashes Atlas user IDs (SHA-256, first 12 chars) — useful
when shipping reports to a non-trusted analytics pipeline.
SIGTERM during apply
The apply phase is per-item with per-item error capture. If the process is
killed mid-apply:
- Already-applied changes persist (the platform writes them as it goes).
- The lock file is left behind. The next run evicts it as stale or
force-removes it as described above. - Re-running the sync converges — anything that didn't apply on the killed
run is included in the next run's plan.
There is no "rollback" mode. The reconciler is designed to be idempotent.
Troubleshooting
Exit codes
These codes predate the CLI-wide contract in exit-codes.md and are kept for compatibility.
| Code | Class | Cause | Fix |
|---|---|---|---|
| 0 | success | Clean run. | — |
| 2 | ConfigError | Missing or malformed config; App ID not found. | Check init.yaml, confirm App ID exists, validate adGroupOverrides schema. |
| 3 | AuthError | Token acquisition failed; missing Graph scopes. | Verify azureEntraClientId/Secret/TenantId. Check secret hasn't expired. Re-grant admin consent. |
| 4 | EntraApiError / generic | Graph API non-2xx after retries. | Check rate limits, scope grants, network egress to Graph. |
| 5 | RegScaleApiError / AppApiError | RegScale platform non-2xx after retries, or a platform response whose shape the CLI's models could not parse. | Check platform connectivity, auth token, server logs. For a shape error, the message names the endpoint and fields — the platform contract may have drifted; regenerate the schema snapshot (make azure-ad-sync-regenerate-platform-schema) and compare. |
| 6 | PartialApplyError / threshold | Some items failed to apply, OR cascade threshold tripped. | Open the report. Fix per-item errors and re-run; or pass --force if the cascade is intentional. |
| 7 | LockHeldError | Another sync is running for this App. | Wait or clear stale lock. |
Common errors
Configuration error: Required key 'azureEntraSecret' is missing or unconfigured
Configuration error: Required key 'azureEntraSecret' is missing or unconfiguredThe required Entra key is empty or set to its sentinel default. Open
init.yaml and populate the value, or run regscale init and follow the
prompts.
Authentication failed: ...
Authentication failed: ...Most common causes:
- Wrong client ID, tenant ID, or secret value (vs. secret ID).
- Secret expired. Issue a fresh secret in Azure Portal and update
azureEntraSecret. - Admin consent not granted. Re-open the App Registration → API permissions
→ Grant admin consent.
EntraApiError: Microsoft Graph returned 429
EntraApiError: Microsoft Graph returned 429Rate limited. The CLI retries automatically. If it still fails, reduce sync
frequency or split the work across more Apps.
EntraApiError: 403 Forbidden
EntraApiError: 403 ForbiddenA required Graph scope is missing. Verify the App Registration has
Group.Read.All, GroupMember.Read.All, User.Read.All as Application
permissions (not Delegated) and that admin consent is granted.
AppGroup 'X' not found in app 'Y' (in unresolved)
AppGroup 'X' not found in app 'Y' (in unresolved)The convention found RegScale-Y-X but App Y has no AppGroup named X.
Fix:
- Create the AppGroup
Xin App Manager, or - Rename the AD group to a valid AppGroup name, or
- Add an
adGroupOverridesentry mappingX'sobjectIdto a different
AppGroup ID.
Lock held: Sync for App N is already running
Lock held: Sync for App N is already runningSee the Lock File Held runbook entry.
App N not found
App N not foundThe provided --app-id does not exist or your auth token can't see it. Verify
the App ID and check that your RegScale credentials in init.yaml are
correct.
Migration from Legacy
If you ran the pre-redesign sync, your init.yaml likely contains the old
keys and your scheduled jobs likely call the old commands.
Config key mapping
The legacy ad* keys are deprecated but still honored for one release
cycle. If an azureEntra* key is missing or still holds its scaffolded
YOUR_* placeholder, the CLI copies the value from the matching legacy key so
an un-migrated init.yaml keeps working. A populated azureEntra* key always
wins over its legacy counterpart. Either way the CLI emits a deprecation
warning naming every legacy key it found and the rename to perform.
This is a compatibility shim, not the supported schema — rename the keys
now. The shim will be removed, and when it is, an init.yaml that still
carries only ad* keys will fail every regscale ad command with
Configuration error: Required key 'azureEntraClientId' is missing or
unconfigured.
| Legacy key | New key | Notes |
|---|---|---|
adAuthUrl | — | Removed. The CLI hard-codes the Microsoft identity platform URL. Delete it. |
adAccessToken | azureEntraAccessToken | Now managed automatically; do not set. |
adClientId | azureEntraClientId | Same value, new key name. |
adClientSecret | azureEntraSecret | Same value, new key name. |
adGraphUrl | — | Removed. Hard-coded to https://graph.microsoft.com/v1.0. Delete it. |
adTenantId | azureEntraTenantId | Same value, new key name. |
Action: copy the values, rename the keys, delete the old keys. This applies to
any external tooling or CI pipeline that generates init.yaml as well — the
generator must emit the new key names before the shim is removed.
Command mapping
The legacy commands are removed. Invoking them produces a stub that prints
the replacement command and exits with code 2, so scripts fail fast with
guidance instead of a bare "no such command". The stubs stay registered for as
long as the legacy ad* config keys are honored above — they serve the same
operators, and retiring one without the other strands half the migration.
Only sync_admins has a like-for-like successor. sync_general and
sync_readonly assigned tenant-wide SystemRoles; sync_app reconciles
membership of a single App's AppGroups. That is a different model, and
swapping the command alone will not reproduce the old behavior — the App and
its AppGroups have to exist in RegScale first (migration checklist step 3
below).
| Legacy command | New command(s) | Drop-in? |
|---|---|---|
regscale ad sync_admins | regscale ad sync_tenant_admins --tenant-id=N for each tenant. | Yes, once per tenant instead of one tenant-wide pass. |
regscale ad sync_general | regscale ad sync_app --app-id=N for each App. | No. The tenant-wide GeneralUser role is replaced by per-App AppGroup membership; create the App and its AppGroups first. |
regscale ad sync_readonly | regscale ad sync_app --app-id=N for each App. | No. Read-only is now an AppGroup role on a specific App, not a tenant-wide ReadOnly role; create the App and a read-only AppGroup first. |
Operators running sync_general or sync_readonly from CI therefore have
platform configuration work to do, not just a command rename.
Migration checklist
- Update
init.yaml: rename keys, remove deprecated keys. - Re-run
regscale ad authenticateto confirm credentials still work. - List Apps and decide which need AD-driven membership. For each:
- Confirm AppGroups exist in App Manager with the names you want.
- Create matching
RegScale-{AppName}-{AppGroupName}AD groups in Entra. - Add an
App.cliConfigonly if this App needs deviations from the
global config.
- Update each scheduled job:
- One job per App:
regscale ad sync_app --app-id=N. - One job per tenant:
regscale ad sync_tenant_admins --tenant-id=N.
- One job per App:
- Run all new jobs in
--dry-runfirst; review the reports; then go live. - Decommission the legacy job definitions.
If you have many Apps and are not yet sure they're all wired up, the override
map is your friend during transition. You can map the old monolithic
RegScale-Users group to specific AppGroups across multiple Apps without
renaming a single AD group.
Updated about 2 hours ago
