Skip to content

Repository files navigation

Overlord

Vendor, contractor, systems & supply-chain-security management for the Head of IT. Purpose-built to avoid the rigidity and bloat of off-the-shelf GRC tools.

  • What it does: tracks vendors (subscriptions, seats, contacts), contractors (engagements, staff, calendar meetings), internal systems, projects, and their interdependencies; stores contracts with versioning and Claude-powered review; surfaces dependency/container CVEs; and maps who can access what.

Stack

  • Next.js 16 (App Router, Server Actions, Route Handlers, proxy.ts) + React 19
  • PostgreSQL via Drizzle ORM
  • Microsoft Entra ID auth (Auth.js v5) with a DB allowlist + RBAC
  • Temporal for scheduling & durable background workflows (always-on worker)
  • GitHub App + OSV.dev for dependency/CVE intel; Microsoft Defender for Cloud for container image scanning; Claude for contract review; Microsoft Graph for calendar + email
  • Azure hosting inside a private VNet (Container Apps, Postgres Flexible Server, Blob, Key Vault, ACR)
  • Tailwind v4 UI

Monorepo layout

apps/
  web/          Next.js app (UI, Server Actions, Route Handlers, proxy.ts)
  worker/       Temporal worker (workflows + activities)
packages/
  core/         Domain enums, RBAC, money math, AES-GCM crypto (framework-free)
  db/           Drizzle schema, migrations, seed, settings service
  integrations/ GitHub, OSV, Defender, Claude, Blob, Graph, Key Vault clients
infra/          Azure Bicep IaC (VNet, ACR, Key Vault, Postgres, Storage, ACA env)
.github/workflows/  ci.yml (PR gate) + deploy.yml (merge-to-main release)

Part 1 — Local development

Prereqs: Node 24, pnpm 10, Docker.

pnpm install
cp .env.example .env                 # see below for the minimum to fill in
pnpm infra:up                        # Postgres + Temporal dev server (docker)
pnpm db:migrate                      # apply schema
pnpm db:seed                         # license catalog + a little demo data
pnpm dev                             # web on :3000, worker polling Temporal

Web UI: http://localhost:3000 · Temporal UI: http://localhost:8233

Minimum local .env: DATABASE_URL (default works with docker), AUTH_SECRET (openssl rand -base64 32), and OVERLORD_ENCRYPTION_KEY (openssl rand -base64 32). Integration credentials (GitHub, Claude, Temporal Cloud) are configured at runtime in Settings, not env. Blob storage falls back to a local .overlord-blobs/ folder when AZURE_STORAGE_ACCOUNT is unset.

  • Auth locally: real sign-in needs Entra credentials in .env. For quick UI work or the e2e suite, set E2E_BYPASS_AUTH=1 to be treated as an owner.
  • First run: the first person to sign in via Entra becomes the owner and is sent to /setup. No bootstrap env var.

Quality gates (identical to CI)

pnpm typecheck     # all packages
pnpm lint          # prettier --check
pnpm db:check      # migration/schema drift
pnpm test          # unit + integration (vitest, needs Postgres for db tests)
pnpm test:e2e      # Playwright (builds + starts the app)

Part 2 — Deploying to Azure (first time)

This is the complete path from an empty Azure subscription + GitHub repo to a running, custom-domain deployment. Do the one-time setup (steps 1–7) once; after that, every push to main deploys automatically (step 8).

Prereqs: an Azure subscription (Owner or Contributor + User Access Administrator), the Azure CLI (az login), an Entra tenant you can register apps in, a GitHub repo hosting this code, and a domain you control.

Naming below assumes the overlord prefix and resource group overlord-prod — adjust to taste (the Bicep namePrefix param and the env: block in deploy.yml must agree).

Step 1 — Provision infrastructure (Bicep)

az group create -n overlord-prod -l eastus

az deployment group create -g overlord-prod -f infra/main.bicep \
  -p adminObjectId="$(az ad signed-in-user show --query id -o tsv)" \
  -p postgresAdminPassword='<a-strong-password>'

This creates (see infra/README.md for detail): a VNet with subnets, ACR (Premium), Key Vault, Log Analytics + Container Apps environment (VNet- injected), Postgres Flexible Server (private access), Storage + a documents container, and private endpoints for KV/Storage/ACR with public access disabled. Note the outputs (acrLoginServer, keyVaultName, postgresFqdn, storageAccountName).

Step 2 — Create the two Container Apps + identities

Create overlord-web and overlord-worker on the Container Apps environment from step 1, each with a system-assigned managed identity, then grant roles:

Identity Role Scope
web and worker Key Vault Secrets User the Key Vault
web Storage Blob Data Contributor the storage account
web + worker AcrPull the ACR
web Container Apps Contributor (or a custom restart role) the worker app (lets "Save & restart worker" work)
# example role assignment
WEB_MI=$(az containerapp show -g overlord-prod -n overlord-web --query identity.principalId -o tsv)
az role assignment create --assignee "$WEB_MI" --role "Key Vault Secrets User" \
  --scope $(az keyvault show -n overlord-kv --query id -o tsv)

Step 3 — Register the Entra application (auth)

  1. Entra admin center → App registrations → New registration.
  2. Redirect URI (Web): https://<APP_BASE_URL>/api/auth/callback/microsoft-entra-id (e.g. https://overlord.yourco.com/...). You can add it now with your intended domain.
  3. Certificates & secrets → New client secret — copy the value.
  4. API permissions → Microsoft Graph → Delegated: User.Read, Calendars.Read, offline_access; then Grant admin consent.
  5. Record: Application (client) ID, Directory (tenant) ID, the client secret, and the issuer https://login.microsoftonline.com/<tenant-id>/v2.0.
  6. Email alerts (optional but recommended). So the worker can email renewal / CVE alerts to owners + admins, grant the worker Container App's managed identity the Microsoft Graph Mail.Send application permission and admin-consent it (assign the Graph app role to the identity's service principal, e.g. via az ad app permission / Microsoft Graph PowerShell). Set AZURE_TENANT_ID on the worker, and set the sender mailbox at first-run under Settings (it must be a real licensed mailbox). Without this, alerts still appear in-app and can post to Teams; only email is skipped.

Step 4 — GitHub → Azure OIDC (no stored cloud password)

Create a federated credential so GitHub Actions can authenticate to Azure without a secret:

  1. Create (or reuse) an app registration / user-assigned identity for CI, and give it Contributor on overlord-prod.
  2. Add a federated credential for this repo's production environment (subject repo:<org>/<repo>:environment:production).
  3. You'll use its client id, plus the tenant id and subscription id, as repo secrets (next step).

Step 5 — Configure GitHub repository secrets & variables

In Settings → Secrets and variables → Actions (scope to the production environment where appropriate):

Secrets (sensitive — synced to Key Vault by the deploy, or used by the pipeline):

Secret How to obtain
DATABASE_URL postgresql://overlord:<pw>@<postgresFqdn>:5432/overlord?sslmode=require (Postgres FQDN from step 1)
AUTH_SECRET openssl rand -base64 32
OVERLORD_ENCRYPTION_KEY openssl rand -base64 32 (32-byte key that encrypts integration secrets at rest)
AUTH_MICROSOFT_ENTRA_ID_SECRET Entra client secret (step 3)
AZURE_CLIENT_ID CI app/identity client id (step 4)
AZURE_TENANT_ID your tenant id
AZURE_SUBSCRIPTION_ID your subscription id

Variables (non-sensitive — injected as container envs):

Variable Value
APP_BASE_URL the public HTTPS URL, e.g. https://overlord.yourco.com (also set as AUTH_URL)
AZURE_STORAGE_ACCOUNT storageAccountName output from step 1

Entra client id / tenant / issuer are needed by the running web app too — set AUTH_MICROSOFT_ENTRA_ID_ID and AUTH_MICROSOFT_ENTRA_ID_ISSUER as container env vars on the web app (or add them to the deploy's --set-env-vars). Integration credentials for GitHub, Claude, and Temporal are not set here — they're entered in the Settings UI after first login and stored encrypted / in Key Vault by the app itself.

Step 6 — DNS + custom domain

Point your domain at the web Container App and bind a managed certificate:

  1. Get the app's default FQDN and the domain-verification id:

    az containerapp show -g overlord-prod -n overlord-web \
      --query properties.configuration.ingress.fqdn -o tsv
    az containerapp show -g overlord-prod -n overlord-web \
      --query properties.customDomainVerificationId -o tsv
  2. Create these DNS records at your provider:

    Type Name Value
    CNAME overlord (your subdomain) the app's <...>.azurecontainerapps.io FQDN
    TXT asuid.overlord the customDomainVerificationId value

    (For an apex/root domain use an A/ALIAS to the environment's static IP instead of a CNAME.)

  3. Bind the domain + managed certificate:

    az containerapp hostname add -g overlord-prod -n overlord-web --hostname overlord.yourco.com
    az containerapp hostname bind -g overlord-prod -n overlord-web \
      --hostname overlord.yourco.com --environment overlord-env --validation-method CNAME
  4. Set APP_BASE_URL (step 5) to the final https://overlord.yourco.com and make sure the Entra redirect URI (step 3) matches.

Step 7 — Enable Defender for Containers

In Microsoft Defender for Cloud → Environment settings → your subscription, turn on the Containers plan so ACR images are scanned. The worker's container-findings sync reads the results via Azure Resource Graph.

Step 8 — Deploy

Push to main (or run the Deploy workflow manually). deploy.yml runs, in order:

  1. Sync Secrets — OIDC login to Azure, push the GitHub secrets into Key Vault.
  2. Build & push — build overlord-web / overlord-worker images to ACR (tagged with the commit SHA).
  3. Migrate — run pnpm db:migrate against the production database.
  4. Deploy — roll the web + worker Container Apps to the new revision (injecting APP_BASE_URL/AUTH_URL + storage/KV reference envs) and health-check /api/health.

main is branch-protected; PRs must pass ci.yml (typecheck, lint, drift check, unit + e2e tests) before merge.

Step 9 — First run & configuration

  1. Visit https://overlord.yourco.com and sign in with Entra — the first sign-in becomes the owner and lands on /setup (set notification sender + reporting currency).
  2. In Settings:
    • GitHubCreate GitHub App (generates the App + private key automatically; install it on your org), or paste an existing App's details.
    • Claude → paste an API key (enables contract review).
    • Temporal → point at your Temporal Cloud namespace + API key. Saving restarts the worker and auto-registers default schedules (security sync, container sync, renewal reminders) — tune them under Settings → Schedules.
    • Notifications → optionally paste a Microsoft Teams Incoming Webhook URL to also post alerts to a channel. Renewal/CVE alerts always appear in-app (bell + /notifications) and are emailed to owners + admins when Graph Mail.Send is configured (step 3.6).
    • Access → add allowlisted app users + roles, and the internal-people directory. Alert emails go to every active owner/admin here.

How it fits together (runtime)

Browser ─▶ Next.js web (Container App) ─▶ Postgres (private)
             │  ├─▶ Blob (Managed Identity) — contract PDFs
             │  ├─▶ Key Vault — secrets (Managed Identity)
             │  └─▶ Temporal client — start/query workflows + schedules
             ▼
     Microsoft Entra (Auth.js)      Temporal ◀── worker (Container App)
                                              ├─▶ GitHub App (Octokit)
                                              ├─▶ OSV.dev (advisories)
                                              ├─▶ Defender for Cloud (ARG)
                                              └─▶ Claude (contract review)

Configuration & integration credentials live in Postgres (secrets AES-GCM-encrypted or in Key Vault), managed at runtime through the Settings UI — only infrastructure secrets are in GitHub/Key Vault.


Security

See SECURITY.md for the disclosure process and full operational warnings. The essentials:

  • Secrets are never in code or committed env. Three infra secrets (DATABASE_URL, AUTH_SECRET, OVERLORD_ENCRYPTION_KEY) flow GitHub Secrets → Key Vault at deploy; all integration credentials are entered in Settings and stored AES-256-GCM-encrypted (or in Key Vault). Secret fields are write-only in the UI and never rendered back.
  • The overlord:overlord Postgres credentials and the fixed test secrets are localhost/CI only — production sources every secret from Key Vault via Managed Identity.
  • E2E_BYPASS_AUTH must never be set in production. It grants synthetic owner access for local dev / e2e; it is automatically inert once Entra is configured and logs a loud startup warning if ever active.
  • First Entra sign-in on an empty database becomes the owner — deploy to a trusted tenant and claim it yourself before sharing the URL.
  • Known limitations (acceptable for v1): next-auth is on a v5 beta; there is no application-level rate limiting (relies on Entra + ingress); delegated Graph tokens live in the HttpOnly session JWT; the Teams webhook URL is admin-configured and treated as trusted.

License

MIT. The bundled SPDX license catalog (packages/db/src/data/spdx-licenses.json) is derived from the public-domain SPDX license list.

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages