Sign inSign up

amslertec/pwnotify

By amslertec

Updated about 2 months ago

Password expiry notification for Microsoft Entra ID - hardened, self-hosted, multi-tenant

Image
Security
Content management system
0

9.3K

amslertec/pwnotify repository overview

PwNotify

Password Expiry Notification Tool for Microsoft Entra ID (Azure AD).

PwNotify checks your Entra ID users via Microsoft Graph, computes when each password expires, and sends staged reminder e-mails — to the primary mailbox, the alternate (SSPR) address, or both. Self-hosted, hardened, and driven by a modern admin UI with a first-run setup wizard.

docker pull amslertec/pwnotify:0.3.10

Highlights

  • Graph sync — client-credentials flow, pagination, 429 throttling, per-domain password-validity detection. Optionally scope the sync to a single Entra group (dynamic or static) so only the right users are checked.
  • Staged reminders — configurable days before expiry (default 14/7/3/1/0), deduplicated per user + stage + expiry cycle, with catch-up after downtime.
  • Two mail backends — Microsoft Graph sendMail or SMTP, switchable at runtime.
  • Modern UI — dashboard, users table with CSV/XLSX export, notification & run history, editable DE/EN templates, branding, local + Microsoft-SSO logins, profile photos across user tables (Entra + local/SSO).
  • Multi-tenant — manage one Entra tenant or many customer tenants from a single instance, off by default (Settings → General); per-customer isolation via PostgreSQL Row-Level Security under a restricted DB role.
  • Teams & access model — global superadmin, plus local admins/auditors scoped to customers via grants or Entra-group-based Teams, with Graph member sync driving access automatically.
  • Hardened & compliant — Chainguard base (0 known HIGH/CRITICAL CVEs), non-root, read-only rootfs, no-new-privileges, all capabilities dropped, SBOM + provenance attestations, multi-arch (amd64 + arm64).

Supported tags

TagMeaning
0.3.10Pinned release (recommended for production)
latestMost recent release

Multi-arch manifests: linux/amd64 and linux/arm64.


Quick start (Docker Compose)

You need two files on the target server: docker-compose-prod.yml and example.env. The commands below download them straight from the public repository — no clone required.

mkdir pwnotify && cd pwnotify

# Download the two files straight to their final names (public repo, no auth needed):
curl -fsSL https://raw.githubusercontent.com/amslertec/pwnotify/main/docker-compose-prod.yml -o docker-compose.yml
curl -fsSL https://raw.githubusercontent.com/amslertec/pwnotify/main/example.env -o .env

#   -> edit .env:  POSTGRES_PASSWORD, PWNOTIFY_RUNTIME_DB_PASSWORD, PWNOTIFY_BASE_URL,
#                  PWNOTIFY_BIND, PWNOTIFY_COOKIE_SECURE

docker compose pull
docker compose up -d
docker compose ps        # wait for "healthy"

Then open the app — the first-run setup wizard guides you through database check → admin account → Microsoft Graph (with a built-in Entra registration guide) → mail backend.

Can't reach it / ERR_CONNECTION_REFUSED? The container binds to 127.0.0.1:8080 by default (localhost only) — correct behind a reverse proxy, but unreachable from another machine. For direct LAN access set in .env: PWNOTIFY_BIND=0.0.0.0:8080, PWNOTIFY_BASE_URL=http://<server-ip>:8080, PWNOTIFY_COOKIE_SECURE=false, then docker compose up -d again. The bundled PostgreSQL stays on the internal Docker network.

Minimal run without Compose

PwNotify needs a PostgreSQL database. If you already have one:

docker run -d --name pwnotify \
  -p 127.0.0.1:8080:8080 \
  -e PWNOTIFY_DATABASE_URL="postgresql+asyncpg://user:pass@host:5432/pwnotify" \
  -e PWNOTIFY_BASE_URL="https://pwnotify.example.com" \
  -e PWNOTIFY_COOKIE_SECURE=true \
  -v pwnotify-data:/data \
  --read-only --tmpfs /tmp \
  --security-opt no-new-privileges:true --cap-drop ALL \
  amslertec/pwnotify:0.3.10

Configuration

Environment variables are an initial seed only — after the first start everything is managed in the database via the Settings UI. Secrets are Fernet-encrypted at rest.

VariableDefaultDescription
PWNOTIFY_DATABASE_URLRequired. Async DSN, e.g. postgresql+asyncpg://user:pass@db:5432/pwnotify
PWNOTIFY_RUNTIME_DB_PASSWORDRequired. Password for the non-superuser pwnotify_runtime DB role used for tenant queries (defence in depth for RLS). Provisioned automatically on start. Startup fails fast if unset
PWNOTIFY_SECRET_KEYautoFernet master key. Empty → generated into /data/secret.key
PWNOTIFY_BASE_URLhttp://localhost:8080Public URL (e-mail links, cookies, OIDC redirect)
PWNOTIFY_COOKIE_SECUREtrueRequire HTTPS cookies (set false for plain HTTP)
PWNOTIFY_BIND127.0.0.1:8080Host bind address. 0.0.0.0:8080 for direct LAN access; keep localhost behind a reverse proxy
PWNOTIFY_TRUSTED_PROXIES127.0.0.1Proxy peers allowed to set X-Forwarded-For. Set your proxy's IP/subnet, otherwise the audit log and rate limiter see the proxy IP, not the real client
PWNOTIFY_ALLOWED_HOSTSOptional comma-separated Host-header allow-list (empty = no check)
PWNOTIFY_IDLE_TIMEOUT_MIN30Sign out after this many minutes of inactivity (0 = off)
PWNOTIFY_REFRESH_TOKEN_TTL_DAYS14Maximum session lifetime
PWNOTIFY_TIMEZONEEurope/ZurichScheduler timezone
PWNOTIFY_LOG_LEVELINFODEBUG | INFO | WARNING | ERROR
PWNOTIFY_LOG_JSONtrueJSON logs (false = colored console)
PWNOTIFY_ADMIN_USERNAME / _PASSWORDOptional first-admin seed (else via wizard)
PWNOTIFY_GRAPH_TENANT_ID / _CLIENT_ID / _CLIENT_SECRETOptional Graph seed
PWNOTIFY_MAIL_BACKENDgraphgraph | smtp
PWNOTIFY_MAIL_FROMSender address

See example.env for the full list.

Ports & volumes
Port8080 (HTTP, app + API)
Volume /dataFernet key, uploaded logos/favicons, avatars — back this up
DB volume pgdataPostgreSQL data directory
Entra app registration

Create an app registration (client-credentials) with these application permissions and grant admin consent:

PermissionPurpose
User.Read.AllRead users, UPN, last password change (+ SSO profile photos)
Domain.Read.AllRead per-domain password validity
Mail.SendSend reminder e-mails via Graph
GroupMember.Read.AllOptional — only for group-scoped sync, Microsoft SSO, and/or group-based Teams assignment

The setup wizard and the Settings → Graph tab contain a step-by-step guide.


Security & compliance

This image is built for a Docker Scout A rating:

  • 0 known HIGH/CRITICAL CVEs — Chainguard/Wolfi minimal base, no shell, no package manager, no compilers in the runtime image.
  • Non-root — runs as UID 65532.
  • Read-only root filesystem compatible (--read-only + a small tmpfs).
  • Multi-stage build — no build tools, Node, uv, tests, or VCS metadata in the runtime.
  • Digest-pinned base image, full OCI labels.
  • SBOM + provenance (mode=max) attestations attached to the manifest.

Inspect the attestations yourself:

docker scout cves amslertec/pwnotify:0.3.10            # vulnerability report
docker buildx imagetools inspect amslertec/pwnotify:0.3.10   # platforms + attestations
docker scout sbom amslertec/pwnotify:0.3.10            # software bill of materials

Upgrade

docker compose -f docker-compose-prod.yml pull
docker compose -f docker-compose-prod.yml up -d

Database schema migrations (Alembic) run automatically on start.

Existing deployments upgrading to 0.2.7+: set PWNOTIFY_RUNTIME_DB_PASSWORD (a strong value) in .env, then pull and up -d as above. The migration creates a dedicated non-superuser DB role (pwnotify_runtime) for tenant queries automatically — no manual SQL. Startup fails fast if the variable is missing.

Backup

Back up two things: the /data volume (Fernet key + uploads) and the PostgreSQL database.

# Database dump
docker compose -f docker-compose-prod.yml exec db \
  pg_dump -U pwnotify pwnotify > pwnotify-backup.sql

# /data volume (tar)
docker run --rm -v pwnotify_data:/data -v "$PWD":/backup alpine \
  tar czf /backup/pwnotify-data.tgz -C /data .

If you lose /data/secret.key without a PWNOTIFY_SECRET_KEY set, the encrypted secrets in the database can no longer be decrypted. Keep it (or an explicit key) safe.


License

MIT © amslertec — source: https://github.com/amslertec/pwnotify

Tag summary

Content type

Image

Digest

sha256:3879741e8

Size

66.8 MB

Last updated

about 2 months ago

docker pull amslertec/pwnotify