nixpkgs/nixos/modules/services/web-servers/nginx/tailscale-auth.nix
2023-12-06 20:57:38 +01:00

159 lines
5.1 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.nginx.tailscaleAuth;
in
{
options.services.nginx.tailscaleAuth = {
enable = mkEnableOption (lib.mdDoc "Enable tailscale.nginx-auth, to authenticate nginx users via tailscale.");
package = lib.mkPackageOptionMD pkgs "tailscale-nginx-auth" {};
user = mkOption {
type = types.str;
default = "tailscale-nginx-auth";
description = lib.mdDoc "User which runs tailscale-nginx-auth";
};
group = mkOption {
type = types.str;
default = "tailscale-nginx-auth";
description = lib.mdDoc "Group which runs tailscale-nginx-auth";
};
expectedTailnet = mkOption {
default = "";
type = types.nullOr types.str;
example = "tailnet012345.ts.net";
description = lib.mdDoc ''
If you want to prevent node sharing from allowing users to access services
across tailnets, declare your expected tailnets domain here.
'';
};
socketPath = mkOption {
default = "/run/tailscale-nginx-auth/tailscale-nginx-auth.sock";
type = types.path;
description = lib.mdDoc ''
Path of the socket listening to nginx authorization requests.
'';
};
virtualHosts = mkOption {
type = types.listOf types.str;
default = [];
description = lib.mdDoc ''
A list of nginx virtual hosts to put behind tailscale.nginx-auth
'';
};
};
config = mkIf cfg.enable {
services.tailscale.enable = true;
services.nginx.enable = true;
users.users.${cfg.user} = {
isSystemUser = true;
inherit (cfg) group;
};
users.groups.${cfg.group} = { };
users.users.${config.services.nginx.user}.extraGroups = [ cfg.group ];
systemd.sockets.tailscale-nginx-auth = {
description = "Tailscale NGINX Authentication socket";
partOf = [ "tailscale-nginx-auth.service" ];
wantedBy = [ "sockets.target" ];
listenStreams = [ cfg.socketPath ];
socketConfig = {
SocketMode = "0660";
SocketUser = cfg.user;
SocketGroup = cfg.group;
};
};
systemd.services.tailscale-nginx-auth = {
description = "Tailscale NGINX Authentication service";
after = [ "nginx.service" ];
wants = [ "nginx.service" ];
requires = [ "tailscale-nginx-auth.socket" ];
serviceConfig = {
ExecStart = "${lib.getExe cfg.package}";
RuntimeDirectory = "tailscale-nginx-auth";
User = cfg.user;
Group = cfg.group;
BindPaths = [ "/run/tailscale/tailscaled.sock" ];
CapabilityBoundingSet = "";
DeviceAllow = "";
LockPersonality = true;
MemoryDenyWriteExecute = true;
PrivateDevices = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
RestrictNamespaces = true;
RestrictAddressFamilies = [ "AF_UNIX" ];
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallErrorNumber = "EPERM";
SystemCallFilter = [
"@system-service"
"~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@setuid"
];
};
};
services.nginx.virtualHosts = genAttrs
cfg.virtualHosts
(vhost: {
locations."/auth" = {
extraConfig = ''
internal;
proxy_pass http://unix:${cfg.socketPath};
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;
'';
};
locations."/".extraConfig = ''
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";
${lib.optionalString (cfg.expectedTailnet != "") ''proxy_set_header Expected-Tailnet "${cfg.expectedTailnet}";''}
'';
});
};
meta.maintainers = with maintainers; [ phaer ];
}