Zero Trust Assessment pack
This pack runs Microsoft’s Zero Trust Assessment and shows each of its results in the Maester report, next to Maester’s own checks. It ships with Maester and does not run unless you ask for it.
Each assessment result is one row, ZTA.<assessment test ID> (for example ZTA.21770), with the
assessment’s title, risk level (as the severity), description, remediation steps and findings. Results are tagged
ZeroTrust and ZeroTrust.<pillar> (ZeroTrust.Identity, ZeroTrust.Devices and so on).
Maester also places each result under the product, feature and security area it is about, such as Entra ID and
Conditional Access, and lists the MITRE ATT&CK techniques its control addresses, so assessment results appear
beside Maester’s own checks in the report’s product, area and ATT&CK views. The assessment does not record these,
so Maester keeps the mapping in the pack’s taxonomy.json.
The pack’s ID is maester.zero-trust-assessment. It needs no Install-MtPack: it is part of the Maester module,
and Get-MtPack lists it.
Run it
Section titled “Run it”Install the assessment’s module once, connect Maester to the tenant, then name the pack. ZeroTrustAssessment 3.1.0 needs PowerShell 7.6 or later (Maester itself runs on 7.4); on an older PowerShell the pack is skipped and says so.
Install-Module ZeroTrustAssessment -Scope CurrentUserConnect-MaesterInvoke-Maester -Pack ZTA -SkipBuiltIn # only the Zero Trust AssessmentInvoke-Maester -Pack ZTA # Maester's tests and the assessmentYou don’t run Connect-ZtAssessment yourself. The assessment runs in a separate PowerShell process, which can’t
use the sign-ins of your session, so Maester signs in there for you (see
Let Maester run the assessment). After an interactive Connect-Maester you
sign in a second time, in the browser, for the assessment. Connect-Maester is still needed: it tells the
assessment which tenant to sign in to, and the pack is skipped while Maester is not connected to Graph. If you
read a report you already have (below), you run Connect-ZtAssessment and Invoke-ZtAssessment yourself first.
The pack is a long-running test, so Invoke-Maester leaves it out unless you name it. These also run it:
Invoke-Maester -TestId ZTA # only the Zero Trust AssessmentInvoke-Maester -Suite ZTA # the sameInvoke-Maester -IncludeLongRunning # everything, including the assessmentThe pack itself stays enabled, so naming it is enough; it is the long-running flag that keeps it out of a plain
Invoke-Maester. To remove it entirely, set Packs: [{ "Id": "maester.zero-trust-assessment", "Enabled": false }].
To run it on every run, add it to Selection.Pack in maester-config.json, which works like -Pack: Maester’s own
tests still run, and packs you do not name there do not. Don’t use Selection.TestId for this: it limits the run to
the IDs it lists, so ["ZTA"] runs only the assessment. You can also set Selection.IncludeLongRunning to true,
which runs every other long-running test too. Give it time with Execution.LongRunningTimeoutSeconds: the assessment
can take an hour or more on a large tenant.
The pack gets the assessment’s results one of two ways.
Read a report you already have (recommended in CI)
Section titled “Read a report you already have (recommended in CI)”Run the assessment as its own step, then point Maester at the report it wrote:
Install-Module ZeroTrustAssessment -Scope CurrentUserConnect-ZtAssessmentInvoke-ZtAssessment -Path ./zt -NoBrowser{ "GlobalSettings": { "ZeroTrustAssessment.ReportPath": "./zt" } }Then, in a new PowerShell session (the assessment’s modules can’t share one with Maester’s), connect Maester to the same tenant and run the pack:
Connect-MaesterInvoke-Maester -Pack ZTAZeroTrustAssessment.ReportPath takes the report file (ZeroTrustAssessmentReport.json), the folder given to
Invoke-ZtAssessment -Path, or its zt-export folder. Maester refuses a report written for another tenant than the
one it is connected to, and one older than ZeroTrustAssessment.MaxReportAgeDays (7 by default; 0 turns the check
off).
Let Maester run the assessment
Section titled “Let Maester run the assessment”Without ZeroTrustAssessment.ReportPath, Maester runs the assessment itself. Install the module first:
Install-Module ZeroTrustAssessment -Scope CurrentUserThe assessment loads its own versions of the Microsoft Graph, Exchange Online and Az modules, which cannot share
a PowerShell session with the ones Maester has loaded. So Maester starts it in a separate pwsh process, which
signs in with Connect-ZtAssessment:
- It signs in to the tenant Maester is connected to.
- When Maester is connected to Graph app-only with a certificate, it uses the same app and certificate. The app
needs the Graph application permissions that
Get-ZtGraphScopelists. - When Maester is connected app-only another way (a client secret, a managed identity or workload identity federation), the pack is skipped with the reason: the assessment signs in app-only only with a certificate. See Run it in a pipeline.
- Otherwise it signs in interactively, so you sign in a second time. Without a browser, for example over SSH, set
ZeroTrustAssessment.Connectto{ "UseDeviceCode": true }.
While it runs, what the assessment writes is shown live: in the full-screen dashboard of an interactive run it
scrolls in a box in the middle of the screen, under the phases of the run, and in other modes (CI, redirected
output, -Verbosity Normal) it is written to the console, indented. The assessment runs with -ShowLog, so it lists
the tests it runs, and its progress bars become one line at the bottom of the box that updates in place. Set
ZeroTrustAssessment.ShowLog to false for its shorter output.
The top of the box shows where the assessment is, read once a second from the progress the assessment keeps for its own progress page and from the progress logs it writes in its output folder:
- the six stages of the assessment as a track (Export, Tests, Tenant info, Results, Report data, HTML report), with the step of the stage it is on, such as “Importing Data into Database”;
- a bar of the exports or tests done of how many, the percentage, how many failed and are running, and the time left;
- each export or test that is running, with what it is doing and how long it has run, and the ones that finished,
with their result and how long they took, listed the way the dashboard lists Maester’s own tests (tests by
their
ZTA.ID and title); - each export, database import or test that fails or times out, with its error. The box counts them.
The rest of the dashboard follows the assessment while it runs:
- the Results panel and the result blocks over the box count the assessment’s results (passed, failed, investigate, skipped) as its tests finish;
- the assessment’s pillars are listed where Maester’s products usually are, each with a bar and its counts;
- the Pace graph next to the logo draws how long each export and test took, and the Slowest panel lists the slowest of them.
Outside the dashboard the stage is written when it changes, each export and test as it finishes with its result and time, the counts at most every ten seconds, and each failure as it happens. This needs version 3.1 or later of the ZeroTrustAssessment module; with an earlier version the box shows the log only.
The assessment’s own report and raw export go to a temporary folder that Maester deletes after reading it, because
the export holds tenant data. Set ZeroTrustAssessment.OutputPath to keep them.
Run it in a pipeline
Section titled “Run it in a pipeline”In GitHub Actions, Azure DevOps or any other pipeline, nobody is there to sign in, so the assessment signs in
as a service principal. Connect-ZtAssessment signs in app-only only with a certificate, found by thumbprint in
the certificate store of the computer it runs on. A client secret, a managed identity or workload identity
federation (the OIDC sign-in in Maester’s GitHub guide) is enough for Maester’s own
tests but not for the assessment.
So the pipeline needs:
- An app registration with a certificate. Give the app the Graph application permissions that
Get-ZtGraphScopelists (run it afterImport-Module ZeroTrustAssessment), plus Maester’s own permissions if the same run includes Maester’s tests. The assessment’s Azure, Exchange Online, Security & Compliance and SharePoint tests need the app to have the roles the Zero Trust Assessment documentation lists for app-only sign-in; to leave those services out, setZeroTrustAssessment.Connectto{ "Service": ["Graph"] }. - The certificate as a PFX file, saved base64-encoded in a pipeline secret with its password.
- A step that adds the certificate to the current user’s certificate store, connects Maester to Graph with it by thumbprint, and runs the pack. The child process finds the same certificate by its thumbprint.
A GitHub Actions workflow that does this on a schedule, with the secrets AZURE_TENANT_ID, AZURE_CLIENT_ID,
ZTA_CERTIFICATE (the base64 PFX) and ZTA_CERTIFICATE_PASSWORD:
name: Zero Trust Assessmenton: schedule: - cron: '0 3 * * 1' workflow_dispatch:
jobs: zero-trust: runs-on: ubuntu-latest timeout-minutes: 360 steps: - uses: actions/checkout@v4
- name: Run the Zero Trust Assessment with Maester shell: pwsh env: TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} CERTIFICATE: ${{ secrets.ZTA_CERTIFICATE }} CERTIFICATE_PASSWORD: ${{ secrets.ZTA_CERTIFICATE_PASSWORD }} run: | Install-Module Maester, ZeroTrustAssessment -Scope CurrentUser -Force
# Add the certificate to the current user's store, where the assessment looks for it. $flags = 'PersistKeySet, Exportable' $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new( [Convert]::FromBase64String($env:CERTIFICATE), $env:CERTIFICATE_PASSWORD, $flags) $store = [System.Security.Cryptography.X509Certificates.X509Store]::new('My', 'CurrentUser') $store.Open('ReadWrite'); $store.Add($cert); $store.Close()
Connect-MgGraph -ClientId $env:CLIENT_ID -TenantId $env:TENANT_ID -CertificateThumbprint $cert.Thumbprint -NoWelcome Invoke-Maester -Pack ZTA -NonInteractive -OutputFolder ./test-results
- uses: actions/upload-artifact@v4 if: always() with: name: maester-zero-trust path: test-resultsThe assessment can take an hour or more on a large tenant: keep the job’s timeout-minutes above that, and raise
Execution.LongRunningTimeoutSeconds in maester-config.json if Maester stops it first. On Windows runners the
same steps work in pwsh.
If the assessment runs somewhere else, or you’d rather keep the certificate out of the Maester step, run
Connect-ZtAssessment -ClientId <app> -TenantId <tenant> -Certificate <thumbprint> and
Invoke-ZtAssessment -Path ./zt -NoBrowser as their own step, then let Maester read the report with
ZeroTrustAssessment.ReportPath (see Read a report you already have).
Maester’s step can then connect any way it likes, including OIDC, as long as it is the same tenant.
The assessment’s own report
Section titled “The assessment’s own report”The Maester report embeds the HTML report the assessment wrote, with its pillar dashboards and charts. Open it with the Zero Trust Assessment button on the report’s overview, under Assessment report in the pack’s menu, or on any ZTA result. It opens inside the Maester report, with an option to open it in a new browser tab, and works offline, so the Maester report stays a single file to share.
The embedded report is compressed: a 10 MB assessment report adds about 2 MB to the Maester report. It is left
out when ZeroTrustAssessment.EmbedReport is false, when you read a report file that has no
ZeroTrustAssessmentReport.html beside it or one folder up, and when Invoke-Maester -RedactUserIdentity is
used, because Maester cannot redact the assessment’s report.
Zero Trust plan
Section titled “Zero Trust plan”The report’s Zero Trust plan view shows the Microsoft Zero Trust Workshop plan for the tenant. Each pillar (Identity, Devices, Data, Network, Infrastructure, Security Operations and AI) is split into functional areas and swimlanes, and each task has its phase, priority, effort, user impact, licence, dependencies and Microsoft’s guidance. A task shows the status of the checks that measure it, so you can see which parts of the plan are done, which fail and which no check covers yet. The pillars the run has results for are marked.
Open it from the pack’s menu: select Microsoft Zero Trust Assessment in the report’s left menu, then a pillar under Zero Trust plan. Expand a pillar to go straight to one of its functional areas, and an area to go to one swimlane.
The view opens on Swimlanes: one functional area at a time, with its swimlanes as rows and the workshop’s phases (First, Then, Next) as columns. Table lists every task in the pillar, grouped by functional area and swimlane, and can be sorted and filtered. Select a task in either view to see its checks and guidance in a panel on the right.
A task’s checks come from three places:
- the assessment tests Microsoft maps to the task;
- Maester’s built-in tests that check the same setting as one of those assessment tests;
- Maester tests that Maester maps to the task itself, with a note where it checks more than the workshop asks.
Maester also adds a few tasks the workshop does not have, such as hardening Microsoft Entra Connect sync. They are
marked as Maester’s. Run Maester’s own tests with the pack (Invoke-Maester -Pack ZTA) to see their status.
The plan ships with the pack, so the view works offline. Set ZeroTrustAssessment.WorkshopPlan to false to
leave it out. With Invoke-Maester -RedactUserIdentity the plan is kept, redacted like the results.
Zero Trust Workshop content © Microsoft; guidance and check mapping from microsoft/zerotrustassessment, MIT licence.
Add a Maester test to a task, or refresh the plan
Section titled “Add a Maester test to a task, or refresh the plan”Maester’s additions are kept by hand in the pack’s workshop/maester.json: list the built-in tests that measure a
workshop task under its task ID, or add a task under ExtraTasks. The rest of the folder is generated from the
workshop site and the assessment’s repository:
./build/zero-trust-workshop/Update-MtZtWorkshopData.ps1build/zero-trust-workshop/README.md
has the file formats and the rules the unit tests check.
Facts on the Affected objects page
Section titled “Facts on the Affected objects page”The assessment exports the tenant’s apps, service principals, users, devices, directory roles, agent identities
and Intune configuration policies into a zt-export folder next to its report. Maester reads that export and adds
what it says about each object to the Affected objects page, so an app, user or device shows more than the
checks that named it. Expand an object to see its facts. A red risk or amber to review badge says how many
need attention.
| Object | Facts |
|---|---|
| Enterprise application | Publisher, application permissions (Tier 0 Graph permissions, those with a path to Global Administrator, are a risk), permissions consented for all users, last sign-in, client secrets and certificates, Entra roles, single sign-on |
| App registration | Who can sign in, owners, client secrets and certificates, and the permissions and last sign-in of its enterprise application |
| User | Member or guest, synced from on-premises, Entra roles (a permanent privileged role is a risk), MFA registration (none on a privileged user is a risk), passwordless, last sign-in |
| Device | Operating system, join type, managed, compliant, last sign-in |
| Agent identity, blueprint and blueprint principal | Blueprint, sponsors, owners, credentials, permissions, Entra roles |
| Intune configuration policy | Assignments, platform, settings, last change |
The facts carry the shared fact keys, such as
user.mfaRegistered and signIn.daysSince, so other reports and summaries can read them as data.
Facts only describe objects that the run lists, whichever check named them. They are what the export said when
the assessment ran, so the page says the date they come from. Anything unused for 90 days before the assessment
is marked to review. The export is read before a temporary output folder is deleted; when you read a report you
already have, keep the zt-export folder with it. To leave the facts out, set ZeroTrustAssessment.ObjectFacts
to false.
Settings
Section titled “Settings”All settings go in GlobalSettings of maester-config.json.
| Setting | Default | What it does |
|---|---|---|
ZeroTrustAssessment.ReportPath | empty | Read this report instead of running the assessment. |
ZeroTrustAssessment.MaxReportAgeDays | 7 | Refuse a report older than this. 0 turns the check off. |
ZeroTrustAssessment.EmbedReport | true | Embed the assessment’s own HTML report in the Maester report. |
ZeroTrustAssessment.WorkshopPlan | true | Add the Zero Trust Workshop plan, with the status of each task, to the Maester report. |
ZeroTrustAssessment.ObjectFacts | true | Add what the assessment’s export says about each app, user, device and policy to the Affected objects page. |
ZeroTrustAssessment.OutputPath | temporary | Where the assessment writes its report and export. Kept when set. |
ZeroTrustAssessment.Pillar | All | Run one pillar only: Identity, Devices, Network, Data, Infrastructure, SecOps or AI. |
ZeroTrustAssessment.Tests | all | Run only these assessment test IDs, for example ["21770", "21771"]. |
ZeroTrustAssessment.Days | 30 | Days of sign-in logs the assessment reads (1 to 30). |
ZeroTrustAssessment.ShowLog | true | Run the assessment with -ShowLog, which lists the tests it runs in its live output. |
ZeroTrustAssessment.DisableTelemetry | true | The assessment sends the tenant ID to Microsoft unless this is true. |
ZeroTrustAssessment.Connect | none | Further Connect-ZtAssessment parameters, for example { "Service": ["Graph", "Azure"], "UseDeviceCode": true }. |
EmergencyAccessAccounts is passed to the assessment as well, so its Conditional Access tests leave out the same
break-glass accounts as Maester’s.
How results map
Section titled “How results map”| Assessment status | Maester result |
|---|---|
| Passed | Passed |
| Failed | Failed |
| Investigate | Investigate |
| Skipped | Skipped, with the assessment’s reason |
| Error | Error |
| Planned | Not shown: the assessment has not built the test yet |
A test that writes several rows under one ID (the Defender for Cloud recommendations) gets one row each, with a suffix made from the row’s title so that it keeps its ID from run to run.
Licence
Section titled “Licence”The Zero Trust Assessment is © Microsoft Corporation and published under the MIT licence. This pack does not include its code: it runs the module you install, or reads the report it wrote.