Changes: nbd: Update nixos/modules/services/networking/nbd.nix Co-authored-by: pennae <82953136+pennae@users.noreply.github.com> nbd: Update nixos/modules/services/networking/nbd.nix Co-authored-by: pennae <82953136+pennae@users.noreply.github.com> nbd: Update nixos/tests/nbd.nix Co-authored-by: pennae <82953136+pennae@users.noreply.github.com> nbd: generalize options in nbd service nbd: harden service nbd: Update nixos/modules/services/networking/nbd.nix Co-authored-by: pennae <82953136+pennae@users.noreply.github.com> nbd: Update nixos/modules/services/networking/nbd.nix Co-authored-by: pennae <82953136+pennae@users.noreply.github.com> nbd: refactor code a bit and BindPaths automaticallymain
parent
7a622c17fd
commit
252f20aaa2
@ -0,0 +1,19 @@ |
||||
{ config, lib, pkgs, ... }: |
||||
|
||||
with lib; |
||||
|
||||
let |
||||
cfg = config.programs.nbd; |
||||
in |
||||
{ |
||||
options = { |
||||
programs.nbd = { |
||||
enable = mkEnableOption "Network Block Device (nbd) support"; |
||||
}; |
||||
}; |
||||
|
||||
config = mkIf cfg.enable { |
||||
environment.systemPackages = with pkgs; [ nbd ]; |
||||
boot.kernelModules = [ "nbd" ]; |
||||
}; |
||||
} |
@ -0,0 +1,146 @@ |
||||
{ config, lib, pkgs, ... }: |
||||
|
||||
with lib; |
||||
|
||||
let |
||||
cfg = config.services.nbd; |
||||
configFormat = pkgs.formats.ini { }; |
||||
iniFields = with types; attrsOf (oneOf [ bool int float str ]); |
||||
serverConfig = configFormat.generate "nbd-server-config" |
||||
({ |
||||
generic = |
||||
(cfg.server.extraOptions // { |
||||
user = "root"; |
||||
group = "root"; |
||||
port = cfg.server.listenPort; |
||||
} // (optionalAttrs (cfg.server.listenAddress != null) { |
||||
listenaddr = cfg.server.listenAddress; |
||||
})); |
||||
} |
||||
// (mapAttrs |
||||
(_: { path, allowAddresses, extraOptions }: |
||||
extraOptions // { |
||||
exportname = path; |
||||
} // (optionalAttrs (allowAddresses != null) { |
||||
authfile = pkgs.writeText "authfile" (concatStringsSep "\n" allowAddresses); |
||||
})) |
||||
cfg.server.exports) |
||||
); |
||||
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 { |
||||
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"; |
||||
}; |
||||
}; |
||||
}; |
||||
} |
@ -0,0 +1,87 @@ |
||||
import ./make-test-python.nix ({ pkgs, ... }: |
||||
let |
||||
listenPort = 30123; |
||||
testString = "It works!"; |
||||
mkCreateSmallFileService = { path, loop ? false }: { |
||||
script = '' |
||||
${pkgs.coreutils}/bin/dd if=/dev/zero of=${path} bs=1K count=100 |
||||
${pkgs.lib.optionalString loop |
||||
"${pkgs.util-linux}/bin/losetup --find ${path}"} |
||||
''; |
||||
serviceConfig = { |
||||
Type = "oneshot"; |
||||
}; |
||||
wantedBy = [ "multi-user.target" ]; |
||||
before = [ "nbd-server.service" ]; |
||||
}; |
||||
in |
||||
{ |
||||
name = "nbd"; |
||||
|
||||
nodes = { |
||||
server = { config, pkgs, ... }: { |
||||
# Create some small files of zeros to use as the ndb disks |
||||
## `vault-pub.disk` is accessible from any IP |
||||
systemd.services.create-pub-file = |
||||
mkCreateSmallFileService { path = "/vault-pub.disk"; }; |
||||
## `vault-priv.disk` is accessible only from localhost. |
||||
## It's also a loopback device to test exporting /dev/... |
||||
systemd.services.create-priv-file = |
||||
mkCreateSmallFileService { path = "/vault-priv.disk"; loop = true; }; |
||||
|
||||
# Needed only for nbd-client used in the tests. |
||||
environment.systemPackages = [ pkgs.nbd ]; |
||||
|
||||
# Open the nbd port in the firewall |
||||
networking.firewall.allowedTCPPorts = [ listenPort ]; |
||||
|
||||
# Run the nbd server and expose the small file created above |
||||
services.nbd.server = { |
||||
enable = true; |
||||
exports = { |
||||
vault-pub = { |
||||
path = "/vault-pub.disk"; |
||||
}; |
||||
vault-priv = { |
||||
path = "/dev/loop0"; |
||||
allowAddresses = [ "127.0.0.1" "::1" ]; |
||||
}; |
||||
}; |
||||
listenAddress = "0.0.0.0"; |
||||
listenPort = listenPort; |
||||
}; |
||||
}; |
||||
|
||||
client = { config, pkgs, ... }: { |
||||
programs.nbd.enable = true; |
||||
}; |
||||
}; |
||||
|
||||
testScript = '' |
||||
testString = "${testString}" |
||||
|
||||
start_all() |
||||
server.wait_for_open_port(${toString listenPort}) |
||||
|
||||
# Client: Connect to the server, write a small string to the nbd disk, and cleanly disconnect |
||||
client.succeed("nbd-client server ${toString listenPort} /dev/nbd0 -name vault-pub -persist") |
||||
client.succeed(f"echo '{testString}' | dd of=/dev/nbd0 conv=notrunc") |
||||
client.succeed("nbd-client -d /dev/nbd0") |
||||
|
||||
# Server: Check that the string written by the client is indeed in the file |
||||
foundString = server.succeed(f"dd status=none if=/vault-pub.disk count={len(testString)}")[:len(testString)] |
||||
if foundString != testString: |
||||
raise Exception(f"Read the wrong string from nbd disk. Expected: '{testString}'. Found: '{foundString}'") |
||||
|
||||
# Client: Fail to connect to the private disk |
||||
client.fail("nbd-client server ${toString listenPort} /dev/nbd0 -name vault-priv -persist") |
||||
|
||||
# Server: Successfully connect to the private disk |
||||
server.succeed("nbd-client localhost ${toString listenPort} /dev/nbd0 -name vault-priv -persist") |
||||
server.succeed(f"echo '{testString}' | dd of=/dev/nbd0 conv=notrunc") |
||||
foundString = server.succeed(f"dd status=none if=/dev/loop0 count={len(testString)}")[:len(testString)] |
||||
if foundString != testString: |
||||
raise Exception(f"Read the wrong string from nbd disk. Expected: '{testString}'. Found: '{foundString}'") |
||||
server.succeed("nbd-client -d /dev/nbd0") |
||||
''; |
||||
}) |
Loading…
Reference in new issue