python310Packages.pydeconz: 104 -> 105
[NixPkgs.git] / nixos / tests / mosquitto.nix
blob70eecc89278b3df12b5bb4a32ca8bea8bae5cf81
1 import ./make-test-python.nix ({ pkgs, lib, ... }:
3 let
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";
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"
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"
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   '';
56 in {
57   name = "mosquitto";
58   meta = with pkgs.lib; {
59     maintainers = with maintainers; [ pennae peterhoeg ];
60   };
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               };
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     };
137     client1 = client;
138     client2 = client;
139   };
141   testScript = ''
142     import json
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)
156     def publish(args, user, topic="${topic}", port=${toString port}):
157         return "{} {}".format(mosquitto_cmd("pub", user, topic, port), args)
159     def subscribe(args, user, topic="${topic}", port=${toString port}):
160         return "{} -W 5 -C 1 {}".format(mosquitto_cmd("sub", user, topic, port), args)
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()
168     def wait_uuid(uuid):
169         server.wait_for_console_text(uuid)
170         return None
173     start_all()
174     server.wait_for_unit("mosquitto.service")
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'])
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
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"))
203     with subtest("check acl"):
204         client1.succeed(subscribe("", "reader", topic="$SYS/#"))
205         client1.fail(subscribe("", "writer", topic="$SYS/#"))
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             ])
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             ])
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"))
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   '';