Replace basic auth with Microsoft 365 sign-in
nginx now checks every dashboard and API request with oauth2-proxy (auth_request), which signs users in with Microsoft Entra ID. Who may sign in is controlled in Entra: a single-tenant app registration with "Assignment required", so only assigned users/groups get through. - deploy/oauth2-proxy.cfg (entra-id provider, auth-only mode, secure cookie, trusted proxy = localhost) and deploy/oauth2-proxy.service - nginx: pages redirect to Microsoft sign-in and back to the same URL; API calls get a plain 401 (the page already asks to reload); /api/v1/ingest stays key-only for collectors; /oauth2/auth is internal; the signed-in user is passed to the API in headers nginx sets (so browsers can't spoof them); dashboard files are served with Cache-Control: no-cache so updates show up at once - GET /api/v1/me; the top bar shows the signed-in user and a Sign out that also signs out of Microsoft (otherwise SSO signs straight back in) - README: Entra and server setup steps Tested locally with oauth2-proxy 7.15.5 and nginx 1.31 running the deploy configs (TLS, self-signed): redirects to the tenant's sign-in with the return URL kept, 401s, ingest bypass, sign-out allow-list, cookie flags, security headers. The signed-in path was tested with a stand-in for oauth2-proxy's auth answer; a real Entra sign-in wasn't. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -5,8 +5,9 @@ Proof of concept backup dashboard, intended to display backup jobs from multiple
|
||||
```
|
||||
collectors ──POST /api/v1/ingest (API key)──▶ ┌──────────────┐
|
||||
│ FastAPI + │
|
||||
browser ──nginx (basic auth, TLS)──▶ /api/* ─▶│ SQLite │
|
||||
└─ static files: public/ └──────────────┘
|
||||
browser ──nginx (TLS)──▶ /api/* ─────────────▶│ SQLite │
|
||||
│ └─ static files: public/ └──────────────┘
|
||||
└─ auth_request ─▶ oauth2-proxy ─▶ Microsoft Entra ID (Microsoft 365 sign-in)
|
||||
```
|
||||
|
||||
- `public/`: static frontend (no build step). Add `?mock=1` to the URL to use built-in sample data.
|
||||
@@ -72,12 +73,58 @@ The OpenAPI schema is at `/api/v1/openapi.json`.
|
||||
|
||||
## Authentication
|
||||
|
||||
- **Dashboard users:** nginx basic auth over HTTPS (`deploy/nginx.conf`). Every user can see every tenant.
|
||||
*Planned:* Microsoft 365 sign-in through [oauth2-proxy](https://oauth2-proxy.github.io/oauth2-proxy/)
|
||||
with the Entra ID provider and nginx `auth_request`. That's an nginx-only change. The frontend
|
||||
already tells the user to reload when a session expires (HTTP 401).
|
||||
- **Dashboard users:** Microsoft 365 sign-in. nginx checks every request with
|
||||
[oauth2-proxy](https://oauth2-proxy.github.io/oauth2-proxy/), which signs users in with Microsoft
|
||||
Entra ID. Who may sign in is controlled in Entra (users or a group assigned to the app). Every
|
||||
signed-in user can see every tenant. Sessions last 12 hours; signing in again is usually silent.
|
||||
The dashboard shows who's signed in; **Sign out** also signs out of Microsoft in that browser,
|
||||
otherwise the next visit would sign straight back in.
|
||||
- **Collectors:** per-collector bearer keys, checked by the app. Only a SHA-256 hash is stored.
|
||||
To revoke one, run `python manage.py revoke-key NAME`.
|
||||
To revoke one, run `python manage.py revoke-key NAME`. `/api/v1/ingest` is the only path that
|
||||
doesn't need Microsoft sign-in.
|
||||
|
||||
### Microsoft 365 sign-in setup
|
||||
|
||||
**In the Microsoft Entra admin center** (entra.microsoft.com), as an admin of your tenant:
|
||||
|
||||
1. *App registrations → New registration*. Name: `Backup Dashboard`. Supported account types:
|
||||
**Accounts in this organizational directory only** (single tenant). Redirect URI: platform
|
||||
**Web**, `https://<your dashboard host>/oauth2/callback`. Register.
|
||||
2. On its *Overview*, note the **Application (client) ID** and **Directory (tenant) ID**.
|
||||
3. *Certificates & secrets → New client secret*. Copy the secret's **Value** now (it's only shown
|
||||
once). Note its expiry date: sign-in stops working when it expires, so set a reminder to
|
||||
create a new one before then.
|
||||
4. *Enterprise applications → Backup Dashboard → Properties*: set **Assignment required** to
|
||||
**Yes**. Then *Users and groups → Add user/group* and add the people (or a group) who should
|
||||
see the dashboard. Everyone else in the tenant is refused by Microsoft before reaching it.
|
||||
|
||||
No API permissions beyond the default `User.Read` are needed.
|
||||
|
||||
**On the dashboard server:**
|
||||
|
||||
1. Install oauth2-proxy 7.8 or newer from its
|
||||
[releases](https://github.com/oauth2-proxy/oauth2-proxy/releases) as `/usr/local/bin/oauth2-proxy`,
|
||||
and create its user: `sudo useradd --system --no-create-home oauth2-proxy`.
|
||||
2. Install the config and the client secret, readable only by root and oauth2-proxy:
|
||||
```bash
|
||||
sudo install -d -m 750 -g oauth2-proxy /etc/oauth2-proxy
|
||||
sudo install -m 640 -g oauth2-proxy deploy/oauth2-proxy.cfg /etc/oauth2-proxy/backup-dashboard.cfg
|
||||
sudo sh -c 'umask 027; printf %s "<client secret value>" > /etc/oauth2-proxy/client-secret' && sudo chgrp oauth2-proxy /etc/oauth2-proxy/client-secret
|
||||
```
|
||||
In `/etc/oauth2-proxy/backup-dashboard.cfg`, fill in `<TENANT_ID>`, `<APPLICATION_CLIENT_ID>`,
|
||||
`<DASHBOARD_HOST>`, and `<COOKIE_SECRET>`, generated with:
|
||||
`openssl rand -base64 32 | tr -- '+/' '-_'`
|
||||
3. Install `deploy/oauth2-proxy.service` into `/etc/systemd/system/`, then
|
||||
`sudo systemctl daemon-reload && sudo systemctl enable --now oauth2-proxy`.
|
||||
Check it with `journalctl -u oauth2-proxy -n 20`: it should say "OAuthProxy configured for
|
||||
Microsoft Entra ID".
|
||||
4. Install the new `deploy/nginx.conf` (keeping your server name and certificate paths), then
|
||||
`sudo nginx -t && sudo systemctl reload nginx`. Basic auth and its htpasswd file are no longer
|
||||
used.
|
||||
|
||||
**Troubleshooting:** sign-in problems show in `journalctl -u oauth2-proxy`. Microsoft's own
|
||||
errors (wrong redirect URI, user not assigned, expired secret) appear on its sign-in page with an
|
||||
`AADSTS…` code.
|
||||
|
||||
## Development
|
||||
|
||||
@@ -96,7 +143,7 @@ BACKUP_DASH_SERVE_STATIC=1 uvicorn --factory app.main:create_app --reload
|
||||
1. Copy the repo to `/opt/backup-dashboard`, create a venv at `/opt/backup-dashboard/venv`, and run `pip install -r backend/requirements.txt`.
|
||||
2. Copy `deploy/backup-dashboard.env` to `/etc/backup-dashboard/backup-dashboard.env`. Both the service and `manage.py` read their settings from it.
|
||||
3. Create a `backupdash` system user, then install `deploy/backup-dashboard-api.service` into `/etc/systemd/system/` and enable it. The database is created on first start.
|
||||
4. Copy `public/` to `/var/www/backup-dashboard/public`, install `deploy/nginx.conf`, get a certificate with certbot, and create users with `htpasswd -B`.
|
||||
4. Copy `public/` to `/var/www/backup-dashboard/public`, install `deploy/nginx.conf`, get a certificate with certbot, and set up Microsoft 365 sign-in (see [Microsoft 365 sign-in setup](#microsoft-365-sign-in-setup)).
|
||||
5. Run `manage.py` as the service user. It finds the database through the env file:
|
||||
```bash
|
||||
cd /opt/backup-dashboard/backend
|
||||
|
||||
Reference in New Issue
Block a user