Training module · Draft
Attribute-Driven Migration, Module 7: Extend Lifecycle Workflows
Build the Logic App a custom task extension calls, so it calls back to the right cloud, reports failure at once, does its work once, and runs only after the leaver is blocked
Status — Draft
This page is still being written. Expect gaps and changes; it is not part of the workbook's production guidance. Last reviewed 2026-11-07.
Learning path: Building the Attribute-Driven Migration · Module 7 of 9 · 50 minutes · Guide Gate 7 and Gate 8 · Workbook Chapter 05
Watch: the narrated deck for this module: Deck · ▶ Watch (about 14 minutes, captions) · MP4 · Script · Captions.
Also: the chapter’s narrated tour, about 12 minutes: ▶ Watch.
Unit 1: Introduction
5 minutes, with the objectives and prerequisites
Gate 7 proves one personnel action changes access only through the attributes, with the mover workflow’s run history as evidence. Gate 8 puts leaver automation first in every wave and sizes Lifecycle Workflows for the agency. The built-in tasks cover the account. The rest of a leaver, such as the PIV credential, the devices and the servers, goes through a custom task extension that calls a Logic App. Learn’s definition calls back to the worldwide Graph on beta, and always says it completed. In this module you see what that costs a GCC High leaver, and run the definition that fixes it.
This module comes after Modules 4 to 6 because the path follows the gates: Chapter 05 serves Gates 7 and 8, after the Gate 5 work.
The federal requirement. A custom task extension carries the part of a leaver the built-in tasks don’t, so its evidence is account and credential removal. From Chapter 05’s table:
| Requirement | KSI | Evidence the ISSO keeps |
|---|---|---|
| AC-2; PS-4 Personnel Termination: the callback reports what really happened, at once, and a repeat delivery has one effect | KSI-IAM-AAM | The task processing result in Lifecycle Workflows |
| CM-3(2): the program’s definition passes, and retries that reach the timeout are refused | KSI-CMT-VTD | The pull request’s check run |
| CM-2; CM-6; AC-3: the deployment pieces are built from the reviewed registry, SAS off and proof-of-possession on | KSI-CMT-RMV; KSI-IAM-SNU | The repository and the deployment record |
| CM-6; PS-4; AC-2: the extension read back matches the registry, and the leaver blocks before the custom task | KSI-MLA-EVC; KSI-IAM-AAM | The check output, filed with the change |
| AC-6: the callback identity holds no tenant-wide Lifecycle Workflows permission | KSI-IAM-ELP; KSI-IAM-SNU | The extension’s authorizedApps, and the check output |
| AU-12 Audit Record Generation; AC-2: an on-demand run completes, and run history holds no personal data (Lab 2) | KSI-MLA-LET | The Logic App run history and the task processing result |
Key Security Indicators are FedRAMP 20x indicators for cloud service providers. The agency authorizes its own system and lines this evidence up with what its providers report against them; it doesn’t report KSIs itself (Book 00, Appendix B). Which output supports which control is the program’s reading (design guidance).
Learning objectives
In this module, you’ll:
- Describe where custom task extensions sit in Book 03’s leaver, and the three rules this chapter builds in code.
- Explain what Learn’s Logic App definition leaves to you.
- Run a definition for a cloud, through transient failures and repeat deliveries, and read what Lifecycle Workflows records.
- Build the deployment pieces from the registry, and explain why an Azure Government build is refused.
- Check a workflow read-back for the leaver order.
Prerequisites
- Module 1: provisioning creates and updates accounts; Lifecycle Workflows act on accounts that exist.
- Python 3.10 or later, and a POSIX shell.
Unit 2: Where the extensions sit, and what Learn’s definition leaves to you
8 minutes
One parameterized Logic App per integration, not one per component. Block first: Disable user account and Revoke all refresh tokens for user before the custom task. Custom tasks are idempotent and retry with a bound. Learn’s definition resumes the task with a managed identity and no secret, which the program keeps; it also hard-codes the worldwide Graph host, calls back on beta, uses the callback path unchecked, and always reports completed. Read:
Unit 4: Exercise: Lab 1 (fixtures, no tenant)
22 minutes
Every command in this lab, in order, from examples/lifecycle-extensions. Each step below says what it does and what to look for.
# Step 1: Extract and lint Learn's definition
python tools/lcwext.py learn-sample
python tools/lcwext.py lint out/learn-sample.workflow.json --timeout PT1H
# Step 2: Run Learn's definition for a GCC High tenant
python tools/lcwext.py run out/learn-sample.workflow.json tests/fixtures/trigger-leaver.json --cloud GCCHigh
# Step 2: Run Learn's definition for a GCC High tenant
python tools/lcwext.py run out/learn-sample.workflow.json tests/fixtures/trigger-leaver.json --cloud Global
# Step 3: Lint the program's definition
python tools/lcwext.py lint logicapp/integration.workflow.json --timeout PT1H
python tools/lcwext.py lint logicapp/integration.workflow.json --timeout PT15M
# Step 4: Run it for GCC High through two transient failures
python tools/lcwext.py run logicapp/integration.workflow.json tests/fixtures/trigger-leaver.json --cloud GCCHigh --fail 503,503
python tools/lcwext.py run logicapp/integration.workflow.json tests/fixtures/trigger-leaver.json --cloud GCCHigh --fail 503,503,503,503,503
python tools/lcwext.py run logicapp/integration.workflow.json tests/fixtures/trigger-leaver.json --cloud GCCHigh --deliveries 2
# Step 5: Build the deployment pieces
python tools/lcwext.py build config/integrations.json --name cms-revocation --tenant-id 6e3a1f2b-8c4d-4e5f-9a0b-1c2d3e4f5a6b
# Step 5: Build the deployment pieces
sed -i 's/"azureCloud": "AzureCloud"/"azureCloud": "AzureUSGovernment"/' config/integrations.json
python tools/lcwext.py build config/integrations.json --name cms-revocation --tenant-id 6e3a1f2b-8c4d-4e5f-9a0b-1c2d3e4f5a6b
sed -i 's/"azureCloud": "AzureUSGovernment"/"azureCloud": "AzureCloud"/' config/integrations.json
# Step 6: Check the read-backs
python tools/lcwext.py check-extension config/integrations.json --name cms-revocation tests/fixtures/extension-get.json
python tools/lcwext.py check-workflow config/integrations.json --name cms-revocation --extension-id c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e01 tests/fixtures/workflow-get-leaver.json
# Step 6: Check the read-backs
sed 's/"executionSequence": 3, "displayName": "CMS/"executionSequence": 0, "displayName": "CMS/' tests/fixtures/workflow-get-leaver.json > workflow-seq0.json
python tools/lcwext.py check-workflow config/integrations.json --name cms-revocation --extension-id c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e01 workflow-seq0.json
# Step 7: Run the tests
python -m unittest discover -s tests -v
# Step 8: Clean up
rm -rf out workflow-seq0.jsonYou lint and run Learn’s definition and the program’s, build the deployment pieces, and check the read-backs. Work in examples/lifecycle-extensions. Nothing signs in. These are the steps of Chapter 05, Lab 1, which explains each result.
Extract and lint Learn’s definition.
python tools/lcwext.py learn-sample python tools/lcwext.py lint out/learn-sample.workflow.json --timeout PT1HExpected: eight results and exit 2. Five are findings:
LX-03(hard-codesgraph.microsoft.com),LX-04(calls back on/beta),LX-06(path not checked),LX-07(no callback when the work fails) andLX-08(always reportsCompleted). TwoLX-10warnings are for run history, and oneLX-11warning for the implicit retry policy.Run Learn’s definition for a GCC High tenant.
python tools/lcwext.py run out/learn-sample.workflow.json tests/fixtures/trigger-leaver.json --cloud GCCHighExpected: the one
POSTgoes tohttps://graph.microsoft.com/beta/..., and the last line isLifecycle Workflows task: failed (no callback within PT1H). Run it again for a worldwide tenant:python tools/lcwext.py run out/learn-sample.workflow.json tests/fixtures/trigger-leaver.json --cloud GlobalExpected:
Lifecycle Workflows task: completed.Lint the program’s definition, then with a 15-minute timeout:
python tools/lcwext.py lint logicapp/integration.workflow.json --timeout PT1H python tools/lcwext.py lint logicapp/integration.workflow.json --timeout PT15MExpected:
0 result(s); no findings, thenLX-11 Finding retryPolicy worst-case retry waits (960s) reach the callback timeout PT15M, exit 2.Run it for GCC High through two transient failures, then five, then with two deliveries:
python tools/lcwext.py run logicapp/integration.workflow.json tests/fixtures/trigger-leaver.json --cloud GCCHigh --fail 503,503 python tools/lcwext.py run logicapp/integration.workflow.json tests/fixtures/trigger-leaver.json --cloud GCCHigh --fail 503,503,503,503,503 python tools/lcwext.py run logicapp/integration.workflow.json tests/fixtures/trigger-leaver.json --cloud GCCHigh --deliveries 2Expected, the last two lines of each run:
downstream: 3 call(s), 1 effect(s) Lifecycle Workflows task: completeddownstream: 5 call(s), 0 effect(s) Lifecycle Workflows task: faileddownstream: 2 call(s), 1 effect(s) Lifecycle Workflows task: completedThe callbacks go to
https://graph.microsoft.us/v1.0/.... With five failures the task fails at once, not at the end of the wait. With two deliveries the work is done once.Build the deployment pieces.
python tools/lcwext.py build config/integrations.json --name cms-revocation --tenant-id 6e3a1f2b-8c4d-4e5f-9a0b-1c2d3e4f5a6bExpected:
wrote out/arm-cms-revocation.jsonandwrote out/extension-cms-revocation.json. Set the Logic App’s cloud to Azure Government, build again, and put it back:sed -i 's/"azureCloud": "AzureCloud"/"azureCloud": "AzureUSGovernment"/' config/integrations.json python tools/lcwext.py build config/integrations.json --name cms-revocation --tenant-id 6e3a1f2b-8c4d-4e5f-9a0b-1c2d3e4f5a6b sed -i 's/"azureCloud": "AzureUSGovernment"/"azureCloud": "AzureCloud"/' config/integrations.jsonExpected: the second build exits 1 with
error: cms-revocation: Learn documents the PoP policy only for Azure commercial; no values for AzureUSGovernment (research gap), so nothing is built.Check the read-backs.
python tools/lcwext.py check-extension config/integrations.json --name cms-revocation tests/fixtures/extension-get.json python tools/lcwext.py check-workflow config/integrations.json --name cms-revocation --extension-id c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e01 tests/fixtures/workflow-get-leaver.jsonExpected:
0 result(s); no findingstwice. Now move the custom task to the front of a copy of the workflow, and check the copy:sed 's/"executionSequence": 3, "displayName": "CMS/"executionSequence": 0, "displayName": "CMS/' tests/fixtures/workflow-get-leaver.json > workflow-seq0.json python tools/lcwext.py check-workflow config/integrations.json --name cms-revocation --extension-id c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e01 workflow-seq0.jsonExpected output (exit 2):
WX-03 Finding tasks 'Disable user account' doesn't run before the custom task (Book 03: block first) WX-03 Finding tasks 'Revoke all refresh tokens for user' doesn't run before the custom task (Book 03: block first) 2 result(s); findingsRun the tests.
python -m unittest discover -s tests -vExpected:
Ran 55 tests,OK.Clean up.
rm -rf out workflow-seq0.json
Check your work: for any run of the extension, you can say where its callback goes, what Lifecycle Workflows records, and whether the leaver was blocked first.
Unit 5: Exercise: Lab 2 (your test tenant)
Not timed. Not run by the authors.
Create the extension and its Logic App in the admin center, record what the admin center made and check it against the registry, replace the definition with the program’s, build an on-demand test leaver workflow that blocks first, run it for one fictional user, and drill the failure path. Follow Chapter 05, Lab 2.
- You need: a test tenant with Microsoft Entra ID Governance licenses and a test Azure subscription, never production; Lifecycle Workflows Administrator, and Logic App Contributor, Contributor or Owner on the resource group.
- Back it out: step 7 removes the custom task and the workflow, then the extension, the Logic App, the resource group, the role assignments and the test users.
Unit 6: Knowledge check
5 minutes
- Learn’s definition runs for a GCC High leaver. What does Lifecycle Workflows record?
- Completed.
- Failed, at the end of the wait, because the callback went to the worldwide Graph on beta.
- Failed at once.
- The downstream system fails five times in a row. What does the program’s definition do?
- It retries until the wait ends.
- It calls back
failed, so the task fails at once. - It calls back
completed.
- Why does the builder refuse a Logic App in Azure Government?
- Logic Apps don’t run there.
- Learn documents the proof-of-possession policy only for Azure commercial, so the builder refuses rather than guess.
- The registry doesn’t allow it.
- What does WX-03 report?
- The custom task runs before Disable user account or Revoke all refresh tokens for user.
- The extension uses a bearer token.
- The workflow isn’t in the registry.
- b. See what Learn’s definition leaves to you and step 2 of the lab.
- b. See step 4 of Chapter 05, Lab 1.
- b. See authorization, built and checked and step 5 of the lab.
- a. See the leaver order and step 6 of the lab.
Unit 7: Summary
2 minutes
You ran a custom task extension’s definition for the right cloud, through failures and repeat deliveries, built its deployment pieces from a reviewed registry, and checked that the leaver blocks before the custom task runs.
Evidence an ISSO keeps from this module: the registry and the built ARM and extension files in the repository, the check output filed with the change, and in Lab 2 the task processing results, the Logic App run history, the drill record and the activity logs. Gate 7’s evidence, the mover workflow’s run history joined on the key, is in the guide’s Gate 7. Before shipping, work through the Chapter 05 checklist.