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:
+54
-8
@@ -1,7 +1,7 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name backups.example.com;
|
||||
# Basic auth sends the password with every request — never serve this over plain HTTP
|
||||
# Session cookies and API keys must never cross plain HTTP
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
@@ -23,26 +23,72 @@ server {
|
||||
add_header Referrer-Policy "no-referrer" always;
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
|
||||
# Dashboard users. Create with: htpasswd -B -c /etc/nginx/backup-dashboard.htpasswd <user>
|
||||
# To move to Microsoft 365 sign-in later, replace these two lines with an auth_request to
|
||||
# oauth2-proxy (provider=entra-id); the app itself doesn't need to change.
|
||||
auth_basic "Backup Monitor";
|
||||
auth_basic_user_file /etc/nginx/backup-dashboard.htpasswd;
|
||||
# ---------- Microsoft 365 sign-in ----------
|
||||
# oauth2-proxy (deploy/oauth2-proxy.cfg) runs on 127.0.0.1:4180 and does the sign-in with
|
||||
# Entra ID. Every request below asks it "is this browser signed in?" via auth_request.
|
||||
|
||||
# Its own pages: /oauth2/start (go to Microsoft), /oauth2/callback, /oauth2/sign_out
|
||||
location /oauth2/ {
|
||||
proxy_pass http://127.0.0.1:4180;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# Where to return after signing in: the page that was asked for
|
||||
proxy_set_header X-Auth-Request-Redirect $request_uri;
|
||||
# Entra ID session cookies are large
|
||||
proxy_buffer_size 16k;
|
||||
proxy_buffers 8 16k;
|
||||
}
|
||||
|
||||
# The sign-in check itself, only reachable as an auth_request subrequest
|
||||
location = /oauth2/auth {
|
||||
internal;
|
||||
proxy_pass http://127.0.0.1:4180;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Uri $request_uri;
|
||||
proxy_set_header Content-Length "";
|
||||
proxy_pass_request_body off;
|
||||
proxy_buffer_size 16k;
|
||||
proxy_buffers 8 16k;
|
||||
}
|
||||
|
||||
# ---------- Dashboard ----------
|
||||
|
||||
location / {
|
||||
auth_request /oauth2/auth;
|
||||
# Not signed in: straight to Microsoft's sign-in, then back to this page
|
||||
error_page 401 = /oauth2/start;
|
||||
# Make browsers check for a newer page/script on every load (a cheap 304 when unchanged),
|
||||
# so an update shows up at once. (expires, not add_header: an add_header here would drop
|
||||
# the security headers above.)
|
||||
expires -1;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
auth_request /oauth2/auth;
|
||||
# No redirect here: API calls from the page get a plain 401, and the page asks the user to
|
||||
# reload, which goes through sign-in above.
|
||||
|
||||
# Who's signed in, for the dashboard's "signed in as" (proxy_set_header replaces any value
|
||||
# the browser sent, so these can't be spoofed)
|
||||
auth_request_set $auth_email $upstream_http_x_auth_request_email;
|
||||
auth_request_set $auth_user $upstream_http_x_auth_request_user;
|
||||
auth_request_set $auth_name $upstream_http_x_auth_request_preferred_username;
|
||||
proxy_set_header X-Forwarded-Email $auth_email;
|
||||
proxy_set_header X-Forwarded-User $auth_user;
|
||||
proxy_set_header X-Forwarded-Preferred-Username $auth_name;
|
||||
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Collectors authenticate with their own API key (Authorization: Bearer …), checked by the app
|
||||
# Collectors authenticate with their own API key (Authorization: Bearer …), checked by the app,
|
||||
# so no Microsoft sign-in here
|
||||
location = /api/v1/ingest {
|
||||
auth_basic off;
|
||||
client_max_body_size 256k;
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# oauth2-proxy config for Backup Dashboard's Microsoft 365 (Entra ID) sign-in.
|
||||
# Install as /etc/oauth2-proxy/backup-dashboard.cfg, readable only by root and the oauth2-proxy
|
||||
# user (it holds the cookie secret). Needs oauth2-proxy 7.8 or newer for the entra-id provider.
|
||||
# Replace the four <...> values; see the README's "Microsoft 365 sign-in" section.
|
||||
|
||||
## Where it listens: only nginx talks to it
|
||||
http_address = "127.0.0.1:4180"
|
||||
reverse_proxy = true
|
||||
# Only nginx (on this machine) may supply X-Forwarded-* headers
|
||||
trusted_proxy_ips = ["127.0.0.1/32", "::1/128"]
|
||||
|
||||
## Entra ID app registration (single tenant)
|
||||
provider = "entra-id"
|
||||
oidc_issuer_url = "https://login.microsoftonline.com/<TENANT_ID>/v2.0"
|
||||
client_id = "<APPLICATION_CLIENT_ID>"
|
||||
# The client secret's *value*, in a file of its own, root:oauth2-proxy, mode 640
|
||||
client_secret_file = "/etc/oauth2-proxy/client-secret"
|
||||
redirect_url = "https://<DASHBOARD_HOST>/oauth2/callback"
|
||||
|
||||
## Who may sign in
|
||||
# Anyone in the tenant that Entra lets through. Limit it in Entra itself: Enterprise
|
||||
# applications -> this app -> Properties -> "Assignment required" = Yes, then assign users or a
|
||||
# group. Entra then refuses everyone else before they ever reach the dashboard.
|
||||
email_domains = ["*"]
|
||||
# The user's sign-in name (UPN), which every work account has. The "email" claim is only present
|
||||
# when the account has a mailbox address.
|
||||
oidc_email_claim = "preferred_username"
|
||||
|
||||
## Session cookie
|
||||
# Generate with: openssl rand -base64 32 | tr -- '+/' '-_'
|
||||
cookie_secret = "<COOKIE_SECRET>"
|
||||
cookie_name = "_backup_dashboard"
|
||||
cookie_secure = true
|
||||
cookie_samesite = "lax"
|
||||
# Sign in again (usually silently, via the Microsoft session) after this long
|
||||
cookie_expire = "12h"
|
||||
|
||||
## Behaviour with nginx's auth_request
|
||||
# Auth-only: nginx serves the dashboard; oauth2-proxy just answers "signed in or not"
|
||||
upstreams = ["static://202"]
|
||||
# Put the user in X-Auth-Request-* headers on /oauth2/auth responses, for nginx to pass on
|
||||
set_xauthrequest = true
|
||||
# Go straight to Microsoft instead of showing a "Sign in with..." page first
|
||||
skip_provider_button = true
|
||||
# Allow "Sign out" to continue to Microsoft's sign-out page
|
||||
whitelist_domains = ["login.microsoftonline.com"]
|
||||
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=oauth2-proxy (Microsoft 365 sign-in for Backup Dashboard)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
User=oauth2-proxy
|
||||
Group=oauth2-proxy
|
||||
ExecStart=/usr/local/bin/oauth2-proxy --config=/etc/oauth2-proxy/backup-dashboard.cfg
|
||||
Restart=on-failure
|
||||
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user