1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7let
8 inherit (lib)
9 mkEnableOption
10 mkPackageOption
11 mkOption
12 types
13 ;
14
15 cfg = config.services.wg-access-server;
16
17 settingsFormat = pkgs.formats.yaml { };
18 configFile = settingsFormat.generate "config.yaml" cfg.settings;
19in
20{
21
22 options.services.wg-access-server = {
23 enable = mkEnableOption "wg-access-server";
24
25 package = mkPackageOption pkgs "wg-access-server" { };
26
27 settings = mkOption {
28 type = lib.types.submodule {
29 freeformType = settingsFormat.type;
30 options = {
31 dns.enabled = mkOption {
32 type = types.bool;
33 default = true;
34 description = ''
35 Enable/disable the embedded DNS proxy server.
36 This is enabled by default and allows VPN clients to avoid DNS leaks by sending all DNS requests to wg-access-server itself.
37 '';
38 };
39 storage = mkOption {
40 type = types.str;
41 default = "sqlite3://db.sqlite";
42 description = "A storage backend connection string. See [storage docs](https://www.freie-netze.org/wg-access-server/3-storage/)";
43 };
44 };
45 };
46 description = "See <https://www.freie-netze.org/wg-access-server/2-configuration/> for possible options";
47 };
48
49 secretsFile = mkOption {
50 type = types.path;
51 description = ''
52 yaml file containing all secrets. this needs to be in the same structure as the configuration.
53
54 This must to contain the admin password and wireguard private key.
55 As well as the secrets for your auth backend.
56
57 Example:
58 ```yaml
59 adminPassword: <admin password>
60 wireguard:
61 privateKey: <wireguard private key>
62 auth:
63 oidc:
64 clientSecret: <client secret>
65 ```
66 '';
67 };
68 };
69
70 config = lib.mkIf cfg.enable {
71 assertions =
72 map
73 (attrPath: {
74 assertion = !lib.hasAttrByPath attrPath config.services.wg-access-server.settings;
75 message = ''
76 {option}`services.wg-access-server.settings.${lib.concatStringsSep "." attrPath}` must definded
77 in {option}`services.wg-access-server.secretsFile`.
78 '';
79 })
80 [
81 [ "adminPassword" ]
82 [
83 "wireguard"
84 "privateKey"
85 ]
86 [
87 "auth"
88 "sessionStore"
89 ]
90 [
91 "auth"
92 "oidc"
93 "clientSecret"
94 ]
95 [
96 "auth"
97 "gitlab"
98 "clientSecret"
99 ]
100 ];
101
102 boot.kernel.sysctl = {
103 "net.ipv4.conf.all.forwarding" = "1";
104 "net.ipv6.conf.all.forwarding" = "1";
105 };
106
107 systemd.services.wg-access-server = {
108 description = "WG access server";
109 wantedBy = [ "multi-user.target" ];
110 requires = [ "network-online.target" ];
111 after = [ "network-online.target" ];
112 script = ''
113 # merge secrets into main config
114 yq eval-all "select(fileIndex == 0) * select(fileIndex == 1)" ${configFile} $CREDENTIALS_DIRECTORY/SECRETS_FILE \
115 > "$STATE_DIRECTORY/config.yml"
116
117 ${lib.getExe cfg.package} serve --config "$STATE_DIRECTORY/config.yml"
118 '';
119
120 path = with pkgs; [
121 iptables
122 # needed by startup script
123 yq-go
124 ];
125
126 serviceConfig =
127 let
128 capabilities = [
129 "CAP_NET_ADMIN"
130 ]
131 ++ lib.optional cfg.settings.dns.enabled "CAP_NET_BIND_SERVICE";
132 in
133 {
134 WorkingDirectory = "/var/lib/wg-access-server";
135 StateDirectory = "wg-access-server";
136
137 LoadCredential = [
138 "SECRETS_FILE:${cfg.secretsFile}"
139 ];
140
141 # Hardening
142 DynamicUser = true;
143 AmbientCapabilities = capabilities;
144 CapabilityBoundingSet = capabilities;
145 };
146 };
147 };
148}