at 23.05-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 services.mosquitto = { 70 enable = true; 71 settings = { 72 sys_interval = 1; 73 }; 74 listeners = [ 75 { 76 inherit port; 77 users = { 78 password_store = { 79 inherit password; 80 }; 81 password_file = { 82 passwordFile = pkgs.writeText "mqtt-password" password; 83 }; 84 hashed_store = { 85 inherit hashedPassword; 86 }; 87 hashed_file = { 88 hashedPasswordFile = pkgs.writeText "mqtt-hashed-password" hashedPassword; 89 }; 90 91 reader = { 92 inherit password; 93 acl = [ 94 "read ${topic}" 95 "read $SYS/#" # so we always have something to read 96 ]; 97 }; 98 writer = { 99 inherit password; 100 acl = [ "write ${topic}" ]; 101 }; 102 }; 103 } 104 { 105 port = tlsPort; 106 users.client1 = { 107 acl = [ "read $SYS/#" ]; 108 }; 109 settings = { 110 cafile = "${snakeOil}/ca.crt"; 111 certfile = "${snakeOil}/server.crt"; 112 keyfile = "${snakeOil}/server.key"; 113 require_certificate = true; 114 use_identity_as_username = true; 115 }; 116 } 117 { 118 port = anonPort; 119 omitPasswordAuth = true; 120 settings.allow_anonymous = true; 121 acl = [ "pattern read #" ]; 122 users = { 123 anonWriter = { 124 password = "<ignored>" + password; 125 acl = [ "write ${topic}" ]; 126 }; 127 }; 128 } 129 { 130 settings.bind_interface = "eth0"; 131 port = bindTestPort; 132 } 133 ]; 134 }; 135 }; 136 137 client1 = client; 138 client2 = client; 139 }; 140 141 testScript = '' 142 import json 143 144 def mosquitto_cmd(binary, user, topic, port): 145 return ( 146 "mosquitto_{} " 147 "-V mqttv311 " 148 "-h server " 149 "-p {} " 150 "-u {} " 151 "-P '${password}' " 152 "-t '{}'" 153 ).format(binary, port, user, topic) 154 155 156 def publish(args, user, topic="${topic}", port=${toString port}): 157 return "{} {}".format(mosquitto_cmd("pub", user, topic, port), args) 158 159 def subscribe(args, user, topic="${topic}", port=${toString port}): 160 return "{} -W 5 -C 1 {}".format(mosquitto_cmd("sub", user, topic, port), args) 161 162 def parallel(*fns): 163 from threading import Thread 164 threads = [ Thread(target=fn) for fn in fns ] 165 for t in threads: t.start() 166 for t in threads: t.join() 167 168 def wait_uuid(uuid): 169 server.wait_for_console_text(uuid) 170 return None 171 172 173 start_all() 174 server.wait_for_unit("mosquitto.service") 175 176 with subtest("bind_interface"): 177 addrs = dict() 178 for iface in json.loads(server.succeed("ip -json address show")): 179 for addr in iface['addr_info']: 180 # don't want to deal with multihoming here 181 assert addr['local'] not in addrs 182 addrs[addr['local']] = (iface['ifname'], addr['family']) 183 184 # mosquitto grabs *one* random address per type for bind_interface 185 (has4, has6) = (False, False) 186 for line in server.succeed("ss -HlptnO sport = ${toString bindTestPort}").splitlines(): 187 items = line.split() 188 if "mosquitto" not in items[5]: continue 189 listener = items[3].rsplit(':', maxsplit=1)[0].strip('[]') 190 assert listener in addrs 191 assert addrs[listener][0] == "eth0" 192 has4 |= addrs[listener][1] == 'inet' 193 has6 |= addrs[listener][1] == 'inet6' 194 assert has4 195 assert has6 196 197 with subtest("check passwords"): 198 client1.succeed(publish("-m test", "password_store")) 199 client1.succeed(publish("-m test", "password_file")) 200 client1.succeed(publish("-m test", "hashed_store")) 201 client1.succeed(publish("-m test", "hashed_file")) 202 203 with subtest("check acl"): 204 client1.succeed(subscribe("", "reader", topic="$SYS/#")) 205 client1.fail(subscribe("", "writer", topic="$SYS/#")) 206 207 parallel( 208 lambda: client1.succeed(subscribe("-i 3688cdd7-aa07-42a4-be22-cb9352917e40", "reader")), 209 lambda: [ 210 wait_uuid("3688cdd7-aa07-42a4-be22-cb9352917e40"), 211 client2.succeed(publish("-m test", "writer")) 212 ]) 213 214 parallel( 215 lambda: client1.fail(subscribe("-i 24ff16a2-ae33-4a51-9098-1b417153c712", "reader")), 216 lambda: [ 217 wait_uuid("24ff16a2-ae33-4a51-9098-1b417153c712"), 218 client2.succeed(publish("-m test", "reader")) 219 ]) 220 221 with subtest("check tls"): 222 client1.succeed( 223 subscribe( 224 "--cafile ${snakeOil}/ca.crt " 225 "--cert ${snakeOil}/client1.crt " 226 "--key ${snakeOil}/client1.key", 227 topic="$SYS/#", 228 port=${toString tlsPort}, 229 user="no_such_user")) 230 231 with subtest("check omitPasswordAuth"): 232 parallel( 233 lambda: client1.succeed(subscribe("-i fd56032c-d9cb-4813-a3b4-6be0e04c8fc3", 234 "anonReader", port=${toString anonPort})), 235 lambda: [ 236 wait_uuid("fd56032c-d9cb-4813-a3b4-6be0e04c8fc3"), 237 client2.succeed(publish("-m test", "anonWriter", port=${toString anonPort})) 238 ]) 239 ''; 240})