2022-03-06 14:49:57 +01:00
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
let
|
|
|
|
cfg = config.services.nbd;
|
|
|
|
iniFields = with types; attrsOf (oneOf [ bool int float str ]);
|
2022-04-18 10:44:46 +02:00
|
|
|
# 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 =
|
|
|
|
mapAttrs
|
2022-03-06 14:49:57 +01:00
|
|
|
(_: { path, allowAddresses, extraOptions }:
|
|
|
|
extraOptions // {
|
|
|
|
exportname = path;
|
|
|
|
} // (optionalAttrs (allowAddresses != null) {
|
|
|
|
authfile = pkgs.writeText "authfile" (concatStringsSep "\n" allowAddresses);
|
|
|
|
}))
|
2022-04-18 10:44:46 +02:00
|
|
|
cfg.server.exports;
|
|
|
|
serverConfig =
|
|
|
|
pkgs.writeText "nbd-server-config" ''
|
|
|
|
${lib.generators.toINI {} genericSection}
|
|
|
|
${lib.generators.toINI {} exportSections}
|
|
|
|
'';
|
2022-03-06 14:49:57 +01:00
|
|
|
splitLists =
|
|
|
|
partition
|
|
|
|
(path: hasPrefix "/dev/" path)
|
|
|
|
(mapAttrsToList (_: { path, ... }: path) cfg.server.exports);
|
|
|
|
allowedDevices = splitLists.right;
|
|
|
|
boundPaths = splitLists.wrong;
|
|
|
|
in
|
|
|
|
{
|
|
|
|
options = {
|
|
|
|
services.nbd = {
|
|
|
|
server = {
|
|
|
|
enable = 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
|
|
|
|
<citerefentry><refentrytitle>nbd-server</refentrytitle>
|
|
|
|
<manvolnum>5</manvolnum></citerefentry>.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
exports = mkOption {
|
|
|
|
description = "Files or block devices to make available over the network.";
|
|
|
|
default = { };
|
|
|
|
type = with types; 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
|
|
|
|
<citerefentry><refentrytitle>nbd-server</refentrytitle>
|
|
|
|
<manvolnum>5</manvolnum></citerefentry>.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
listenAddress = mkOption {
|
|
|
|
type = with types; 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 {
|
2022-04-18 10:44:46 +02:00
|
|
|
assertions = [
|
|
|
|
{
|
|
|
|
assertion = !(cfg.server.exports ? "generic");
|
|
|
|
message = "services.nbd.server exports must not be named 'generic'";
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
2022-03-06 14:49:57 +01:00
|
|
|
boot.kernelModules = [ "nbd" ];
|
|
|
|
|
|
|
|
systemd.services.nbd-server = {
|
|
|
|
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";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|