TLS configuration for mosquitto

My goal: to set up an MQTT broker (mosquitto) for use with the OwnTracks app under a NixOS server

My approach: Make a custom systemd oneshot script that creates the TLS keys and certificates for the MQTT broker using a script provided by the OwnTracks project. Then, configure mosquitto to use the generated CA certificate, server certificate, and key file.

The issue: I can’t figure out how to point mosquitto at the TLS files. So far I’ve tried:

services.mosquitto = {
  enable = true;
  settings = {
    cafile = "/root/SSL/mosquitto/ca.crt"; 
    certfile = "/root/SSL/mosquitto/myhostname.crt";
    keyfile = "/root/SSL/mosquitto/myhostname.key";
  };
  # Other configuration....

Which results in:

[root@myhostname:~]# nixos-rebuild test
building Nix...
building the system configuration...
error: 
    Failed assertions:
     - Invalid config key services.mosquitto.settings.cafile
     - Invalid config key services.mosquitto.settings.certfile
     - Invalid config key services.mosquitto.settings.keyfile
(use '--show-trace' to show detailed location information) 

I’ve also tried using the extraConfig parameter, which results in:

error: The option 'services.mosquitto.extraConf' does not exist. Definition values:

If anyone can help me figure out how to point mosquitto to the generated TLS files, I would be very grateful.

Nevermind, figured it out. It turns out that the TLS configuration parameters go into listeners,
not into the root of the service configuration. So my config now looks like:

  services.mosquitto = {
    enable = true;
    listeners = [
      {
        users.someuser = {
          acl = [
            "readwrite #"
          ];
          password = "somepassword";
        };
        settings = {
          cafile = "/DATA/SSL/mosquitto/ca.crt";
          certfile = "/DATA/SSL/mosquitto/myhostname.crt";
          keyfile = "/DATA/SSL/mosquitto/myhostname.key";
        };
      }
    ];
  };

I also had to change ownership of the TLS files to the mosquitto user.

If you use the security.acme service, you can add your mosquitto user/service to that group:

  users.groups.acme.members = [ "mosquitto" ];

(Also possible with SupplementaryGroups on the unit).

Then I use

      settings = let
        certDir = config.security.acme.certs."${domainName}".directory;
      in
      {
        keyfile = certDir + "/key.pem";
        certfile = certDir + "/cert.pem";
        cafile = certDir + "/chain.pem";
      };

I’m gonna try this, thank you! This looks much more elegant than the hack I put together for running OwnTracks’ certificate generator.

Just for posterity’s sake, here’s the script I had before:

    systemd.services."create-mqtt-cert" = {
      description = "Create a certificate for mosquitto";
      path = [
        pkgs.bash
        pkgs.git
        pkgs.openssl
        pkgs.unixtools.ifconfig
        pkgs.unixtools.nettools
        pkgs.which
        ];

      script = ''
        DIR="/DATA/SSL/mosquitto"
        HOST="myhostname"
        test -d "$DIR" && {
                echo "Mosquitto SSL already exists, exiting"
                exit 0
        }

        # path to `ifconfig` is hardcoded in the owntracks script
        mkdir /sbin
        ln -s "$(which ifconfig)" /sbin/ifconfig

        mkdir -p "$DIR"
        cd "$DIR"
        git clone https://github.com/owntracks/tools/
        ./tools/TLS/generate-CA.sh "$HOST"

        rm /sbin/ifconfig
        rmdir /sbin

        chown -R mosquitto:mosquitto "$DIR"
      '';

      wantedBy = [ "multi-user.target" "mosquitto.service" ] ;

      unitConfig = {
        Before = [ "multi-user.target" "mosquitto.service" ]  ;
      };

      serviceConfig = {
        User = "root";
        Type = "oneshot";
        RemainAfterExit = true;
      };
    };