157 lines
4.4 KiB
Nix
157 lines
4.4 KiB
Nix
{
|
|
config,
|
|
pkgs,
|
|
lib,
|
|
...
|
|
}:
|
|
with lib;
|
|
let
|
|
cfg = config.qois.vpn-server;
|
|
cfgLoadbalancer = config.qois.loadbalancer;
|
|
defaultDnsRecords =
|
|
(mapAttrs (
|
|
name: value: mkIf (cfgLoadbalancer.hostmap ? ${value}) cfgLoadbalancer.hostmap.${value}
|
|
) cfgLoadbalancer.domains)
|
|
// {
|
|
"vpn.qo.is" = config.services.headscale.address;
|
|
};
|
|
in
|
|
{
|
|
|
|
options.qois.vpn-server = {
|
|
enable = mkEnableOption "Enable vpn server services";
|
|
domain = mkOption {
|
|
description = "Domain for the VPN admin server";
|
|
type = types.str;
|
|
default = "vpn.qo.is";
|
|
};
|
|
dnsRecords = mkOption {
|
|
description = "DNS records to add to Hosts";
|
|
type = with types; attrsOf str;
|
|
default = defaultDnsRecords;
|
|
};
|
|
wheelUsers = mkOption {
|
|
description = "Usernames that can change configurations";
|
|
type = with types; listOf str;
|
|
default = [ ];
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable ({
|
|
|
|
environment.systemPackages = [ pkgs.headscale ];
|
|
|
|
# We bind to the backplane vpn IP, so wait for the wireguard net to be available
|
|
systemd.services.headscale.after = [ "wireguard-wg-backplane.service" ];
|
|
|
|
qois.backup-client.includePaths =
|
|
with config.services.headscale.settings;
|
|
(
|
|
[
|
|
database.sqlite.path
|
|
derp.server.private_key_path
|
|
noise.private_key_path
|
|
]
|
|
++ derp.paths
|
|
);
|
|
|
|
networking.firewall.checkReversePath = "loose";
|
|
networking.firewall.allowedTCPPorts = [ config.services.headscale.port ];
|
|
networking.firewall.allowedUDPPorts = [
|
|
41641
|
|
];
|
|
services.headscale =
|
|
let
|
|
vnet = config.qois.meta.network.virtual;
|
|
vpnNet = vnet.vpn;
|
|
vpnNetPrefix = "${vpnNet.v4.id}/${toString vpnNet.v4.prefixLength}";
|
|
backplaneNetPrefix = "${vnet.backplane.v4.id}/${builtins.toString vnet.backplane.v4.prefixLength}";
|
|
in
|
|
{
|
|
enable = true;
|
|
address = vnet.backplane.hosts.cyprianspitz.v4.ip;
|
|
port = 46084;
|
|
settings = {
|
|
server_url = "https://${cfg.domain}:443";
|
|
|
|
tls_letsencrypt_challenge_type = "TLS-ALPN-01";
|
|
tls_letsencrypt_hostname = vpnNet.domain;
|
|
|
|
dns = {
|
|
base_domain = vpnNet.domain;
|
|
magic_dns = true;
|
|
nameservers.global = [ "127.0.0.1" ];
|
|
search_domains = [
|
|
# First is base_domain by default with magic_dns
|
|
vnet.backplane.domain
|
|
];
|
|
extra_records = pipe cfg.dnsRecords [
|
|
attrsToList
|
|
(map (val: val // { type = "A"; }))
|
|
];
|
|
};
|
|
|
|
ip_prefixes = [ vpnNetPrefix ];
|
|
|
|
policy =
|
|
let
|
|
# Note: headscale has limited acl support currently. This might change in the future.
|
|
aclPolicy = {
|
|
hosts = {
|
|
"clients" = vpnNetPrefix;
|
|
};
|
|
groups = {
|
|
"group:wheel" = cfg.wheelUsers;
|
|
};
|
|
tagOwners = {
|
|
"tag:srv" = [ "srv" ]; # srv tag ist owned by srv user
|
|
};
|
|
autoApprovers = {
|
|
exitNode = [
|
|
"tag:srv"
|
|
"group:wheel"
|
|
];
|
|
routes = {
|
|
${backplaneNetPrefix} = [ "tag:srv" ];
|
|
};
|
|
};
|
|
|
|
acls = [
|
|
# Allow all communication from and to srv tagged hosts
|
|
{
|
|
action = "accept";
|
|
src = [
|
|
"tag:srv"
|
|
"srv"
|
|
];
|
|
dst = [ "*:*" ];
|
|
}
|
|
{
|
|
action = "accept";
|
|
src = [ "*" ];
|
|
dst = [
|
|
"tag:srv:*"
|
|
"srv:*"
|
|
];
|
|
}
|
|
|
|
# Allow access to all connected hosts for wheels
|
|
{
|
|
action = "accept";
|
|
src = [ "group:wheel" ];
|
|
dst = [ "*:*" ];
|
|
}
|
|
];
|
|
};
|
|
in
|
|
{
|
|
mode = "file";
|
|
path = pkgs.writeTextFile {
|
|
name = "acls";
|
|
text = builtins.toJSON aclPolicy;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
});
|
|
}
|