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.
This commit is contained in:
pennae 2021-05-26 18:59:37 +02:00 committed by tomberek
parent 56d0b5cd6a
commit c47fcb70c6

View File

@ -2,13 +2,59 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
let
port = 1888;
username = "mqtt";
tlsPort = 1889;
password = "VERY_secret";
hashedPassword = "$7$101$/WJc4Mp+I+uYE9sR$o7z9rD1EYXHPwEP5GqQj6A7k4W1yVbePlb8TqNcuOLV9WNCiDgwHOB0JHC1WCtdkssqTBduBNUnUGd6kmZvDSw==";
topic = "test/foo";
snakeOil = pkgs.runCommand "snakeoil-certs" {
buildInputs = [ pkgs.gnutls.bin ];
caTemplate = pkgs.writeText "snakeoil-ca.template" ''
cn = server
expiration_days = -1
cert_signing_key
ca
'';
certTemplate = pkgs.writeText "snakeoil-cert.template" ''
cn = server
expiration_days = -1
tls_www_server
encryption_key
signing_key
'';
userCertTemplate = pkgs.writeText "snakeoil-user-cert.template" ''
organization = snakeoil
cn = client1
expiration_days = -1
tls_www_client
encryption_key
signing_key
'';
} ''
mkdir "$out"
certtool -p --bits 2048 --outfile "$out/ca.key"
certtool -s --template "$caTemplate" --load-privkey "$out/ca.key" \
--outfile "$out/ca.crt"
certtool -p --bits 2048 --outfile "$out/server.key"
certtool -c --template "$certTemplate" \
--load-ca-privkey "$out/ca.key" \
--load-ca-certificate "$out/ca.crt" \
--load-privkey "$out/server.key" \
--outfile "$out/server.crt"
certtool -p --bits 2048 --outfile "$out/client1.key"
certtool -c --template "$userCertTemplate" \
--load-privkey "$out/client1.key" \
--load-ca-privkey "$out/ca.key" \
--load-ca-certificate "$out/ca.crt" \
--outfile "$out/client1.crt"
'';
in {
name = "mosquitto";
meta = with pkgs.lib; {
maintainers = with maintainers; [ peterhoeg ];
maintainers = with maintainers; [ pennae peterhoeg ];
};
nodes = let
@ -17,79 +63,131 @@ in {
};
in {
server = { pkgs, ... }: {
networking.firewall.allowedTCPPorts = [ port ];
networking.firewall.allowedTCPPorts = [ port tlsPort ];
services.mosquitto = {
enable = true;
settings = {
sys_interval = 1;
};
listeners = [
{
inherit port;
users.${username} = {
inherit password;
acl = [
"readwrite ${topic}"
];
users = {
password_store = {
inherit password;
};
password_file = {
passwordFile = pkgs.writeText "mqtt-password" password;
};
hashed_store = {
inherit hashedPassword;
};
hashed_file = {
hashedPasswordFile = pkgs.writeText "mqtt-hashed-password" hashedPassword;
};
reader = {
inherit password;
acl = [
"read ${topic}"
"read $SYS/#" # so we always have something to read
];
};
writer = {
inherit password;
acl = [ "write ${topic}" ];
};
};
}
{
port = tlsPort;
users.client1 = {
acl = [ "read $SYS/#" ];
};
settings = {
cafile = "${snakeOil}/ca.crt";
certfile = "${snakeOil}/server.crt";
keyfile = "${snakeOil}/server.key";
require_certificate = true;
use_identity_as_username = true;
};
}
];
};
# disable private /tmp for this test
systemd.services.mosquitto.serviceConfig.PrivateTmp = lib.mkForce false;
};
client1 = client;
client2 = client;
};
testScript = let
file = "/tmp/msg";
in ''
def mosquitto_cmd(binary):
testScript = ''
def mosquitto_cmd(binary, user, topic, port):
return (
"${pkgs.mosquitto}/bin/mosquitto_{} "
"mosquitto_{} "
"-V mqttv311 "
"-h server "
"-p ${toString port} "
"-u ${username} "
"-p {} "
"-u {} "
"-P '${password}' "
"-t ${topic}"
).format(binary)
"-t '{}'"
).format(binary, port, user, topic)
def publish(args):
return "{} {}".format(mosquitto_cmd("pub"), args)
def publish(args, user, topic="${topic}", port=${toString port}):
return "{} {}".format(mosquitto_cmd("pub", user, topic, port), args)
def subscribe(args):
return "({} -C 1 {} | tee ${file} &)".format(mosquitto_cmd("sub"), args)
def subscribe(args, user, topic="${topic}", port=${toString port}):
return "{} -C 1 {}".format(mosquitto_cmd("sub", user, topic, port), args)
def parallel(*fns):
from threading import Thread
threads = [ Thread(target=fn) for fn in fns ]
for t in threads: t.start()
for t in threads: t.join()
start_all()
server.wait_for_unit("mosquitto.service")
for machine in server, client1, client2:
machine.fail("test -f ${file}")
def check_passwords():
client1.succeed(publish("-m test", "password_store"))
client1.succeed(publish("-m test", "password_file"))
client1.succeed(publish("-m test", "hashed_store"))
client1.succeed(publish("-m test", "hashed_file"))
# QoS = 0, so only one subscribers should get it
server.execute(subscribe("-q 0"))
check_passwords()
# we need to give the subscribers some time to connect
client2.execute("sleep 5")
client2.succeed(publish("-m FOO -q 0"))
def check_acl():
client1.succeed(subscribe("", "reader", topic="$SYS/#"))
client1.fail(subscribe("-W 5", "writer", topic="$SYS/#"))
server.wait_until_succeeds("grep -q FOO ${file}")
server.execute("rm ${file}")
parallel(
lambda: client1.succeed(subscribe("-i 3688cdd7-aa07-42a4-be22-cb9352917e40", "reader")),
lambda: [
server.wait_for_console_text("3688cdd7-aa07-42a4-be22-cb9352917e40"),
client2.succeed(publish("-m test", "writer"))
])
# QoS = 1, so both subscribers should get it
server.execute(subscribe("-q 1"))
client1.execute(subscribe("-q 1"))
parallel(
lambda: client1.fail(subscribe("-W 5 -i 24ff16a2-ae33-4a51-9098-1b417153c712", "reader")),
lambda: [
server.wait_for_console_text("24ff16a2-ae33-4a51-9098-1b417153c712"),
client2.succeed(publish("-m test", "reader"))
])
# we need to give the subscribers some time to connect
client2.execute("sleep 5")
client2.succeed(publish("-m BAR -q 1"))
check_acl()
for machine in server, client1:
machine.wait_until_succeeds("grep -q BAR ${file}")
machine.execute("rm ${file}")
def check_tls():
client1.succeed(
subscribe(
"--cafile ${snakeOil}/ca.crt "
"--cert ${snakeOil}/client1.crt "
"--key ${snakeOil}/client1.key",
topic="$SYS/#",
port=${toString tlsPort},
user="no_such_user"))
check_tls()
'';
})