TIL How to secure a web server with SSO
I was setting up an instance with the clearml-server for my team to start tracking their data science experiments and versioning their datasets and tracing artifacts. They were impressed with the application, however they raised some valid concerns:
Is it secure? Can we add SSO to this server?
I scoured the docs, apparently it’s available in the enterprise edition, but I was more keen to self-host and learn more about the process.
The details don’t really matter that much, but in a nutshell:
- Hosted on Port 5050
- Orchestrated with Docker Compose
Turns out nginx
might provide a solution! I came across a container for oauth2-proxy
, which can
be placed in front of my web app by nginx
. In short, nginx
directs traffic to oauth2-proxy
,
which then directs traffic to an underlying web server after a successful authentication.
On my server, I created an nginx
directory:
/opt/nginx/
├── certs
│ ├── tailscaled.crt
│ └── tailscaled.key
└── nginx.conf
I’m using tailscale
as my VPN solution for this project, which gives me a domain name that can
only be accessed on my network, and also can issue a signed SSL certificate when you enable HTTPS
and Magic DNS
on your network. The certificate can be generated with tailscale cert <domain-name>
.
The content of nginx.conf
is:
1worker_processes 1;
2
3events {}
4
5http {
6 log_format main '$remote_addr - $remote_user [$time_local] "$request" '
7 '$status $body_bytes_sent "$http_referer" '
8 '"$http_user_agent" "$http_x_forwarded_for"';
9
10 access_log /var/log/nginx/access.log main;
11
12 sendfile on;
13 tcp_nopush on;
14 tcp_nodelay on;
15 keepalive_timeout 65;
16 types_hash_max_size 2048;
17
18 include /etc/nginx/mime.types;
19 default_type application/octet-stream;
20
21 # HTTPS server block
22 server {
23 listen 443 ssl;
24
25 # Use Tailscale-provided SSL certificates
26 ssl_certificate /etc/nginx/certs/tailscaled.crt;
27 ssl_certificate_key /etc/nginx/certs/tailscaled.key;
28
29 # Proxy traffic to oauth2-proxy
30 location / {
31 proxy_pass http://oauth2-proxy:4180; # Ensure this matches the oauth2-proxy service
32 proxy_set_header Host $host;
33 proxy_set_header X-Real-IP $remote_addr;
34 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
35 proxy_set_header X-Forwarded-Proto $scheme;
36 }
37 }
38
39 # Optional HTTP-to-HTTPS redirect block
40 server {
41 listen 80;
42
43 return 301 https://$host$request_uri;
44 }
45}
If HTTPS
isn’t required, the content of the server block after listen 443 ssl;
can be moved into
the server { listen 80; ... }
block and the return 301
can be removed as well. Also ignore the
ssl_certificate
and ssl_certificate_key
.
I then used this docker-compose.yml
to spin up nginx
and oauth2-proxy
:
version: "3.6"
services:
nginx:
image: nginx:latest
container_name: nginx
volumes:
- /opt/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- /opt/nginx/certs:/etc/nginx/certs:ro
ports:
- "80:80"
- "443:443"
depends_on:
- oauth2-proxy
networks:
- frontend
oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:latest
container_name: oauth2-proxy
environment:
- OAUTH2_PROXY_CLIENT_ID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- OAUTH2_PROXY_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- OAUTH2_PROXY_REDIRECT_URL=https://my-webserver-name.tailnet1234.ts.net/oauth2/callback
- OAUTH2_PROXY_COOKIE_SECRET=XXXXXXXXXXXXXXXX
- OAUTH2_PROXY_PROVIDER=google # Or another OAuth provider
- OAUTH2_PROXY_UPSTREAMS=http://my-webserver-container:5050
- OAUTH2_PROXY_EMAIL_DOMAINS=company.com
- OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180 # Default OAuth2 proxy port
- OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true # Disable the OAuth2 provider button if needed
- OAUTH2_PROXY_COOKIE_SECURE=true # Enable if serving through HTTPS
- OAUTH2_PROXY_COOKIE_HTTPONLY=true # Use HTTP-only cookies for added security
ports:
- "4180:4180"
networks:
- frontend
restart: always
Now with all the containers running, I’m able to navigate to
https://my-webserver-name.tailnet1234.ts.net/oauth2/callback
in my browser when connected to my
VPN. I’m immediately redirected to an SSO page (from Google in this particular case). Once I sign in
and confirm my identity. I get redirected back to the domain and I’m able to access the server web
interface!
Also make sure to close the port for the underlying web app ;)