Best practices to secure mycroft MessageBus

I once read in the docu that the MessageBus is not secure out-of-the-box.

What kind of best practices could I apply to secure it?

Hi @ScienceGuy,

You could bind it on 127.0.0.1 instead of listening on 0.0.0.0 , you could block the port via iptables for other connections than from localhost.

You could have a look here:


2 Likes

But if I block the port, isn’t the connection to Android app blocked as well?
I understood that the App connects via an (unsecured) websocket.

You could allow traffic only on the bus port for this Android device only.

This goes back several years but you can read the original thread here.

1 Like

Hey all, seanfitz here, author of the original MessageBus. The security concerns here are very real, and should be taken seriously. There has yet to be (or I have yet to see) a concrete effort to secure the MessageBus, and as such it should not be exposed to off-device networks as-is[0]. You’re leaving yourself open to abuse and sniffing similar to (and beyond) what pcwii has described.

First, let me talk a little bit about some of the decisions for the MessageBus and the event-based architecture. By using an eventing system, it became trivial to compose skills; in fact, the IntentDeterminationService was (is?) itself a skill! The SpeechListener could have/should have been a skill, but operated in many of the same ways (no input events, emitted utterance events). This was good, and allowed for interesting development/debugging tooling, connecting skills/event sources from multiple languages, and kept the door open for web-like environments.

Aside: did you know that most smart-tvs can run a WebKit like environment? Many set-top box or TV native apps are written portably because of this!

So, while the door was left open for “just connect any websocket client” type access, initial development focused on the local services, and the Mark I (or raspi) devices. Initial security on the MessageBus[3] relied on the assumption that the server was bound to localhost and/or behind a firewall, assumptions that were later invalidated.

Enough about the past, let’s talk about what should be done here! There are two aspects that need to be addressed. Without both, you may get some warm fuzzies, but your things aren’t actually secure.

Transport Security

Transport security today usually involves encrypting data between two parties. We’re typically referring to SSL or it’s more modern sibling TLS. This provides two types of guarantees. First, that no one (nation states aside) can listen in on the conversation between the two parties simply by capturing bytes as they go past (or interject their own bytes). Second, by relying on certificate chains/authority, the client has a reasonable level of confidence that they have connected to the server to which they intended to connect. There have been a couple of folks [2] that have added SSL/TLS frontends to the MessageBus; this can be done pretty easily nowadays by spinning up nginx and LetsEncrypt and letting them run between the MessageBus and the outside world.

This seems pretty solid on its face, but there is a glaring hole based on the nature of the MessageBus. You’ll note that nothing thus far has indicated what parties are allowed to connect, and in truth that’s because any websocket client can connect! Aside from being able to trigger RCEs as pcwii rightly calls out, because of the core functionality of MessageBus, any client receives messages sent by any other client, allowing a casual observer to capture the contents of any message as it goes past.

Transport security is a good thing! But in isolation, it provides us with little meaningful security.

Access Control

Access control involves deciding who can do what, and is frequently[1] broken down into two components.

Authentication

Authentication, or authN, deals with identifying the client. In it’s most basic (pun-intended) form on the web, this can be handled via Basic HTTP Authentication, which requires that a user present a username (my identity) and a password (a secret only I know, so you know it’s me!). There are way better ways to do this now, but we’ll leverage this for the sake of an already long post.

Authorization

Authorization, or authZ, deals with “what is this user/client allowed to do?” In our simple setup so far, the user explicitly states their Identity via authN, and implicitly states what they believe they have permissions to do via the action they attempt. When systems only deal with Authentication, they implicitly assume “user is identified as userA, and is allowed access to all resources and operations.” It’s not great, but it’s definitely better than nothing.

A Proof of Concept

Because WebSockets rely on HTTP/1.1 to establish connectivity, we can leverage many of the same tools and techniques to get some minimum grade security precautions in place.

Transport Security

A number of folks have set up SSL/TLS for themselves, and there are myriad guides available on the internet for your stack of choice. Personally, I’ve become a fan of dokku and the dokku-letsencrypt plugin. I like the development flow of dokku (ymmv). The LetsEncrypt extension provides brain-dead simple TLS setup, putting nginx between your service and the outside world, and managing cert rotation. I suspect there’s a bit of effort to get mycroft-core running within dokku, but it is unlikely that far off from any existing containerization efforts.

Access Control

One of the key benefits of Mycroft, in my humble opinion, is the ability to arbitrarily compose skills using events. This is also, sadly, it’s greatest security/privacy vulnerability, as a malicious skill has a lot of power. This is akin to the problems found in the early Android app ecosystem, and in the long term, likely requires as sophisticated a solution. Apps request specific permissions at installation time for what messages can be sent/received, most permissions should be considered optional and apps should fail gracefully in their absence, better execution/file system sandboxing, and much more. This is a non-trivial endeavor, and beyond the scope of what I’ll propose today.

Instead, let’s leverage HTTP Basic Authentication. Per the MDN docs for protocol upgrades, a WebSocket connection is initiated by an HTTP GET request (with some additional headers). Knowing this, we can extend our server implementation to require Basic Auth for that GET request, something any webserver worth its mettle should have built in. Fortunately for us, MessageBus is built on Tornado, a webserver worth its mettle.

Below is a proof-of-concept authentication mixin for an instance of tornado.websocket.WebSocketHandler. As opposed to user:pass combos, this example will assume that the user portion of the credentials is a shared secret/API key (similar to how the Stripe API authenticates), and the password portion is left blank.

import base64
from typing import Set

StringSet = Set[str]


class AuthenticatedHandlerMixin(object):
    """
    Basic authentication
    Intention is for username to be an api key and the password to be null
    If secrets provided, check for existence of API key.
    """

    def __request_auth(self):
        self.set_header('WWW-Authenticate', 'Basic realm=BasicAuthSample')
        self.set_status(401)
        self.finish()

    def check_authentication(self, secrets: StringSet = None) -> bool:
        auth_header = self.request.headers.get("Authorization", "")

        if not auth_header.startswith("Basic "):
            self.__request_auth()
            return False
        else:
            auth_decoded = base64.b64decode(auth_header[6:]).decode('ascii')
            auth_parts = str(auth_decoded).split(':', 1)
            if len(auth_parts) != 2:
                self.__request_auth()
                return False

            username, password = str(auth_decoded).split(':', 1)
            if not username:
                self.__request_auth()
                return False

            if secrets and username in secrets:
                return True

        self.__request_auth()
        return False

And here’s how you might hook it into the MessageBus event handler:

class MessageBusEventHandler(AuthenticatedHandlerMixin, WebSocketHandler):
    def initialize(self, secrets: StringSet = None):
        self.secrets = secrets

    def prepare(self) -> Optional[Awaitable[None]]:
        self.check_authentication(self.secrets).

The above snippets are practically drop-in ready for mycroft-core, however they represent only half the battle. Next, every websocket client[4] will have to be updated to allow you use Basic Auth. I found this to be surprisingly uncommon (and nigh impossible in mycroft-core back in the day), but more modern client libraries will allow you to specify HTTP headers directly, and then it just falls on you to construct the right value. The majority of this work will actually be in plumbing through the configuration.

Here’s an example of constructing the right value:

auth_str = user + ":"
    if password:
        auth_str += password
auth_header = "Authorization: Basic %s " % str(base64.b64encode(bytes(auth_str, 'utf-8')), 'utf-8')

Other Implementations

Because I use an off-the-shelf solution for transport security, I baked the access control components into my application. I suspect that one could configure nginx to handle both transport security and this naive Basic Auth solution and drop it in front of any WebSocket service.

Improvements

The benefit of the above example is that it demonstrates how to get some kind of Authorization into the websocket connectivity, a precursor to real security in my mind. While I’ve used a simple Basic Auth mechanism, the industrious coder could just as easily build or integrate with an OAuth or JWT solution, use HMAC request signing (both for connection and on each message!), or leverage/mix any other industry accepted solution.

Appendix

[0] A number of folks have mentioned using firewalls to lock down their MessageBus; this is plausibly secure (from an Access Control perspective), but assumes that you have either a limited and fixed set of IP addresses from which you connect, or are familiar enough with IPTables to configure MAC address filtering and have a small number of devices from which you connect. In my experience, neither holds up well in the fullness of time, and in the absence of a more flexible solution, people will sacrifice security for convenience. I am surely guilty of this!

[1] I still think about this problem regularly, but it’s been a few years since I’ve had to put together a concrete implementation. Let’s assume my information is best practices circa 2015!

[2] Citation needed, but you know who you are!

[3] Or the complete lack there-of!

[4] There’s been some effort of late to unify a bunch of the websocket client setup code within mycroft-core, minimizing the effort there. There are, however, a number of non-mycroft-core websocket clients out there, including the KDE and Gnome clients.

3 Likes