lol
fork

Configure Feed

Select the types of activity you want to include in your feed.

nixos/tests/sftpgo: init

authored by

Yaya and committed by
Yureka
931a1b97 f63f7810

+388
+1
nixos/tests/all-tests.nix
··· 665 665 seafile = handleTest ./seafile.nix {}; 666 666 searx = handleTest ./searx.nix {}; 667 667 service-runner = handleTest ./service-runner.nix {}; 668 + sftpgo = runTest ./sftpgo.nix; 668 669 sfxr-qt = handleTest ./sfxr-qt.nix {}; 669 670 sgtpuzzles = handleTest ./sgtpuzzles.nix {}; 670 671 shadow = handleTest ./shadow.nix {};
+384
nixos/tests/sftpgo.nix
··· 1 + # SFTPGo NixOS test 2 + # 3 + # This NixOS test sets up a basic test scenario for the SFTPGo module 4 + # and covers the following scenarios: 5 + # - uploading a file via sftp 6 + # - downloading the file over sftp 7 + # - assert that the ACLs are respected 8 + # - share a file between alice and bob (using sftp) 9 + # - assert that eve cannot acceess the shared folder between alice and bob. 10 + # 11 + # Additional test coverage for the remaining protocols (i.e. ftp, http and webdav) 12 + # would be a nice to have for the future. 13 + { pkgs, lib, ... }: 14 + 15 + with lib; 16 + 17 + let 18 + inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey; 19 + 20 + # Returns an attributeset of users who are not system users. 21 + normalUsers = config: 22 + filterAttrs (name: user: user.isNormalUser) config.users.users; 23 + 24 + # Returns true if a user is a member of the given group 25 + isMemberOf = 26 + config: 27 + # str 28 + groupName: 29 + # users.users attrset 30 + user: 31 + any (x: x == user.name) config.users.groups.${groupName}.members; 32 + 33 + # Generates a valid SFTPGo user configuration for a given user 34 + # Will be converted to JSON and loaded on application startup. 35 + generateUserAttrSet = 36 + config: 37 + # attrset returned by config.users.users.<username> 38 + user: { 39 + # 0: user is disabled, login is not allowed 40 + # 1: user is enabled 41 + status = 1; 42 + 43 + username = user.name; 44 + password = ""; # disables password authentication 45 + public_keys = user.openssh.authorizedKeys.keys; 46 + email = "${user.name}@example.com"; 47 + 48 + # User home directory on the local filesystem 49 + home_dir = "${config.services.sftpgo.dataDir}/users/${user.name}"; 50 + 51 + # Defines a mapping between virtual SFTPGo paths and filesystem paths outside the user home directory. 52 + # 53 + # Supported for local filesystem only. If one or more of the specified folders are not 54 + # inside the dataprovider they will be automatically created. 55 + # You have to create the folder on the filesystem yourself 56 + virtual_folders = 57 + optional (isMemberOf config sharedFolderName user) { 58 + name = sharedFolderName; 59 + mapped_path = "${config.services.sftpgo.dataDir}/${sharedFolderName}"; 60 + virtual_path = "/${sharedFolderName}"; 61 + }; 62 + 63 + # Defines the ACL on the virtual filesystem 64 + permissions = 65 + recursiveUpdate { 66 + "/" = [ "list" ]; # read-only top level directory 67 + "/private" = [ "*" ]; # private subdirectory, not shared with others 68 + } (optionalAttrs (isMemberOf config "shared" user) { 69 + "/shared" = [ "*" ]; 70 + }); 71 + 72 + filters = { 73 + allowed_ip = []; 74 + denied_ip = []; 75 + web_client = [ 76 + "password-change-disabled" 77 + "password-reset-disabled" 78 + "api-key-auth-change-disabled" 79 + ]; 80 + }; 81 + 82 + upload_bandwidth = 0; # unlimited 83 + download_bandwidth = 0; # unlimited 84 + expiration_date = 0; # means no expiration 85 + max_sessions = 0; 86 + quota_size = 0; 87 + quota_files = 0; 88 + }; 89 + 90 + # Generates a json file containing a static configuration 91 + # of users and folders to import to SFTPGo. 92 + loadDataJson = config: pkgs.writeText "users-and-folders.json" (builtins.toJSON { 93 + users = 94 + mapAttrsToList (name: user: generateUserAttrSet config user) (normalUsers config); 95 + 96 + folders = [ 97 + { 98 + name = sharedFolderName; 99 + description = "shared folder"; 100 + 101 + # 0: local filesystem 102 + # 1: AWS S3 compatible 103 + # 2: Google Cloud Storage 104 + filesystem.provider = 0; 105 + 106 + # Mapped path on the local filesystem 107 + mapped_path = "${config.services.sftpgo.dataDir}/${sharedFolderName}"; 108 + 109 + # All users in the matching group gain access 110 + users = config.users.groups.${sharedFolderName}.members; 111 + } 112 + ]; 113 + }); 114 + 115 + # Generated Host Key for connecting to SFTPGo's sftp subsystem. 116 + snakeOilHostKey = pkgs.writeText "sftpgo_ed25519_host_key" '' 117 + -----BEGIN OPENSSH PRIVATE KEY----- 118 + b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 119 + QyNTUxOQAAACBOtQu6U135yxtrvUqPoozUymkjoNNPVK6rqjS936RLtQAAAJAXOMoSFzjK 120 + EgAAAAtzc2gtZWQyNTUxOQAAACBOtQu6U135yxtrvUqPoozUymkjoNNPVK6rqjS936RLtQ 121 + AAAEAoRLEV1VD80mg314ObySpfrCcUqtWoOSS3EtMPPhx08U61C7pTXfnLG2u9So+ijNTK 122 + aSOg009UrquqNL3fpEu1AAAADHNmdHBnb0BuaXhvcwE= 123 + -----END OPENSSH PRIVATE KEY----- 124 + ''; 125 + 126 + adminUsername = "admin"; 127 + adminPassword = "secretadminpassword"; 128 + aliceUsername = "alice"; 129 + alicePassword = "secretalicepassword"; 130 + bobUsername = "bob"; 131 + bobPassword = "secretbobpassword"; 132 + eveUsername = "eve"; 133 + evePassword = "secretevepassword"; 134 + sharedFolderName = "shared"; 135 + 136 + # A file for testing uploading via SFTP 137 + testFile = pkgs.writeText "test.txt" "hello world"; 138 + sharedFile = pkgs.writeText "shared.txt" "shared content"; 139 + 140 + # Define the for exposing SFTP 141 + sftpPort = 2022; 142 + 143 + # Define the for exposing HTTP 144 + httpPort = 8080; 145 + in 146 + { 147 + name = "sftpgo"; 148 + 149 + meta.maintainers = with maintainers; [ yayayayaka ]; 150 + 151 + nodes = { 152 + server = { nodes, ... }: { 153 + networking.firewall.allowedTCPPorts = [ sftpPort httpPort ]; 154 + 155 + # nodes.server.configure postgresql database 156 + services.postgresql = { 157 + enable = true; 158 + ensureDatabases = [ "sftpgo" ]; 159 + ensureUsers = [{ 160 + name = "sftpgo"; 161 + ensurePermissions."DATABASE sftpgo" = "ALL PRIVILEGES"; 162 + }]; 163 + }; 164 + 165 + services.sftpgo = { 166 + enable = true; 167 + 168 + loadDataFile = (loadDataJson nodes.server); 169 + 170 + settings = { 171 + data_provider = { 172 + driver = "postgresql"; 173 + name = "sftpgo"; 174 + username = "sftpgo"; 175 + host = "/run/postgresql"; 176 + port = 5432; 177 + 178 + # Enables the possibility to create an initial admin user on first startup. 179 + create_default_admin = true; 180 + }; 181 + 182 + httpd.bindings = [ 183 + { 184 + address = ""; # listen on all interfaces 185 + port = httpPort; 186 + enable_https = false; 187 + 188 + enable_web_client = true; 189 + enable_web_admin = true; 190 + } 191 + ]; 192 + 193 + # Enable sftpd 194 + sftpd = { 195 + bindings = [{ 196 + address = ""; # listen on all interfaces 197 + port = sftpPort; 198 + }]; 199 + host_keys = [ snakeOilHostKey ]; 200 + password_authentication = false; 201 + keyboard_interactive_authentication = false; 202 + }; 203 + }; 204 + }; 205 + 206 + systemd.services.sftpgo = { 207 + after = [ "postgresql.service"]; 208 + environment = { 209 + # Update existing users 210 + SFTPGO_LOADDATA_MODE = "0"; 211 + SFTPGO_DEFAULT_ADMIN_USERNAME = adminUsername; 212 + 213 + # This will end up in cleartext in the systemd service. 214 + # Don't use this approach in production! 215 + SFTPGO_DEFAULT_ADMIN_PASSWORD = adminPassword; 216 + }; 217 + }; 218 + 219 + # Sets up the folder hierarchy on the local filesystem 220 + systemd.tmpfiles.rules = 221 + let 222 + sftpgoUser = nodes.server.services.sftpgo.user; 223 + sftpgoGroup = nodes.server.services.sftpgo.group; 224 + statePath = nodes.server.services.sftpgo.dataDir; 225 + in [ 226 + # Create state directory 227 + "d ${statePath} 0750 ${sftpgoUser} ${sftpgoGroup} -" 228 + "d ${statePath}/users 0750 ${sftpgoUser} ${sftpgoGroup} -" 229 + 230 + # Created shared folder directories 231 + "d ${statePath}/${sharedFolderName} 2770 ${sftpgoUser} ${sharedFolderName} -" 232 + ] 233 + ++ mapAttrsToList (name: user: 234 + # Create private user directories 235 + '' 236 + d ${statePath}/users/${user.name} 0700 ${sftpgoUser} ${sftpgoGroup} - 237 + d ${statePath}/users/${user.name}/private 0700 ${sftpgoUser} ${sftpgoGroup} - 238 + '' 239 + ) (normalUsers nodes.server); 240 + 241 + users.users = 242 + let 243 + commonAttrs = { 244 + isNormalUser = true; 245 + openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; 246 + }; 247 + in { 248 + # SFTPGo admin user 249 + admin = commonAttrs // { 250 + password = adminPassword; 251 + }; 252 + 253 + # Alice and bob share folders with each other 254 + alice = commonAttrs // { 255 + password = alicePassword; 256 + extraGroups = [ sharedFolderName ]; 257 + }; 258 + 259 + bob = commonAttrs // { 260 + password = bobPassword; 261 + extraGroups = [ sharedFolderName ]; 262 + }; 263 + 264 + # Eve has no shared folders 265 + eve = commonAttrs // { 266 + password = evePassword; 267 + }; 268 + }; 269 + 270 + users.groups.${sharedFolderName} = {}; 271 + 272 + specialisation = { 273 + # A specialisation for asserting that SFTPGo can bind to privileged ports. 274 + privilegedPorts.configuration = { ... }: { 275 + networking.firewall.allowedTCPPorts = [ 22 80 ]; 276 + services.sftpgo = { 277 + settings = { 278 + sftpd.bindings = mkForce [{ 279 + address = ""; 280 + port = 22; 281 + }]; 282 + 283 + httpd.bindings = mkForce [{ 284 + address = ""; 285 + port = 80; 286 + }]; 287 + }; 288 + }; 289 + }; 290 + }; 291 + }; 292 + 293 + client = { nodes, ... }: { 294 + # Add the SFTPGo host key to the global known_hosts file 295 + programs.ssh.knownHosts = 296 + let 297 + commonAttrs = { 298 + publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE61C7pTXfnLG2u9So+ijNTKaSOg009UrquqNL3fpEu1"; 299 + }; 300 + in { 301 + "server" = commonAttrs; 302 + "[server]:2022" = commonAttrs; 303 + }; 304 + }; 305 + }; 306 + 307 + testScript = { nodes, ... }: let 308 + # A function to generate test cases for wheter 309 + # a specified username is expected to access the shared folder. 310 + accessSharedFoldersSubtest = 311 + { # The username to run as 312 + username 313 + # Whether the tests are expected to succeed or not 314 + , shouldSucceed ? true 315 + }: '' 316 + with subtest("Test whether ${username} can access shared folders"): 317 + client.${if shouldSucceed then "succeed" else "fail"}("sftp -P ${toString sftpPort} -b ${ 318 + pkgs.writeText "${username}-ls-${sharedFolderName}" '' 319 + ls ${sharedFolderName} 320 + '' 321 + } ${username}@server") 322 + ''; 323 + statePath = nodes.server.services.sftpgo.dataDir; 324 + in '' 325 + start_all() 326 + 327 + client.wait_for_unit("default.target") 328 + server.wait_for_unit("sftpgo.service") 329 + 330 + with subtest("web client"): 331 + client.wait_until_succeeds("curl -sSf http://server:${toString httpPort}/web/client/login") 332 + 333 + # Ensure sftpgo found the static folder 334 + client.wait_until_succeeds("curl -o /dev/null -sSf http://server:${toString httpPort}/static/favicon.ico") 335 + 336 + with subtest("Setup SSH keys"): 337 + client.succeed("mkdir -m 700 /root/.ssh") 338 + client.succeed("cat ${snakeOilPrivateKey} > /root/.ssh/id_ecdsa") 339 + client.succeed("chmod 600 /root/.ssh/id_ecdsa") 340 + 341 + with subtest("Copy a file over sftp"): 342 + client.wait_until_succeeds("scp -P ${toString sftpPort} ${toString testFile} alice@server:/private/${testFile.name}") 343 + server.succeed("test -s ${statePath}/users/alice/private/${testFile.name}") 344 + 345 + # The configured ACL should prevent uploading files to the root directory 346 + client.fail("scp -P ${toString sftpPort} ${toString testFile} alice@server:/") 347 + 348 + with subtest("Attempting an interactive SSH sessions must fail"): 349 + client.fail("ssh -p ${toString sftpPort} alice@server") 350 + 351 + ${accessSharedFoldersSubtest { 352 + username = "alice"; 353 + shouldSucceed = true; 354 + }} 355 + 356 + ${accessSharedFoldersSubtest { 357 + username = "bob"; 358 + shouldSucceed = true; 359 + }} 360 + 361 + ${accessSharedFoldersSubtest { 362 + username = "eve"; 363 + shouldSucceed = false; 364 + }} 365 + 366 + with subtest("Test sharing files"): 367 + # Alice uploads a file to shared folder 368 + client.succeed("scp -P ${toString sftpPort} ${toString sharedFile} alice@server:/${sharedFolderName}/${sharedFile.name}") 369 + server.succeed("test -s ${statePath}/${sharedFolderName}/${sharedFile.name}") 370 + 371 + # Bob downloads the file from shared folder 372 + client.succeed("scp -P ${toString sftpPort} bob@server:/shared/${sharedFile.name} ${sharedFile.name}") 373 + client.succeed("test -s ${sharedFile.name}") 374 + 375 + # Eve should not get the file from shared folder 376 + client.fail("scp -P ${toString sftpPort} eve@server:/shared/${sharedFile.name}") 377 + 378 + server.succeed("/run/current-system/specialisation/privilegedPorts/bin/switch-to-configuration test") 379 + 380 + client.wait_until_succeeds("sftp -P 22 -b ${pkgs.writeText "get-hello-world.txt" '' 381 + get /private/${testFile.name} 382 + ''} alice@server") 383 + ''; 384 + }
+3
pkgs/servers/sftpgo/default.nix
··· 2 2 , buildGoModule 3 3 , fetchFromGitHub 4 4 , installShellFiles 5 + , nixosTests 5 6 }: 6 7 7 8 buildGoModule rec { ··· 43 44 mkdir -p "$shareDirectory" 44 45 cp -r ./{openapi,static,templates} "$shareDirectory" 45 46 ''; 47 + 48 + passthru.tests = nixosTests.sftpgo; 46 49 47 50 meta = with lib; { 48 51 homepage = "https://github.com/drakkan/sftpgo";