Tenantkeep Posture

How it works

One page for the person who has to reason about the whole thing: what runs where, as whom, reading what, writing where.

One page for the person who has to reason about the whole thing: what runs where, as whom, reading what, writing where. The spec (posture-spec.md) says what the product is; this says how it is put together. Dated statements are decisions; (verify) marks what has not been proven live yet.

The parts

Part Where it lives What it is
Module Tenantkeep.Posture this repo, PowerShell Gallery and signed zips Collectors, check engine, catalog, report renderers, licence verifier. Read-only against the tenant, always.
Check catalog and baselines checks/, baselines/ Data, not code: 204 checks in 13 domains, baselines that select and weight them, framework crosswalks.
Report templates/, rendered by the module One self-contained HTML file per run (inline CSS and JS, no CDN), plus Markdown and optional PDF.
Windows AD collector collectors/windows/Get-ADBaseline.ps1 Standalone read-only script for a domain-joined machine; its files join the evidence folder.
Customer-side setup deploy/setup/, deploy/automation/Grant-ManagedIdentityGraphRole.ps1 The only tenant writes in the repo: app registration, consents, role grants. Run once by the customer's admin, never by the module.
Automation deployment deploy/automation/ (Bicep, deploy.ps1), deploy/schedules/ (runbooks) Option B: an Automation account in the customer's subscription with a managed identity, a nightly Conditional Access cycle and a monthly full posture cycle.
Licensing Tenantkeep.Posture/Private/Test-PostureLicense.ps1, deploy/licensing/, private repo Kainor-LLC/tenantkeep-licensing A signed licence file per tenant, verified offline; issued by a Stripe-driven function in the Kainor tenant that signs through Key Vault.
Websites repos Kainor-LLC/kainor.com, Kainor-LLC/tenantkeep.app Company site and the product family site (guide, generated catalog page).

A run, end to end

  1. Sign in. Locally, the admin signs in to Graph, Exchange, Purview, SharePoint, Teams and Azure (macOS needs separate processes for some; docs/collection.md). In Automation, the account's managed identity signs in to everything except Purview (see Identities).
  2. Collect. Invoke-PostureCollection writes one evidence folder per run: JSON and CSV files named after the audit plan (04-role-assignments-active.csv, exo-04-antiphish.json, ...), a collection.json manifest with the tenant id, account, scopes, API versions and errors, and the Conditional Access snapshot. Every Graph call pins its API version. Nothing leaves the customer's environment.
  3. Profile. New-PostureProfile discovers what evidence can tell (licences, platforms, identity model, BYOD, Copilot) and asks only what it cannot (third-party tools, thresholds, break-glass accounts, frameworks, audience). The result is profile.json in the client folder, beside brand.json, exceptions.json, tenantkeep.lic and findings.csv.
  4. Evaluate. The engine reads the catalog, applies the baseline, asks one fact provider per domain for facts and affected objects from the evidence, evaluates each check's pass expression against thresholds, applies exceptions (accepted risk counts as a pass), third-party coverage and licence applicability. checks.json records every result with its reason, evidence files and affected list.
  5. Report. New-PostureReport builds a model (score, headline numbers, domain scorecards, top risks, admins, Copilot readiness, Conditional Access, recommendations, trend against a previous run, crosswalk, appendix) and renders it. Export-PostureFindings writes the register, merging owner, status, effort and dependency by check id from the previous register.
  6. Keep. Locally the run folder stays under ~/tenantkeep/evidence/. In Automation it is uploaded to the posture-runs container, the register is written back to the client container, old runs are pruned.

Identities and permissions

Licensing and integrity

Storage layout in Automation

<storage account>/ca-snapshots/<stamp>-ca/...        nightly Conditional Access runs
<storage account>/posture-runs/<stamp>/...           monthly posture runs: evidence, checks.json, report.html, report.md, findings.csv
<storage account>/client/                            profile.json, brand.json, exceptions.json, tenantkeep.lic, findings.csv, hybrid/ad-*
<storage account>/modules/                           module zips imported into the runtime environment

The report site (Option B opt-in, decided 2026-10-15, being built)

The report is a file, so until now "save an exception" meant editing exceptions.json by hand. The report site makes the browser the place of record: a small web app in the customer's subscription, signed in with the customer's own Entra accounts, that serves the latest report and accepts saves. Exceptions, the register (owner, status, effort, dependency) and the tenant configuration (thresholds, third-party tools, baseline, break-glass accounts, frameworks) are written to the client container, which is exactly what the next scheduled run reads. Recording an exception re-scores the page immediately; the next run makes it permanent. Design details are in docs/report-site.md once built.

What is still manual

Repositories

Repo Visibility Holds
Kainor-LLC/tenantkeep-posture source-available (currently private on GitHub; the licence assumes readers) the module, catalog, deployment, docs
Kainor-LLC/tenantkeep-licensing private the issuing function
Kainor-LLC/tenantkeep.app, Kainor-LLC/kainor.com public the websites, served by Cloudflare Pages (GitHub Pages does not allow purchase links)
Kainor-LLC/LAPSlock (public), Kainor-LLC/tenantkeep-oncall (private) the other products