at 23.11-pre 7.7 kB view raw
1import ./make-test-python.nix ({ pkgs, lib, ... }: 2 3let 4 port = 1888; 5 tlsPort = 1889; 6 anonPort = 1890; 7 bindTestPort = 18910; 8 password = "VERY_secret"; 9 hashedPassword = "$7$101$/WJc4Mp+I+uYE9sR$o7z9rD1EYXHPwEP5GqQj6A7k4W1yVbePlb8TqNcuOLV9WNCiDgwHOB0JHC1WCtdkssqTBduBNUnUGd6kmZvDSw=="; 10 topic = "test/foo"; 11 12 snakeOil = pkgs.runCommand "snakeoil-certs" { 13 buildInputs = [ pkgs.gnutls.bin ]; 14 caTemplate = pkgs.writeText "snakeoil-ca.template" '' 15 cn = server 16 expiration_days = -1 17 cert_signing_key 18 ca 19 ''; 20 certTemplate = pkgs.writeText "snakeoil-cert.template" '' 21 cn = server 22 expiration_days = -1 23 tls_www_server 24 encryption_key 25 signing_key 26 ''; 27 userCertTemplate = pkgs.writeText "snakeoil-user-cert.template" '' 28 organization = snakeoil 29 cn = client1 30 expiration_days = -1 31 tls_www_client 32 encryption_key 33 signing_key 34 ''; 35 } '' 36 mkdir "$out" 37 38 certtool -p --bits 2048 --outfile "$out/ca.key" 39 certtool -s --template "$caTemplate" --load-privkey "$out/ca.key" \ 40 --outfile "$out/ca.crt" 41 certtool -p --bits 2048 --outfile "$out/server.key" 42 certtool -c --template "$certTemplate" \ 43 --load-ca-privkey "$out/ca.key" \ 44 --load-ca-certificate "$out/ca.crt" \ 45 --load-privkey "$out/server.key" \ 46 --outfile "$out/server.crt" 47 48 certtool -p --bits 2048 --outfile "$out/client1.key" 49 certtool -c --template "$userCertTemplate" \ 50 --load-privkey "$out/client1.key" \ 51 --load-ca-privkey "$out/ca.key" \ 52 --load-ca-certificate "$out/ca.crt" \ 53 --outfile "$out/client1.crt" 54 ''; 55 56in { 57 name = "mosquitto"; 58 meta = with pkgs.lib; { 59 maintainers = with maintainers; [ pennae peterhoeg ]; 60 }; 61 62 nodes = let 63 client = { pkgs, ... }: { 64 environment.systemPackages = with pkgs; [ mosquitto ]; 65 }; 66 in { 67 server = { pkgs, ... }: { 68 networking.firewall.allowedTCPPorts = [ port tlsPort anonPort ]; 69 networking.useNetworkd = true; 70 services.mosquitto = { 71 enable = true; 72 settings = { 73 sys_interval = 1; 74 }; 75 listeners = [ 76 { 77 inherit port; 78 users = { 79 password_store = { 80 inherit password; 81 }; 82 password_file = { 83 passwordFile = pkgs.writeText "mqtt-password" password; 84 }; 85 hashed_store = { 86 inherit hashedPassword; 87 }; 88 hashed_file = { 89 hashedPasswordFile = pkgs.writeText "mqtt-hashed-password" hashedPassword; 90 }; 91 92 reader = { 93 inherit password; 94 acl = [ 95 "read ${topic}" 96 "read $SYS/#" # so we always have something to read 97 ]; 98 }; 99 writer = { 100 inherit password; 101 acl = [ "write ${topic}" ]; 102 }; 103 }; 104 } 105 { 106 port = tlsPort; 107 users.client1 = { 108 acl = [ "read $SYS/#" ]; 109 }; 110 settings = { 111 cafile = "${snakeOil}/ca.crt"; 112 certfile = "${snakeOil}/server.crt"; 113 keyfile = "${snakeOil}/server.key"; 114 require_certificate = true; 115 use_identity_as_username = true; 116 }; 117 } 118 { 119 port = anonPort; 120 omitPasswordAuth = true; 121 settings.allow_anonymous = true; 122 acl = [ "pattern read #" ]; 123 users = { 124 anonWriter = { 125 password = "<ignored>" + password; 126 acl = [ "write ${topic}" ]; 127 }; 128 }; 129 } 130 { 131 settings.bind_interface = "eth0"; 132 port = bindTestPort; 133 } 134 ]; 135 }; 136 }; 137 138 client1 = client; 139 client2 = client; 140 }; 141 142 testScript = '' 143 import json 144 145 def mosquitto_cmd(binary, user, topic, port): 146 return ( 147 "mosquitto_{} " 148 "-V mqttv311 " 149 "-h server " 150 "-p {} " 151 "-u {} " 152 "-P '${password}' " 153 "-t '{}'" 154 ).format(binary, port, user, topic) 155 156 157 def publish(args, user, topic="${topic}", port=${toString port}): 158 return "{} {}".format(mosquitto_cmd("pub", user, topic, port), args) 159 160 def subscribe(args, user, topic="${topic}", port=${toString port}): 161 return "{} -W 5 -C 1 {}".format(mosquitto_cmd("sub", user, topic, port), args) 162 163 def parallel(*fns): 164 from threading import Thread 165 threads = [ Thread(target=fn) for fn in fns ] 166 for t in threads: t.start() 167 for t in threads: t.join() 168 169 def wait_uuid(uuid): 170 server.wait_for_console_text(uuid) 171 return None 172 173 174 start_all() 175 server.wait_for_unit("mosquitto.service") 176 177 with subtest("bind_interface"): 178 addrs = dict() 179 for iface in json.loads(server.succeed("ip -json address show")): 180 for addr in iface['addr_info']: 181 # don't want to deal with multihoming here 182 assert addr['local'] not in addrs 183 addrs[addr['local']] = (iface['ifname'], addr['family']) 184 185 # mosquitto grabs *one* random address per type for bind_interface 186 (has4, has6) = (False, False) 187 for line in server.succeed("ss -HlptnO sport = ${toString bindTestPort}").splitlines(): 188 items = line.split() 189 if "mosquitto" not in items[5]: continue 190 listener = items[3].rsplit(':', maxsplit=1)[0].strip('[]') 191 assert listener in addrs 192 assert addrs[listener][0] == "eth0" 193 has4 |= addrs[listener][1] == 'inet' 194 has6 |= addrs[listener][1] == 'inet6' 195 assert has4 196 assert has6 197 198 with subtest("check passwords"): 199 client1.succeed(publish("-m test", "password_store")) 200 client1.succeed(publish("-m test", "password_file")) 201 client1.succeed(publish("-m test", "hashed_store")) 202 client1.succeed(publish("-m test", "hashed_file")) 203 204 with subtest("check acl"): 205 client1.succeed(subscribe("", "reader", topic="$SYS/#")) 206 client1.fail(subscribe("", "writer", topic="$SYS/#")) 207 208 parallel( 209 lambda: client1.succeed(subscribe("-i 3688cdd7-aa07-42a4-be22-cb9352917e40", "reader")), 210 lambda: [ 211 wait_uuid("3688cdd7-aa07-42a4-be22-cb9352917e40"), 212 client2.succeed(publish("-m test", "writer")) 213 ]) 214 215 parallel( 216 lambda: client1.fail(subscribe("-i 24ff16a2-ae33-4a51-9098-1b417153c712", "reader")), 217 lambda: [ 218 wait_uuid("24ff16a2-ae33-4a51-9098-1b417153c712"), 219 client2.succeed(publish("-m test", "reader")) 220 ]) 221 222 with subtest("check tls"): 223 client1.succeed( 224 subscribe( 225 "--cafile ${snakeOil}/ca.crt " 226 "--cert ${snakeOil}/client1.crt " 227 "--key ${snakeOil}/client1.key", 228 topic="$SYS/#", 229 port=${toString tlsPort}, 230 user="no_such_user")) 231 232 with subtest("check omitPasswordAuth"): 233 parallel( 234 lambda: client1.succeed(subscribe("-i fd56032c-d9cb-4813-a3b4-6be0e04c8fc3", 235 "anonReader", port=${toString anonPort})), 236 lambda: [ 237 wait_uuid("fd56032c-d9cb-4813-a3b4-6be0e04c8fc3"), 238 client2.succeed(publish("-m test", "anonWriter", port=${toString anonPort})) 239 ]) 240 ''; 241})