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;
|
||||
|
||||
Reference in New Issue
Block a user