Skip to content
Sponsor
3.0 preview2.3.0 stable

These docs describe the preview. Pick the version you run and every page marks what it doesn't have yet. Get the preview →
Maester 2.x docs (archived) →

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.

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 CurrentUser
Connect-Maester
Invoke-Maester -Pack ZTA -SkipBuiltIn # only the Zero Trust Assessment
Invoke-Maester -Pack ZTA # Maester's tests and the assessment

You 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 Assessment
Invoke-Maester -Suite ZTA # the same
Invoke-Maester -IncludeLongRunning # everything, including the assessment

The 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.

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 CurrentUser
Connect-ZtAssessment
Invoke-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-Maester
Invoke-Maester -Pack ZTA

ZeroTrustAssessment.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).

Without ZeroTrustAssessment.ReportPath, Maester runs the assessment itself. Install the module first:

Install-Module ZeroTrustAssessment -Scope CurrentUser

The 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-ZtGraphScope lists.
  • 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.Connect to { "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.

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:

  1. An app registration with a certificate. Give the app the Graph application permissions that Get-ZtGraphScope lists (run it after Import-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, set ZeroTrustAssessment.Connect to { "Service": ["Graph"] }.
  2. The certificate as a PFX file, saved base64-encoded in a pipeline secret with its password.
  3. 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 Assessment
on:
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-results

The 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 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.

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.ps1

build/zero-trust-workshop/README.md has the file formats and the rules the unit tests check.

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.

ObjectFacts
Enterprise applicationPublisher, 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 registrationWho can sign in, owners, client secrets and certificates, and the permissions and last sign-in of its enterprise application
UserMember 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
DeviceOperating system, join type, managed, compliant, last sign-in
Agent identity, blueprint and blueprint principalBlueprint, sponsors, owners, credentials, permissions, Entra roles
Intune configuration policyAssignments, 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.

All settings go in GlobalSettings of maester-config.json.

SettingDefaultWhat it does
ZeroTrustAssessment.ReportPathemptyRead this report instead of running the assessment.
ZeroTrustAssessment.MaxReportAgeDays7Refuse a report older than this. 0 turns the check off.
ZeroTrustAssessment.EmbedReporttrueEmbed the assessment’s own HTML report in the Maester report.
ZeroTrustAssessment.WorkshopPlantrueAdd the Zero Trust Workshop plan, with the status of each task, to the Maester report.
ZeroTrustAssessment.ObjectFactstrueAdd what the assessment’s export says about each app, user, device and policy to the Affected objects page.
ZeroTrustAssessment.OutputPathtemporaryWhere the assessment writes its report and export. Kept when set.
ZeroTrustAssessment.PillarAllRun one pillar only: Identity, Devices, Network, Data, Infrastructure, SecOps or AI.
ZeroTrustAssessment.TestsallRun only these assessment test IDs, for example ["21770", "21771"].
ZeroTrustAssessment.Days30Days of sign-in logs the assessment reads (1 to 30).
ZeroTrustAssessment.ShowLogtrueRun the assessment with -ShowLog, which lists the tests it runs in its live output.
ZeroTrustAssessment.DisableTelemetrytrueThe assessment sends the tenant ID to Microsoft unless this is true.
ZeroTrustAssessment.ConnectnoneFurther 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.

Assessment statusMaester result
PassedPassed
FailedFailed
InvestigateInvestigate
SkippedSkipped, with the assessment’s reason
ErrorError
PlannedNot 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.

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.