vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / networking / keepalived / default.nix
blob1eaf0fd8b4aa835794bc1575a31e824a81b33e28
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
7   cfg = config.services.keepalived;
9   keepalivedConf = pkgs.writeText "keepalived.conf" ''
10     global_defs {
11       ${optionalString cfg.enableScriptSecurity "enable_script_security"}
12       ${snmpGlobalDefs}
13       ${cfg.extraGlobalDefs}
14     }
16     ${vrrpScriptStr}
17     ${vrrpInstancesStr}
18     ${cfg.extraConfig}
19   '';
21   snmpGlobalDefs = with cfg.snmp; optionalString enable (
22     optionalString (socket != null) "snmp_socket ${socket}\n"
23     + optionalString enableKeepalived "enable_snmp_keepalived\n"
24     + optionalString enableChecker "enable_snmp_checker\n"
25     + optionalString enableRfc "enable_snmp_rfc\n"
26     + optionalString enableRfcV2 "enable_snmp_rfcv2\n"
27     + optionalString enableRfcV3 "enable_snmp_rfcv3\n"
28     + optionalString enableTraps "enable_traps"
29   );
31   vrrpScriptStr = concatStringsSep "\n" (map (s:
32     ''
33       vrrp_script ${s.name} {
34         script "${s.script}"
35         interval ${toString s.interval}
36         fall ${toString s.fall}
37         rise ${toString s.rise}
38         timeout ${toString s.timeout}
39         weight ${toString s.weight}
40         user ${s.user} ${optionalString (s.group != null) s.group}
42         ${s.extraConfig}
43       }
44     ''
45   ) vrrpScripts);
47   vrrpInstancesStr = concatStringsSep "\n" (map (i:
48     ''
49       vrrp_instance ${i.name} {
50         interface ${i.interface}
51         state ${i.state}
52         virtual_router_id ${toString i.virtualRouterId}
53         priority ${toString i.priority}
54         ${optionalString i.noPreempt "nopreempt"}
56         ${optionalString i.useVmac (
57           "use_vmac" + optionalString (i.vmacInterface != null) " ${i.vmacInterface}"
58         )}
59         ${optionalString i.vmacXmitBase "vmac_xmit_base"}
61         ${optionalString (i.unicastSrcIp != null) "unicast_src_ip ${i.unicastSrcIp}"}
62         ${optionalString (builtins.length i.unicastPeers > 0) ''
63           unicast_peer {
64             ${concatStringsSep "\n" i.unicastPeers}
65           }
66         ''}
68         virtual_ipaddress {
69           ${concatMapStringsSep "\n" virtualIpLine i.virtualIps}
70         }
72         ${optionalString (builtins.length i.trackScripts > 0) ''
73           track_script {
74             ${concatStringsSep "\n" i.trackScripts}
75           }
76         ''}
78         ${optionalString (builtins.length i.trackInterfaces > 0) ''
79           track_interface {
80             ${concatStringsSep "\n" i.trackInterfaces}
81           }
82         ''}
84         ${i.extraConfig}
85       }
86     ''
87   ) vrrpInstances);
89   virtualIpLine = ip: ip.addr
90     + optionalString (notNullOrEmpty ip.brd) " brd ${ip.brd}"
91     + optionalString (notNullOrEmpty ip.dev) " dev ${ip.dev}"
92     + optionalString (notNullOrEmpty ip.scope) " scope ${ip.scope}"
93     + optionalString (notNullOrEmpty ip.label) " label ${ip.label}";
95   notNullOrEmpty = s: !(s == null || s == "");
97   vrrpScripts = mapAttrsToList (name: config:
98     {
99       inherit name;
100     } // config
101   ) cfg.vrrpScripts;
103   vrrpInstances = mapAttrsToList (iName: iConfig:
104     {
105       name = iName;
106     } // iConfig
107   ) cfg.vrrpInstances;
109   vrrpInstanceAssertions = i: [
110     { assertion = i.interface != "";
111       message = "services.keepalived.vrrpInstances.${i.name}.interface option cannot be empty.";
112     }
113     { assertion = i.virtualRouterId >= 0 && i.virtualRouterId <= 255;
114       message = "services.keepalived.vrrpInstances.${i.name}.virtualRouterId must be an integer between 0..255.";
115     }
116     { assertion = i.priority >= 0 && i.priority <= 255;
117       message = "services.keepalived.vrrpInstances.${i.name}.priority must be an integer between 0..255.";
118     }
119     { assertion = i.vmacInterface == null || i.useVmac;
120       message = "services.keepalived.vrrpInstances.${i.name}.vmacInterface has no effect when services.keepalived.vrrpInstances.${i.name}.useVmac is not set.";
121     }
122     { assertion = !i.vmacXmitBase || i.useVmac;
123       message = "services.keepalived.vrrpInstances.${i.name}.vmacXmitBase has no effect when services.keepalived.vrrpInstances.${i.name}.useVmac is not set.";
124     }
125   ] ++ flatten (map (virtualIpAssertions i.name) i.virtualIps)
126     ++ flatten (map (vrrpScriptAssertion i.name) i.trackScripts);
128   virtualIpAssertions = vrrpName: ip: [
129     { assertion = ip.addr != "";
130       message = "The 'addr' option for an services.keepalived.vrrpInstances.${vrrpName}.virtualIps entry cannot be empty.";
131     }
132   ];
134   vrrpScriptAssertion = vrrpName: scriptName: {
135     assertion = builtins.hasAttr scriptName cfg.vrrpScripts;
136     message = "services.keepalived.vrrpInstances.${vrrpName} trackscript ${scriptName} is not defined in services.keepalived.vrrpScripts.";
137   };
139   pidFile = "/run/keepalived.pid";
143   meta.maintainers = [ lib.maintainers.raitobezarius ];
145   options = {
146     services.keepalived = {
148       enable = mkOption {
149         type = types.bool;
150         default = false;
151         description = ''
152           Whether to enable Keepalived.
153         '';
154       };
156       openFirewall = mkOption {
157         type = types.bool;
158         default = false;
159         description = ''
160           Whether to automatically allow VRRP and AH packets in the firewall.
161         '';
162       };
164       enableScriptSecurity = mkOption {
165         type = types.bool;
166         default = false;
167         description = ''
168           Don't run scripts configured to be run as root if any part of the path is writable by a non-root user.
169         '';
170       };
172       snmp = {
174         enable = mkOption {
175           type = types.bool;
176           default = false;
177           description = ''
178             Whether to enable the builtin AgentX subagent.
179           '';
180         };
182         socket = mkOption {
183           type = types.nullOr types.str;
184           default = null;
185           description = ''
186             Socket to use for connecting to SNMP master agent. If this value is
187             set to null, keepalived's default will be used, which is
188             unix:/var/agentx/master, unless using a network namespace, when the
189             default is udp:localhost:705.
190           '';
191         };
193         enableKeepalived = mkOption {
194           type = types.bool;
195           default = false;
196           description = ''
197             Enable SNMP handling of vrrp element of KEEPALIVED MIB.
198           '';
199         };
201         enableChecker = mkOption {
202           type = types.bool;
203           default = false;
204           description = ''
205             Enable SNMP handling of checker element of KEEPALIVED MIB.
206           '';
207         };
209         enableRfc = mkOption {
210           type = types.bool;
211           default = false;
212           description = ''
213             Enable SNMP handling of RFC2787 and RFC6527 VRRP MIBs.
214           '';
215         };
217         enableRfcV2 = mkOption {
218           type = types.bool;
219           default = false;
220           description = ''
221             Enable SNMP handling of RFC2787 VRRP MIB.
222           '';
223         };
225         enableRfcV3 = mkOption {
226           type = types.bool;
227           default = false;
228           description = ''
229             Enable SNMP handling of RFC6527 VRRP MIB.
230           '';
231         };
233         enableTraps = mkOption {
234           type = types.bool;
235           default = false;
236           description = ''
237             Enable SNMP traps.
238           '';
239         };
241       };
243       vrrpScripts = mkOption {
244         type = types.attrsOf (types.submodule (import ./vrrp-script-options.nix {
245           inherit lib;
246         }));
247         default = {};
248         description = "Declarative vrrp script config";
249       };
251       vrrpInstances = mkOption {
252         type = types.attrsOf (types.submodule (import ./vrrp-instance-options.nix {
253           inherit lib;
254         }));
255         default = {};
256         description = "Declarative vhost config";
257       };
259       extraGlobalDefs = mkOption {
260         type = types.lines;
261         default = "";
262         description = ''
263           Extra lines to be added verbatim to the 'global_defs' block of the
264           configuration file
265         '';
266       };
268       extraConfig = mkOption {
269         type = types.lines;
270         default = "";
271         description = ''
272           Extra lines to be added verbatim to the configuration file.
273         '';
274       };
276       secretFile = mkOption {
277         type = types.nullOr types.path;
278         default = null;
279         example = "/run/keys/keepalived.env";
280         description = ''
281           Environment variables from this file will be interpolated into the
282           final config file using envsubst with this syntax: `$ENVIRONMENT`
283           or `''${VARIABLE}`.
284           The file should contain lines formatted as `SECRET_VAR=SECRET_VALUE`.
285           This is useful to avoid putting secrets into the nix store.
286         '';
287       };
289     };
290   };
292   config = mkIf cfg.enable {
294     assertions = flatten (map vrrpInstanceAssertions vrrpInstances);
296     networking.firewall = lib.mkIf cfg.openFirewall {
297       extraCommands = ''
298         # Allow VRRP and AH packets
299         ip46tables -A nixos-fw -p vrrp -m comment --comment "services.keepalived.openFirewall" -j ACCEPT
300         ip46tables -A nixos-fw -p ah -m comment --comment "services.keepalived.openFirewall" -j ACCEPT
301       '';
303       extraStopCommands = ''
304         ip46tables -D nixos-fw -p vrrp -m comment --comment "services.keepalived.openFirewall" -j ACCEPT
305         ip46tables -D nixos-fw -p ah -m comment --comment "services.keepalived.openFirewall" -j ACCEPT
306       '';
307     };
309     systemd.timers.keepalived-boot-delay = {
310       description = "Keepalive Daemon delay to avoid instant transition to MASTER state";
311       after = [ "network.target" "network-online.target" "syslog.target" ];
312       requires = [ "network-online.target" ];
313       wantedBy = [ "multi-user.target" ];
314       timerConfig = {
315         OnActiveSec = "5s";
316         Unit = "keepalived.service";
317       };
318     };
320     systemd.services.keepalived = let
321       finalConfigFile = if cfg.secretFile == null then keepalivedConf else "/run/keepalived/keepalived.conf";
322     in {
323       description = "Keepalive Daemon (LVS and VRRP)";
324       after = [ "network.target" "network-online.target" "syslog.target" ];
325       wants = [ "network-online.target" ];
326       serviceConfig = {
327         Type = "forking";
328         PIDFile = pidFile;
329         KillMode = "process";
330         RuntimeDirectory = "keepalived";
331         EnvironmentFile = lib.optional (cfg.secretFile != null) cfg.secretFile;
332         ExecStartPre = lib.optional (cfg.secretFile != null)
333         (pkgs.writeShellScript "keepalived-pre-start" ''
334           umask 077
335           ${pkgs.envsubst}/bin/envsubst -i "${keepalivedConf}" > ${finalConfigFile}
336         '');
337         ExecStart = "${pkgs.keepalived}/sbin/keepalived"
338           + " -f ${finalConfigFile}"
339           + " -p ${pidFile}"
340           + optionalString cfg.snmp.enable " --snmp";
341         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
342         Restart = "always";
343         RestartSec = "1s";
344       };
345     };
346   };