8 May 2024
Tailscale's nginx-auth doesn't provide an email
Initial setup
I’m currently running OpenWebUI behind a Nginx reverse proxy and inside a Tailscale network. I desperately don’t want to manage OpenWebUI users and ask them to manage an extra set of credentials. As of May 2024 OpenWebUI doesn’t support SSO, OAuth, SAML, or OIDC. Fortunately, it does support headers from an authenticating proxy.
To spice things up I’m running it with NixOS on arm, but it’s a minor part of this story.
Rough idea
Tailscale provides an nginx-auth that can send Tailscale user information as headers to Nginx upstream.
OpenWebUI expects a trusted authentication email header. If it matches an existing user, they are authenticated seamlessly; otherwise, a new user is created pending admin approval. If a request comes from outside the Tailscale network, regular login procedures apply.
The journey
Tailscale doesn’t provide an email header, sort of. Let’s take look at available headers from nginx-auth README.md file:
Header | Example Value |
---|---|
Tailscale-User | [email protected] |
Tailscale-Login | azurediamond |
Tailscale-Name | Azure Diamond |
Tailscale-Profile-Picture | https://i.kym-cdn.com/photos/images/newsfeed/001/065/963/ae0.png |
Tailscale-Tailnet | hunter2.net |
Tailscale-User
may look like a properly formatted email.
It is an email for my account with custom OIDC.
Same for Google accounts.
For GitHub, however, it is not.
There might be other cases when user isn’t an actual email, but I haven’t seen them yet.
GitHub’s accounts have ${GITHUB_USERNAME}@github
as a Tailscale-User
.
OpenWebUI doesn’t like it as an email and fails to register a user.
Since there is no email, let’s come up with one. Nginx can map a variable to a new one with some additional logic.
By this moment Nginx configuration looks like this:
. . .
http {
. . .
server {
. . .
location / {
. . .
auth_request /auth;
auth_request_set $auth_user $upstream_http_tailscale_user;
auth_request_set $auth_name $upstream_http_tailscale_name;
auth_request_set $auth_login $upstream_http_tailscale_login;
auth_request_set $auth_tailnet $upstream_http_tailscale_tailnet;
auth_request_set $auth_profile_picture $upstream_http_tailscale_profile_picture;
proxy_set_header X-Webauth-User "$auth_user";
proxy_set_header X-Webauth-Name "$auth_name";
proxy_set_header X-Webauth-Login "$auth_login";
proxy_set_header X-Webauth-Tailnet "$auth_tailnet";
proxy_set_header X-Webauth-Profile-Picture "$auth_profile_picture";
};
location /auth {
. . .
internal;
proxy_pass http://unix:/run/tailscale-nginx-auth/tailscale-nginx-auth.sock;
proxy_pass_request_body off;
# Upstream uses $http_host here, but we are using gixy to check nginx configurations
# gixy wants us to use $host: https://github.com/yandex/gixy/blob/master/docs/en/plugins/hostspoofing.md
proxy_set_header Host $host;
proxy_set_header Remote-Addr $remote_addr;
proxy_set_header Remote-Port $remote_port;
proxy_set_header Original-URI $request_uri;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
}
}
}
In my case it is set by NixOS option services.nginx.tailscaleAuth.virtualHosts.
I took $auth_user
and mapped it to $auth_email
.
GitHub’s users have @github
updated to @github-users.my.domain
.
Others are left as is.
. . .
http {
. . .
map $auth_user $auth_email {
~^(?<user>[^@]+)@github$ [email protected];
default $auth_user;
}
. . .
server {
. . .
location / {
. . .
# Should be places after $auth_user definition
proxy_set_header X-Webauth-Email "$auth_email";
. . .
}
}
}
The only thing left is to update OpenWebUI environment variable WEBUI_AUTH_TRUSTED_EMAIL_HEADER
to be X-Webauth-Email
or the lower case version of that.
After that GitHub users should bed greeted this way:
It’s not an actual email of a GitHub user, but it allows them to get into the system and me to understand weather it’s a highly unlikely GitHub employee or this email “hack”.
Additional detours
lower case header name
OpenWebUI and nginx-auth both provide upper case examples for the trusted authentication email header. My configurations of Nginx & nginx-auth pass lower case headers to OpenWebUI. OpenWebUI is case-sensitive.
NixOS specific
I’ve tried to contain all NixOS details in a separate section.
Resulting Nginx config
To review the active Nginx configuration you need to figure out its path.
systemctl cat nginx.service
Look for ExecStart
and -c
flag. It should look like /nix/store/somehashsum...-nginx.conf
Nginx’s configuration order and placement in NixOS
To place map into http
section of Nginx config I’ve used services.nginx.commonHttpConfig.
I’ve tried services.nginx.virtualHosts.$auth_user
.
This way a variable to map from is not defined yet.
I’ve ended up ditching services.nginx.tailscaleAuth.virtualHosts and defining all header and /auth
myself.
Packages & options
- Nginx: Configured via services.nginx.
- nginx-auth: Enabled with services.nginx.tailscaleAuth but without services.nginx.tailscaleAuth.virtualHosts.
- OpenWebUI: Currently not packaged in NixOS; I use a Docker image from ghcr.io.