nixpkgs/nixos/modules/services/networking/bird-lg.nix
Moritz 'e1mo' Fromm b63e0d77b8 nixos/bird-lg: Rework command attribute generation
Prior to this change, arguments were not escaped nor was the possiblity
for arguments to be empty accounted for. This led to a kinda broken
startup script were arguments were "shifted", e.g. leaving allowedIPs
empty in order to use the default would cause `--bird` (the following
arguments key) to be used as the value. This was also observable when
e.g. the navbarBrand had a space in it where only everything until the
first space would show up.

With the new approach, all arguments are consistently escaped and empty
ones left out.

`extraConfig` now supports and prefers lists of strings instead of
lines (still supported but warned). This is due to the fragility with
respect to e.g. forgetting trailing backslashes after each line.
`frontend.{servers,domain}` are unset by default since the frontend
needs (the upstream project itself has no empty defaults here) needs
them to be set. If not set, an error is caused at build-time.

`proxy.birdSocket` has a new default: The projects README[^1] states
`/var/run/bird/bird.ctl` as the current default value. And bird2 on
NixOS does use this path too.

[^1]: https://github.com/xddxdd/bird-lg-go#proxy
2023-04-25 10:41:35 +02:00

308 lines
9.3 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.bird-lg;
stringOrConcat = sep: v: if builtins.isString v then v else concatStringsSep sep v;
frontend_args = let
fe = cfg.frontend;
in {
"--servers" = concatStringsSep "," fe.servers;
"--domain" = fe.domain;
"--listen" = fe.listenAddress;
"--proxy-port" = fe.proxyPort;
"--whois" = fe.whois;
"--dns-interface" = fe.dnsInterface;
"--bgpmap-info" = concatStringsSep "," cfg.frontend.bgpMapInfo;
"--title-brand" = fe.titleBrand;
"--navbar-brand" = fe.navbar.brand;
"--navbar-brand-url" = fe.navbar.brandURL;
"--navbar-all-servers" = fe.navbar.allServers;
"--navbar-all-url" = fe.navbar.allServersURL;
"--net-specific-mode" = fe.netSpecificMode;
"--protocol-filter" = concatStringsSep "," cfg.frontend.protocolFilter;
};
proxy_args = let
px = cfg.proxy;
in {
"--allowed" = concatStringsSep "," px.allowedIPs;
"--bird" = px.birdSocket;
"--listen" = px.listenAddress;
"--traceroute_bin" = px.traceroute.binary;
"--traceroute_raw" = px.traceroute.rawOutput;
};
mkArgValue = value:
if isString value
then escapeShellArg value
else if isBool value
then boolToString value
else toString value;
filterNull = filterAttrs (_: v: v != "" && v != null && v != []);
argsAttrToList = args: mapAttrsToList (name: value: "${name} " + mkArgValue value ) (filterNull args);
in
{
options = {
services.bird-lg = {
package = mkOption {
type = types.package;
default = pkgs.bird-lg;
defaultText = literalExpression "pkgs.bird-lg";
description = lib.mdDoc "The Bird Looking Glass package to use.";
};
user = mkOption {
type = types.str;
default = "bird-lg";
description = lib.mdDoc "User to run the service.";
};
group = mkOption {
type = types.str;
default = "bird-lg";
description = lib.mdDoc "Group to run the service.";
};
frontend = {
enable = mkEnableOption (lib.mdDoc "Bird Looking Glass Frontend Webserver");
listenAddress = mkOption {
type = types.str;
default = "127.0.0.1:5000";
description = lib.mdDoc "Address to listen on.";
};
proxyPort = mkOption {
type = types.port;
default = 8000;
description = lib.mdDoc "Port bird-lg-proxy is running on.";
};
domain = mkOption {
type = types.str;
example = "dn42.lantian.pub";
description = lib.mdDoc "Server name domain suffixes.";
};
servers = mkOption {
type = types.listOf types.str;
example = [ "gigsgigscloud" "hostdare" ];
description = lib.mdDoc "Server name prefixes.";
};
whois = mkOption {
type = types.str;
default = "whois.verisign-grs.com";
description = lib.mdDoc "Whois server for queries.";
};
dnsInterface = mkOption {
type = types.str;
default = "asn.cymru.com";
description = lib.mdDoc "DNS zone to query ASN information.";
};
bgpMapInfo = mkOption {
type = types.listOf types.str;
default = [ "asn" "as-name" "ASName" "descr" ];
description = lib.mdDoc "Information displayed in bgpmap.";
};
titleBrand = mkOption {
type = types.str;
default = "Bird-lg Go";
description = lib.mdDoc "Prefix of page titles in browser tabs.";
};
netSpecificMode = mkOption {
type = types.str;
default = "";
example = "dn42";
description = lib.mdDoc "Apply network-specific changes for some networks.";
};
protocolFilter = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "ospf" ];
description = lib.mdDoc "Information displayed in bgpmap.";
};
nameFilter = mkOption {
type = types.str;
default = "";
example = "^ospf";
description = lib.mdDoc "Protocol names to hide in summary tables (RE2 syntax),";
};
timeout = mkOption {
type = types.int;
default = 120;
description = lib.mdDoc "Time before request timed out, in seconds.";
};
navbar = {
brand = mkOption {
type = types.str;
default = "Bird-lg Go";
description = lib.mdDoc "Brand to show in the navigation bar .";
};
brandURL = mkOption {
type = types.str;
default = "/";
description = lib.mdDoc "URL of the brand to show in the navigation bar.";
};
allServers = mkOption {
type = types.str;
default = "ALL Servers";
description = lib.mdDoc "Text of 'All server' button in the navigation bar.";
};
allServersURL = mkOption {
type = types.str;
default = "all";
description = lib.mdDoc "URL of 'All servers' button.";
};
};
extraArgs = mkOption {
type = with types; either lines (listOf str);
default = [ ];
description = lib.mdDoc ''
Extra parameters documented [here](https://github.com/xddxdd/bird-lg-go#frontend).
:::{.note}
Passing lines (plain strings) is deprecated in favour of passing lists of strings.
:::
'';
};
};
proxy = {
enable = mkEnableOption (lib.mdDoc "Bird Looking Glass Proxy");
listenAddress = mkOption {
type = types.str;
default = "127.0.0.1:8000";
description = lib.mdDoc "Address to listen on.";
};
allowedIPs = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "192.168.25.52" "192.168.25.53" ];
description = lib.mdDoc "List of IPs to allow (default all allowed).";
};
birdSocket = mkOption {
type = types.str;
default = "/var/run/bird/bird.ctl";
description = lib.mdDoc "Bird control socket path.";
};
traceroute = {
binary = mkOption {
type = types.str;
default = "${pkgs.traceroute}/bin/traceroute";
defaultText = literalExpression ''"''${pkgs.traceroute}/bin/traceroute"'';
description = lib.mdDoc "Traceroute's binary path.";
};
rawOutput = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc "Display traceroute output in raw format.";
};
};
extraArgs = mkOption {
type = with types; either lines (listOf str);
default = [ ];
description = lib.mdDoc ''
Extra parameters documented [here](https://github.com/xddxdd/bird-lg-go#proxy).
:::{.note}
Passing lines (plain strings) is deprecated in favour of passing lists of strings.
:::
'';
};
};
};
};
###### implementation
config = {
warnings =
lib.optional (cfg.frontend.enable && builtins.isString cfg.frontend.extraArgs) ''
Passing strings to `services.bird-lg.frontend.extraOptions' is deprecated. Please pass a list of strings instead.
''
++ lib.optional (cfg.proxy.enable && builtins.isString cfg.proxy.extraArgs) ''
Passing strings to `services.bird-lg.proxy.extraOptions' is deprecated. Please pass a list of strings instead.
''
;
systemd.services = {
bird-lg-frontend = mkIf cfg.frontend.enable {
enable = true;
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
description = "Bird Looking Glass Frontend Webserver";
serviceConfig = {
Type = "simple";
Restart = "on-failure";
ProtectSystem = "full";
ProtectHome = "yes";
MemoryDenyWriteExecute = "yes";
User = cfg.user;
Group = cfg.group;
};
script = ''
${cfg.package}/bin/frontend \
${concatStringsSep " \\\n " (argsAttrToList frontend_args)} \
${stringOrConcat " " cfg.frontend.extraArgs}
'';
};
bird-lg-proxy = mkIf cfg.proxy.enable {
enable = true;
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
description = "Bird Looking Glass Proxy";
serviceConfig = {
Type = "simple";
Restart = "on-failure";
ProtectSystem = "full";
ProtectHome = "yes";
MemoryDenyWriteExecute = "yes";
User = cfg.user;
Group = cfg.group;
};
script = ''
${cfg.package}/bin/proxy \
${concatStringsSep " \\\n " (argsAttrToList proxy_args)} \
${stringOrConcat " " cfg.proxy.extraArgs}
'';
};
};
users = mkIf (cfg.frontend.enable || cfg.proxy.enable) {
groups."bird-lg" = mkIf (cfg.group == "bird-lg") { };
users."bird-lg" = mkIf (cfg.user == "bird-lg") {
description = "Bird Looking Glass user";
extraGroups = lib.optionals (config.services.bird2.enable) [ "bird2" ];
group = cfg.group;
isSystemUser = true;
};
};
};
}