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 ] ++ lib.optional cfg.settings.dns.enabled "CAP_NET_BIND_SERVICE";
131 in
132 {
133 WorkingDirectory = "/var/lib/wg-access-server";
134 StateDirectory = "wg-access-server";
135
136 LoadCredential = [
137 "SECRETS_FILE:${cfg.secretsFile}"
138 ];
139
140 # Hardening
141 DynamicUser = true;
142 AmbientCapabilities = capabilities;
143 CapabilityBoundingSet = capabilities;
144 };
145 };
146 };
147}