at 23.11-pre 4.9 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.nbd; 7 iniFields = with types; attrsOf (oneOf [ bool int float str ]); 8 # The `[generic]` section must come before all the others in the 9 # config file. This means we can't just dump an attrset to INI 10 # because that sorts the sections by name. Instead, we serialize it 11 # on its own first. 12 genericSection = { 13 generic = (cfg.server.extraOptions // { 14 user = "root"; 15 group = "root"; 16 port = cfg.server.listenPort; 17 } // (optionalAttrs (cfg.server.listenAddress != null) { 18 listenaddr = cfg.server.listenAddress; 19 })); 20 }; 21 exportSections = 22 mapAttrs 23 (_: { path, allowAddresses, extraOptions }: 24 extraOptions // { 25 exportname = path; 26 } // (optionalAttrs (allowAddresses != null) { 27 authfile = pkgs.writeText "authfile" (concatStringsSep "\n" allowAddresses); 28 })) 29 cfg.server.exports; 30 serverConfig = 31 pkgs.writeText "nbd-server-config" '' 32 ${lib.generators.toINI {} genericSection} 33 ${lib.generators.toINI {} exportSections} 34 ''; 35 splitLists = 36 partition 37 (path: hasPrefix "/dev/" path) 38 (mapAttrsToList (_: { path, ... }: path) cfg.server.exports); 39 allowedDevices = splitLists.right; 40 boundPaths = splitLists.wrong; 41in 42{ 43 options = { 44 services.nbd = { 45 server = { 46 enable = mkEnableOption (lib.mdDoc "the Network Block Device (nbd) server"); 47 48 listenPort = mkOption { 49 type = types.port; 50 default = 10809; 51 description = lib.mdDoc "Port to listen on. The port is NOT automatically opened in the firewall."; 52 }; 53 54 extraOptions = mkOption { 55 type = iniFields; 56 default = { 57 allowlist = false; 58 }; 59 description = lib.mdDoc '' 60 Extra options for the server. See 61 {manpage}`nbd-server(5)`. 62 ''; 63 }; 64 65 exports = mkOption { 66 description = lib.mdDoc "Files or block devices to make available over the network."; 67 default = { }; 68 type = with types; attrsOf 69 (submodule { 70 options = { 71 path = mkOption { 72 type = str; 73 description = lib.mdDoc "File or block device to export."; 74 example = "/dev/sdb1"; 75 }; 76 77 allowAddresses = mkOption { 78 type = nullOr (listOf str); 79 default = null; 80 example = [ "10.10.0.0/24" "127.0.0.1" ]; 81 description = lib.mdDoc "IPs and subnets that are authorized to connect for this device. If not specified, the server will allow all connections."; 82 }; 83 84 extraOptions = mkOption { 85 type = iniFields; 86 default = { 87 flush = true; 88 fua = true; 89 }; 90 description = lib.mdDoc '' 91 Extra options for this export. See 92 {manpage}`nbd-server(5)`. 93 ''; 94 }; 95 }; 96 }); 97 }; 98 99 listenAddress = mkOption { 100 type = with types; nullOr str; 101 description = lib.mdDoc "Address to listen on. If not specified, the server will listen on all interfaces."; 102 default = null; 103 example = "10.10.0.1"; 104 }; 105 }; 106 }; 107 }; 108 109 config = mkIf cfg.server.enable { 110 assertions = [ 111 { 112 assertion = !(cfg.server.exports ? "generic"); 113 message = "services.nbd.server exports must not be named 'generic'"; 114 } 115 ]; 116 117 boot.kernelModules = [ "nbd" ]; 118 119 systemd.services.nbd-server = { 120 after = [ "network-online.target" ]; 121 before = [ "multi-user.target" ]; 122 wantedBy = [ "multi-user.target" ]; 123 serviceConfig = { 124 ExecStart = "${pkgs.nbd}/bin/nbd-server -C ${serverConfig}"; 125 Type = "forking"; 126 127 DeviceAllow = map (path: "${path} rw") allowedDevices; 128 BindPaths = boundPaths; 129 130 CapabilityBoundingSet = ""; 131 DevicePolicy = "closed"; 132 LockPersonality = true; 133 MemoryDenyWriteExecute = true; 134 NoNewPrivileges = true; 135 PrivateDevices = false; 136 PrivateMounts = true; 137 PrivateTmp = true; 138 PrivateUsers = true; 139 ProcSubset = "pid"; 140 ProtectClock = true; 141 ProtectControlGroups = true; 142 ProtectHome = true; 143 ProtectHostname = true; 144 ProtectKernelLogs = true; 145 ProtectKernelModules = true; 146 ProtectKernelTunables = true; 147 ProtectProc = "noaccess"; 148 ProtectSystem = "strict"; 149 RestrictAddressFamilies = "AF_INET AF_INET6"; 150 RestrictNamespaces = true; 151 RestrictRealtime = true; 152 RestrictSUIDSGID = true; 153 UMask = "0077"; 154 }; 155 }; 156 }; 157}