nixos/mosquitto: rewrite the test

expand the test to check all four forms of passwords, tls certificates (both
server and client), and that acl files are formatted properly.

authored by

pennae and committed by
tomberek
c47fcb70 56d0b5cd

+140 -42
+140 -42
nixos/tests/mosquitto.nix
··· 2 2 3 3 let 4 4 port = 1888; 5 - username = "mqtt"; 5 + tlsPort = 1889; 6 6 password = "VERY_secret"; 7 + hashedPassword = "$7$101$/WJc4Mp+I+uYE9sR$o7z9rD1EYXHPwEP5GqQj6A7k4W1yVbePlb8TqNcuOLV9WNCiDgwHOB0JHC1WCtdkssqTBduBNUnUGd6kmZvDSw=="; 7 8 topic = "test/foo"; 9 + 10 + snakeOil = pkgs.runCommand "snakeoil-certs" { 11 + buildInputs = [ pkgs.gnutls.bin ]; 12 + caTemplate = pkgs.writeText "snakeoil-ca.template" '' 13 + cn = server 14 + expiration_days = -1 15 + cert_signing_key 16 + ca 17 + ''; 18 + certTemplate = pkgs.writeText "snakeoil-cert.template" '' 19 + cn = server 20 + expiration_days = -1 21 + tls_www_server 22 + encryption_key 23 + signing_key 24 + ''; 25 + userCertTemplate = pkgs.writeText "snakeoil-user-cert.template" '' 26 + organization = snakeoil 27 + cn = client1 28 + expiration_days = -1 29 + tls_www_client 30 + encryption_key 31 + signing_key 32 + ''; 33 + } '' 34 + mkdir "$out" 35 + 36 + certtool -p --bits 2048 --outfile "$out/ca.key" 37 + certtool -s --template "$caTemplate" --load-privkey "$out/ca.key" \ 38 + --outfile "$out/ca.crt" 39 + certtool -p --bits 2048 --outfile "$out/server.key" 40 + certtool -c --template "$certTemplate" \ 41 + --load-ca-privkey "$out/ca.key" \ 42 + --load-ca-certificate "$out/ca.crt" \ 43 + --load-privkey "$out/server.key" \ 44 + --outfile "$out/server.crt" 45 + 46 + certtool -p --bits 2048 --outfile "$out/client1.key" 47 + certtool -c --template "$userCertTemplate" \ 48 + --load-privkey "$out/client1.key" \ 49 + --load-ca-privkey "$out/ca.key" \ 50 + --load-ca-certificate "$out/ca.crt" \ 51 + --outfile "$out/client1.crt" 52 + ''; 53 + 8 54 in { 9 55 name = "mosquitto"; 10 56 meta = with pkgs.lib; { 11 - maintainers = with maintainers; [ peterhoeg ]; 57 + maintainers = with maintainers; [ pennae peterhoeg ]; 12 58 }; 13 59 14 60 nodes = let ··· 17 63 }; 18 64 in { 19 65 server = { pkgs, ... }: { 20 - networking.firewall.allowedTCPPorts = [ port ]; 66 + networking.firewall.allowedTCPPorts = [ port tlsPort ]; 21 67 services.mosquitto = { 22 68 enable = true; 69 + settings = { 70 + sys_interval = 1; 71 + }; 23 72 listeners = [ 24 73 { 25 74 inherit port; 26 - users.${username} = { 27 - inherit password; 28 - acl = [ 29 - "readwrite ${topic}" 30 - ]; 75 + users = { 76 + password_store = { 77 + inherit password; 78 + }; 79 + password_file = { 80 + passwordFile = pkgs.writeText "mqtt-password" password; 81 + }; 82 + hashed_store = { 83 + inherit hashedPassword; 84 + }; 85 + hashed_file = { 86 + hashedPasswordFile = pkgs.writeText "mqtt-hashed-password" hashedPassword; 87 + }; 88 + 89 + reader = { 90 + inherit password; 91 + acl = [ 92 + "read ${topic}" 93 + "read $SYS/#" # so we always have something to read 94 + ]; 95 + }; 96 + writer = { 97 + inherit password; 98 + acl = [ "write ${topic}" ]; 99 + }; 100 + }; 101 + } 102 + { 103 + port = tlsPort; 104 + users.client1 = { 105 + acl = [ "read $SYS/#" ]; 106 + }; 107 + settings = { 108 + cafile = "${snakeOil}/ca.crt"; 109 + certfile = "${snakeOil}/server.crt"; 110 + keyfile = "${snakeOil}/server.key"; 111 + require_certificate = true; 112 + use_identity_as_username = true; 31 113 }; 32 114 } 33 115 ]; 34 116 }; 35 - 36 - # disable private /tmp for this test 37 - systemd.services.mosquitto.serviceConfig.PrivateTmp = lib.mkForce false; 38 117 }; 39 118 40 119 client1 = client; 41 120 client2 = client; 42 121 }; 43 122 44 - testScript = let 45 - file = "/tmp/msg"; 46 - in '' 47 - def mosquitto_cmd(binary): 123 + testScript = '' 124 + def mosquitto_cmd(binary, user, topic, port): 48 125 return ( 49 - "${pkgs.mosquitto}/bin/mosquitto_{} " 126 + "mosquitto_{} " 50 127 "-V mqttv311 " 51 128 "-h server " 52 - "-p ${toString port} " 53 - "-u ${username} " 129 + "-p {} " 130 + "-u {} " 54 131 "-P '${password}' " 55 - "-t ${topic}" 56 - ).format(binary) 132 + "-t '{}'" 133 + ).format(binary, port, user, topic) 57 134 58 135 59 - def publish(args): 60 - return "{} {}".format(mosquitto_cmd("pub"), args) 136 + def publish(args, user, topic="${topic}", port=${toString port}): 137 + return "{} {}".format(mosquitto_cmd("pub", user, topic, port), args) 61 138 62 139 63 - def subscribe(args): 64 - return "({} -C 1 {} | tee ${file} &)".format(mosquitto_cmd("sub"), args) 140 + def subscribe(args, user, topic="${topic}", port=${toString port}): 141 + return "{} -C 1 {}".format(mosquitto_cmd("sub", user, topic, port), args) 142 + 143 + def parallel(*fns): 144 + from threading import Thread 145 + threads = [ Thread(target=fn) for fn in fns ] 146 + for t in threads: t.start() 147 + for t in threads: t.join() 65 148 66 149 67 150 start_all() 68 151 server.wait_for_unit("mosquitto.service") 69 152 70 - for machine in server, client1, client2: 71 - machine.fail("test -f ${file}") 153 + def check_passwords(): 154 + client1.succeed(publish("-m test", "password_store")) 155 + client1.succeed(publish("-m test", "password_file")) 156 + client1.succeed(publish("-m test", "hashed_store")) 157 + client1.succeed(publish("-m test", "hashed_file")) 158 + 159 + check_passwords() 72 160 73 - # QoS = 0, so only one subscribers should get it 74 - server.execute(subscribe("-q 0")) 161 + def check_acl(): 162 + client1.succeed(subscribe("", "reader", topic="$SYS/#")) 163 + client1.fail(subscribe("-W 5", "writer", topic="$SYS/#")) 75 164 76 - # we need to give the subscribers some time to connect 77 - client2.execute("sleep 5") 78 - client2.succeed(publish("-m FOO -q 0")) 165 + parallel( 166 + lambda: client1.succeed(subscribe("-i 3688cdd7-aa07-42a4-be22-cb9352917e40", "reader")), 167 + lambda: [ 168 + server.wait_for_console_text("3688cdd7-aa07-42a4-be22-cb9352917e40"), 169 + client2.succeed(publish("-m test", "writer")) 170 + ]) 79 171 80 - server.wait_until_succeeds("grep -q FOO ${file}") 81 - server.execute("rm ${file}") 172 + parallel( 173 + lambda: client1.fail(subscribe("-W 5 -i 24ff16a2-ae33-4a51-9098-1b417153c712", "reader")), 174 + lambda: [ 175 + server.wait_for_console_text("24ff16a2-ae33-4a51-9098-1b417153c712"), 176 + client2.succeed(publish("-m test", "reader")) 177 + ]) 82 178 83 - # QoS = 1, so both subscribers should get it 84 - server.execute(subscribe("-q 1")) 85 - client1.execute(subscribe("-q 1")) 179 + check_acl() 86 180 87 - # we need to give the subscribers some time to connect 88 - client2.execute("sleep 5") 89 - client2.succeed(publish("-m BAR -q 1")) 181 + def check_tls(): 182 + client1.succeed( 183 + subscribe( 184 + "--cafile ${snakeOil}/ca.crt " 185 + "--cert ${snakeOil}/client1.crt " 186 + "--key ${snakeOil}/client1.key", 187 + topic="$SYS/#", 188 + port=${toString tlsPort}, 189 + user="no_such_user")) 90 190 91 - for machine in server, client1: 92 - machine.wait_until_succeeds("grep -q BAR ${file}") 93 - machine.execute("rm ${file}") 191 + check_tls() 94 192 ''; 95 193 })