Securing simple web services behind caddy with SSO
The Problem
I have a simple WebService running behind Caddy on some server1.my.example.fqdn
, and my SSO provider running at auth.my.example.fqdn
. The simple WebService should be protected by a login. I could configure the service to use HTTP Basic Auth, but then I’d have to manage users. I would prefer to use my existing SSO via OIDC. For this use case, any authenticated user should have full and complete access to the service.
Unfortunately, Caddy also does not support OIDC out of the box. I had a surprisingly difficult time getting SSO working the way I wanted here, so I’m quickly recording what I’ve done.
Solution
Configure outh2-proxy and forward auth with Caddy. There are other solutions, but this generally seems to work.
All configurations below assume the SSO provider is already configured.
Docker Compose
services:
caddy:
image: caddy:latest
restart: unless-stopped
networks:
- caddy
container_name: caddy
cap_add:
- NET_ADMIN
- CAP_NET_BIND_SERVICE
- CAP_NET_RAW
ports:
- "80:80" # For redirect
- "443:443"
- "443:443/udp"
volumes:
- confdir/Caddyfile:/etc/caddy/Caddyfile
- datadir:/data
- confdir:/config
environment:
ACME_AGREE: "true"
oauth2-proxy:
container_name: oauth2-proxy
image: bitnami/oauth2-proxy:latest
restart: unless-stopped
command: "oauth2-proxy --email-domain=*"
# WARNING: Many env variables seem to be completely ignored
environment:
- OAUTH2_PROXY_PROVIDER=oidc
- OAUTH2_PROXY_CLIENT_ID=CLIENT_ID_GOES_HERE
- OAUTH2_PROXY_CLIENT_SECRET=CLIENT_SECRET_GOES_HERE
- OAUTH2_PROXY_REDIRECT_URL=https://server1.myexample.fqdn/oauth2/callback
- OAUTH2_PROXY_OIDC_ISSUER_URL=https://auth.my.example.fqdn/application/o/server1/
- OAUTH2_PROXY_COOKIE_SECRET=73EgCj7ktL51UWJH5B0c7lZ9cKPeNmgX5qeBUcsWG7s=
- OAUTH2_PROXY_COOKIE_SECURE=true
- OAUTH2_PROXY_COOKIE_DOMAINS=server1.my.example.fqdn
- OAUTH2_PROXY_WHITELIST_DOMAINS=server1.my.example.fqn
- OAUTH2_PROXY_UPSTREAMS=static://200
- OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180
- OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true
- OAUTH2_PROXY_CODE_CHALLENGE_METHOD=S256
networks:
- caddy
server1:
container_name: server1
image: example/image/here:latest
networks:
- caddy
networks:
caddy:
Important notes
OAUTH2_PROXY_UPSTREAMS=static://200
was a somewhat difficult to dig out setting to respond with http 200 and let Caddy proceed with the reverse proxying after the forward auth. I found that some services worked fine with setting OAUTH2_PROXY_UPSTREAMS=server1:8080
, but some did not.
--email-domain="
is specified in the command. It seems the ouath-proxy image for both bitnami and the project build image (not used here) ignore some documented configurations when using environment variables. In theory, OAUTH2_PROXY_EMAIL_DOMAINS=*
should work but does not.
- OAUTH2_PROXY_CODE_CHALLENGE_METHOD=S256
will vary based on the SSO provider.
Caddyfile
server1.my.example.fqdn {
# Requests to /oauth2/* are proxied to oauth2-proxy without authentication.
# You can't use `reverse_proxy /oauth2/* oauth2-proxy.internal:4180` here because the reverse_proxy directive has lower precedence than the handle directive.
handle /oauth2/* {
reverse_proxy oauth2-proxy:4180 {
# oauth2-proxy requires the X-Real-IP and X-Forwarded-{Proto,Host,Uri} headers.
# The reverse_proxy directive automatically sets X-Forwarded-{For,Proto,Host} headers.
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Uri {uri}
}
}
# Requests to other paths are first processed by oauth2-proxy for authentication.
handle {
forward_auth oauth2-proxy:4180 {
# uri /oauth2/auth # Checking if order
uri /oauth2/
# oauth2-proxy requires the X-Real-IP and X-Forwarded-{Proto,Host,Uri} headers.
# The forward_auth directive automatically sets the X-Forwarded-{For,Proto,Host,Method,Uri} headers.
header_up X-Real-IP {remote_host}
# If needed, you can copy headers from the oauth2-proxy response to the request sent to the upstream.
# Make sure to configure the --set-xauthrequest flag to enable this feature.
#copy_headers X-Auth-Request-User X-Auth-Request-Email
# If oauth2-proxy returns a 401 status, redirect the client to the sign-in page.
@error status 401
handle_response @error {
redir * /oauth2/sign_in?rd={scheme}://{host}{uri}
}
}
}
reverse_proxy server1:8080
}