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