2 Commits 6576416360 ... 69f7ed359a

Author SHA1 Message Date
  Fabian Peter Hammerle 69f7ed359a release v0.4.0 1 week ago
  Fabian Peter Hammerle 292046b88d added command line option --mqtt-disable-tls 1 week ago
5 changed files with 128 additions and 6 deletions
  1. 4 0
      CHANGELOG.md
  2. 5 0
      README.md
  3. 26 4
      systemctl_mqtt/__init__.py
  4. 43 0
      tests/test_cli.py
  5. 50 2
      tests/test_mqtt.py

+ 4 - 0
CHANGELOG.md

@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+## [0.4.0] - 2020-09-10
+### Added
+- command line option `--mqtt-disable-tls`
+
 ## [0.3.0] - 2020-06-21
 ### Added
 - home assistant: enable [automatic discovery](https://www.home-assistant.io/docs/mqtt/discovery/#discovery_prefix)

+ 5 - 0
README.md

@@ -101,6 +101,11 @@ Pre-built docker image are available at https://hub.docker.com/r/fphammerle/syst
 
 Annotation of signed tags `docker/*` contains docker image digests: https://github.com/fphammerle/systemctl-mqtt/tags
 
+## MQTT via TLS
+
+TLS is enabled by default.
+Run `systemctl-mqtt --mqtt-disable-tls …` to disable TLS.
+
 ## MQTT Authentication
 
 ```sh

+ 26 - 4
systemctl_mqtt/__init__.py

@@ -38,6 +38,9 @@ import systemctl_mqtt._dbus
 import systemctl_mqtt._homeassistant
 import systemctl_mqtt._mqtt
 
+_MQTT_DEFAULT_PORT = 1883
+_MQTT_DEFAULT_TLS_PORT = 8883
+
 _LOGGER = logging.getLogger(__name__)
 
 
@@ -263,6 +266,7 @@ def _run(
     mqtt_topic_prefix: str,
     homeassistant_discovery_prefix: str,
     homeassistant_node_id: str,
+    mqtt_disable_tls: bool = False,
 ) -> None:
     # pylint: disable=too-many-arguments
     # https://dbus.freedesktop.org/doc/dbus-python/tutorial.html#setting-up-an-event-loop
@@ -276,9 +280,13 @@ def _run(
         )
     )
     mqtt_client.on_connect = _mqtt_on_connect
-    mqtt_client.tls_set(ca_certs=None)  # enable tls trusting default system certs
+    if not mqtt_disable_tls:
+        mqtt_client.tls_set(ca_certs=None)  # enable tls trusting default system certs
     _LOGGER.info(
-        "connecting to MQTT broker %s:%d", mqtt_host, mqtt_port,
+        "connecting to MQTT broker %s:%d (TLS %s)",
+        mqtt_host,
+        mqtt_port,
+        "disabled" if mqtt_disable_tls else "enabled",
     )
     if mqtt_username:
         mqtt_client.username_pw_set(username=mqtt_username, password=mqtt_password)
@@ -310,8 +318,15 @@ def _main() -> None:
         formatter_class=argparse.ArgumentDefaultsHelpFormatter,
     )
     argparser.add_argument("--mqtt-host", type=str, required=True)
-    argparser.add_argument("--mqtt-port", type=int, default=8883)
+    argparser.add_argument(
+        "--mqtt-port",
+        type=int,
+        help="default {} ({} with --mqtt-disable-tls)".format(
+            _MQTT_DEFAULT_TLS_PORT, _MQTT_DEFAULT_PORT
+        ),
+    )
     argparser.add_argument("--mqtt-username", type=str)
+    argparser.add_argument("--mqtt-disable-tls", action="store_true")
     password_argument_group = argparser.add_mutually_exclusive_group()
     password_argument_group.add_argument("--mqtt-password", type=str)
     password_argument_group.add_argument(
@@ -340,6 +355,12 @@ def _main() -> None:
         help=" ",
     )
     args = argparser.parse_args()
+    if args.mqtt_port:
+        mqtt_port = args.mqtt_port
+    elif args.mqtt_disable_tls:
+        mqtt_port = _MQTT_DEFAULT_PORT
+    else:
+        mqtt_port = _MQTT_DEFAULT_TLS_PORT
     if args.mqtt_password_path:
         # .read_text() replaces \r\n with \n
         mqtt_password = args.mqtt_password_path.read_bytes().decode()
@@ -361,7 +382,8 @@ def _main() -> None:
         )
     _run(
         mqtt_host=args.mqtt_host,
-        mqtt_port=args.mqtt_port,
+        mqtt_port=mqtt_port,
+        mqtt_disable_tls=args.mqtt_disable_tls,
         mqtt_username=args.mqtt_username,
         mqtt_password=mqtt_password,
         mqtt_topic_prefix=args.mqtt_topic_prefix,

+ 43 - 0
tests/test_cli.py

@@ -32,6 +32,7 @@ import systemctl_mqtt._utils
         "argv",
         "expected_mqtt_host",
         "expected_mqtt_port",
+        "expected_mqtt_disable_tls",
         "expected_username",
         "expected_password",
         "expected_topic_prefix",
@@ -41,6 +42,16 @@ import systemctl_mqtt._utils
             ["", "--mqtt-host", "mqtt-broker.local"],
             "mqtt-broker.local",
             8883,
+            False,
+            None,
+            None,
+            None,
+        ),
+        (
+            ["", "--mqtt-host", "mqtt-broker.local", "--mqtt-disable-tls"],
+            "mqtt-broker.local",
+            1883,
+            True,
             None,
             None,
             None,
@@ -49,6 +60,32 @@ import systemctl_mqtt._utils
             ["", "--mqtt-host", "mqtt-broker.local", "--mqtt-port", "8883"],
             "mqtt-broker.local",
             8883,
+            False,
+            None,
+            None,
+            None,
+        ),
+        (
+            ["", "--mqtt-host", "mqtt-broker.local", "--mqtt-port", "8884"],
+            "mqtt-broker.local",
+            8884,
+            False,
+            None,
+            None,
+            None,
+        ),
+        (
+            [
+                "",
+                "--mqtt-host",
+                "mqtt-broker.local",
+                "--mqtt-port",
+                "8884",
+                "--mqtt-disable-tls",
+            ],
+            "mqtt-broker.local",
+            8884,
+            True,
             None,
             None,
             None,
@@ -57,6 +94,7 @@ import systemctl_mqtt._utils
             ["", "--mqtt-host", "mqtt-broker.local", "--mqtt-username", "me"],
             "mqtt-broker.local",
             8883,
+            False,
             "me",
             None,
             None,
@@ -73,6 +111,7 @@ import systemctl_mqtt._utils
             ],
             "mqtt-broker.local",
             8883,
+            False,
             "me",
             "secret",
             None,
@@ -87,6 +126,7 @@ import systemctl_mqtt._utils
             ],
             "mqtt-broker.local",
             8883,
+            False,
             None,
             None,
             "system/command",
@@ -97,6 +137,7 @@ def test__main(
     argv,
     expected_mqtt_host,
     expected_mqtt_port,
+    expected_mqtt_disable_tls,
     expected_username,
     expected_password,
     expected_topic_prefix: typing.Optional[str],
@@ -112,6 +153,7 @@ def test__main(
     run_mock.assert_called_once_with(
         mqtt_host=expected_mqtt_host,
         mqtt_port=expected_mqtt_port,
+        mqtt_disable_tls=expected_mqtt_disable_tls,
         mqtt_username=expected_username,
         mqtt_password=expected_password,
         mqtt_topic_prefix=expected_topic_prefix or "systemctl/hostname",
@@ -157,6 +199,7 @@ def test__main_password_file(tmpdir, password_file_content, expected_password):
     run_mock.assert_called_once_with(
         mqtt_host="localhost",
         mqtt_port=8883,
+        mqtt_disable_tls=False,
         mqtt_username="me",
         mqtt_password=expected_password,
         mqtt_topic_prefix="systemctl/hostname",

+ 50 - 2
tests/test_mqtt.py

@@ -68,8 +68,8 @@ def test__run(
             homeassistant_node_id=homeassistant_node_id,
         )
     assert caplog.records[0].levelno == logging.INFO
-    assert caplog.records[0].message == "connecting to MQTT broker {}:{}".format(
-        mqtt_host, mqtt_port
+    assert caplog.records[0].message == (
+        "connecting to MQTT broker {}:{} (TLS enabled)".format(mqtt_host, mqtt_port)
     )
     # correct remote?
     assert create_socket_mock.call_count == 1
@@ -145,6 +145,54 @@ def test__run(
     assert mqtt_client._thread is None
 
 
+@pytest.mark.parametrize("mqtt_host", ["mqtt-broker.local"])
+@pytest.mark.parametrize("mqtt_port", [1833])
+@pytest.mark.parametrize("mqtt_disable_tls", [True, False])
+def test__run_tls(caplog, mqtt_host, mqtt_port, mqtt_disable_tls):
+    caplog.set_level(logging.INFO)
+    with unittest.mock.patch(
+        "paho.mqtt.client.Client"
+    ) as mqtt_client_class, unittest.mock.patch("gi.repository.GLib.MainLoop.run"):
+        systemctl_mqtt._run(
+            mqtt_host=mqtt_host,
+            mqtt_port=mqtt_port,
+            mqtt_disable_tls=mqtt_disable_tls,
+            mqtt_username=None,
+            mqtt_password=None,
+            mqtt_topic_prefix="systemctl/hosts",
+            homeassistant_discovery_prefix="homeassistant",
+            homeassistant_node_id="host",
+        )
+    assert caplog.records[0].levelno == logging.INFO
+    assert caplog.records[0].message == (
+        "connecting to MQTT broker {}:{} (TLS {})".format(
+            mqtt_host, mqtt_port, "disabled" if mqtt_disable_tls else "enabled"
+        )
+    )
+    if mqtt_disable_tls:
+        mqtt_client_class().tls_set.assert_not_called()
+    else:
+        mqtt_client_class().tls_set.assert_called_once_with(ca_certs=None)
+
+
+def test__run_tls_default():
+    with unittest.mock.patch(
+        "paho.mqtt.client.Client"
+    ) as mqtt_client_class, unittest.mock.patch("gi.repository.GLib.MainLoop.run"):
+        systemctl_mqtt._run(
+            mqtt_host="mqtt-broker.local",
+            mqtt_port=1833,
+            # mqtt_disable_tls default,
+            mqtt_username=None,
+            mqtt_password=None,
+            mqtt_topic_prefix="systemctl/hosts",
+            homeassistant_discovery_prefix="homeassistant",
+            homeassistant_node_id="host",
+        )
+    # enabled by default
+    mqtt_client_class().tls_set.assert_called_once_with(ca_certs=None)
+
+
 @pytest.mark.parametrize("mqtt_host", ["mqtt-broker.local"])
 @pytest.mark.parametrize("mqtt_port", [1833])
 @pytest.mark.parametrize("mqtt_username", ["me"])