depot/third_party/nixpkgs/nixos/modules/services/networking/nbd.nix

158 lines
4.9 KiB
Nix

{ config, lib, pkgs, ... }:
let
inherit (lib) mkIf mkOption types optionalAttrs;
inherit (lib.types) nullOr listOf str attrsOf submodule;
cfg = config.services.nbd;
iniFields = with types; attrsOf (oneOf [ bool int float str ]);
# The `[generic]` section must come before all the others in the
# config file. This means we can't just dump an attrset to INI
# because that sorts the sections by name. Instead, we serialize it
# on its own first.
genericSection = {
generic = (cfg.server.extraOptions // {
user = "root";
group = "root";
port = cfg.server.listenPort;
} // (optionalAttrs (cfg.server.listenAddress != null) {
listenaddr = cfg.server.listenAddress;
}));
};
exportSections =
lib.mapAttrs
(_: { path, allowAddresses, extraOptions }:
extraOptions // {
exportname = path;
} // (optionalAttrs (allowAddresses != null) {
authfile = pkgs.writeText "authfile" (lib.concatStringsSep "\n" allowAddresses);
}))
cfg.server.exports;
serverConfig =
pkgs.writeText "nbd-server-config" ''
${lib.generators.toINI {} genericSection}
${lib.generators.toINI {} exportSections}
'';
splitLists =
lib.partition
(path: lib.hasPrefix "/dev/" path)
(lib.mapAttrsToList (_: { path, ... }: path) cfg.server.exports);
allowedDevices = splitLists.right;
boundPaths = splitLists.wrong;
in
{
options = {
services.nbd = {
server = {
enable = lib.mkEnableOption "the Network Block Device (nbd) server";
listenPort = mkOption {
type = types.port;
default = 10809;
description = "Port to listen on. The port is NOT automatically opened in the firewall.";
};
extraOptions = mkOption {
type = iniFields;
default = {
allowlist = false;
};
description = ''
Extra options for the server. See
{manpage}`nbd-server(5)`.
'';
};
exports = mkOption {
description = "Files or block devices to make available over the network.";
default = { };
type = attrsOf
(submodule {
options = {
path = mkOption {
type = str;
description = "File or block device to export.";
example = "/dev/sdb1";
};
allowAddresses = mkOption {
type = nullOr (listOf str);
default = null;
example = [ "10.10.0.0/24" "127.0.0.1" ];
description = "IPs and subnets that are authorized to connect for this device. If not specified, the server will allow all connections.";
};
extraOptions = mkOption {
type = iniFields;
default = {
flush = true;
fua = true;
};
description = ''
Extra options for this export. See
{manpage}`nbd-server(5)`.
'';
};
};
});
};
listenAddress = mkOption {
type = nullOr str;
description = "Address to listen on. If not specified, the server will listen on all interfaces.";
default = null;
example = "10.10.0.1";
};
};
};
};
config = mkIf cfg.server.enable {
assertions = [
{
assertion = !(cfg.server.exports ? "generic");
message = "services.nbd.server exports must not be named 'generic'";
}
];
boot.kernelModules = [ "nbd" ];
systemd.services.nbd-server = {
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
before = [ "multi-user.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkgs.nbd}/bin/nbd-server -C ${serverConfig}";
Type = "forking";
DeviceAllow = map (path: "${path} rw") allowedDevices;
BindPaths = boundPaths;
CapabilityBoundingSet = "";
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = false;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "noaccess";
ProtectSystem = "strict";
RestrictAddressFamilies = "AF_INET AF_INET6";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
UMask = "0077";
};
};
};
}