Securing Home Assistant Alexa Integration

This is an old post!

This post is over 2 years old. Solutions referenced in this article may no longer be valid. Please consider this when utilizing any information referenced here.

One of the big missing pieces from my conversion to Home Assistant was Amazon Alexa integration. It wasn’t something we used a lot, but it was a nice to have. Especially for walking out a room and saying “Alexa, turn off the living room lights.”

I had been putting it off a bit because the setup instructions are rather complex. But this weekend I found myself with a couple free hours and decided to work through it. It actually wasn’t as difficult as I expected it to be, but it is definitely not the type of thing a beginner or someone who does not have some programming and sysadmin background could accomplish.

But in working through it, there was one thing that was an immediate red flag for me: the need to expose your Home Assistant installation to the Internet. It makes sense that you would need to do this - the Amazon mothership needs to send data to you to take an action after all. But exposing my entire home automation system to the Internet seems like a really, really bad idea.

So in doing this, rather than expose port 443 on my router to the Internet and open my entire home to a Shodan attack, I decided to try something a bit different.

OpenVPN

So I already have a VM that hosts this site among others. So my first step was to setup an OpenVPN tunnel between my pfSense router and the virtual machine. There are a lot of tutorials out there on how to setup OpenVPN so I won’t duplicate that here.

What you do need to do, though, is add a firewall rule that only allows your tunnel to talk to your Home Assistant installation’s IP address. This way your entire internal network is not potentially exposed should your VM be compromised.

nginx

Next, we’ll configure nginx to act as a proxy between AWS and your internal Home Assistant installation. Here’s the configuration I eventually came up with:

server {
    listen 80;
    server_name home-assistant-proxy.example.com;
    include "/etc/nginx/sites-available/shared/letsencrypt.conf";

    location / {
        return 301 https://home-assistant-proxy.example.com$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name home-assistant-proxy.example.com;

    ssl_certificate     /etc/letsencrypt/live/home-assistant-proxy.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/home-assistant-proxy.example.com/privkey.pem;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location /api/alexa/smart_home {
        limit_except POST {
            deny all;
        }

        if ($http_user_agent !~* ".*Alexa.*us-east") {
            return 403;
        }

        proxy_pass http://home-assistant.example.com;
    }

    location / {
        deny all;
    }
}

So a couple of things about what is going on here.

The port 80 (HTTP) host is basically there to allow LetsEncrypt to create and automatically renew the certificates needed. HTTPS is required to work with Alexa and, while you could get self-signed certificates to work with some additional work, there is really no reason to do that when LetsEncrypt exists and is super easy to use.

Second, we are only proxying one API endpoint: /api/alexa/smart_home, and we are only allowing POST requests to it. When Lambda makes a request to your endpoint, it looks something like this:

54.147.55.16 - - [17/May/2020:16:50:39 +0000] "POST /api/alexa/smart_home HTTP/1.1" 200 1955 "-" "<your Alexa still name> - us-east-1 - python-requests/2.21.0"

We’re also looking at the User Agent string sent with the request and being sure that it looks like it’s coming from AWS Lambda. So only if it passes all of these checks do we proxy it to the home Home Assistant installation. The cool thing is that they send in the User Agent the name of the skill you created, so you could give it a unique ID that only you would know, and check for that in your nginx config for an added level of security.

I would really like to have this locked to specific IP addresses even. But because haaska executes in AWS Lambda it could come from any number of thousands of IP addresses (although in testing so far it seems to originate from about five or ten). The only way to get around this is to do a reverse DNS lookup on the IP and be sure it originated from *.compute-1.amazonaws.com. I intend to do this as well, but it requires building a module, and I haven’t had time to go to that level yet.

Conclusions

Nothing is ever fully secure; it’s all about managing risk. If you can do something that keeps 99% of attacks out, that is a definite win. In this case, my threat model was a Shodan style attack where someone spidering the dynamic IP range assigned to my ISP found port 443 open on my router and found a Home Assistant installation that could be compromised.

This solution massively reduces the chances of something like that happening by:

  • Requiring you to know the hostname that the API will answer on.

  • Only exposing a single API endpoint, not the entire Home Assistant installation.

  • Only allowing one specific type of request with a specific type of user agent.

If you don’t want to go the full on VM and OpenVPN route, you could get at least some protection by following this same workflow of putting an nginx proxy in front of your Home Assistant installation and exposing that to the Internet from your router. While that’s not ideal, it’s still far safer than just exposing Home Assistant to the Internet.

Presentation

Interested in learning more about this article? I recently gave a talk about my experience converting to and using Home Assistant. Feel free to check it out and watch the video:

Comments (0)

Interested in why you can't leave comments on my blog? Read the article about why comments are uniquely terrible and need to die. If you are still interested in commenting on this article, feel free to reach out to me directly and/or share it on social media.

Contact Me
Share It
Home Assistant
I have been a SmartThings user for many years. The orginal reason was that, when we bought our current house in 2012, I wanted to turn the eave lights on at sunset and off a few hours later. After a short attempt to use Wifi-based Wemo switches, I settled on SmartThings and GE Z-Wave switches. I was so happy with it that I started putting them in more places. I added Kwikset SmartCode keypad locks and door sensors. I added more switches, like to turn on the garage overhead lights when the doors opened. I added sensors to monitor the temperature in the closet where I keep my server. And for many years this setup worked great. But over the last year, and especially since Samsung acquired SmartThings, I have become increasingly disillusioned with the SmartThings ecosystem. This last week, my disillusionment and frustration finally boiled over, and I migrated to a new platform. So why did I abandon SmartThings?
Read More
Home Assistant
My mailbox - yes, my physical mailbox where I receive actual mail - is one of the things that has stubbornly resisted my attempts to automate it. I’ve tried a few different solutions. Third party proprietary chimes. A Z-Wave tilt sensor on the door. But nothing has worked long-term.
Read More
Hardware
So here we are in October. COVID-19 is still with us and I am still working from home. Meanwhile, summer has quickly changed to autumn. The leaves are falling as are the temperatures. My house was the model home for our neighborhood, and what would have been the garage was finished in and used as a sales office. So when we bought the house, I was like, perfect, a perfect spot for an office! But the problem is that, because it was a garage, it’s not connected to the house’s HVAC system. In the summer there is a mini-split that keeps the whole area cool. But it’s kind of loud. However, I do have some of these DeLonghi Oil Radiators to use in the winter which provide abundant, silent heat without using very much power. But the downside is that they take awhile to warm up. Wouldn’t it be cool if I could have them turn on an hour early and “pre-warm” the office? Well, to get the obvious part out of the way, yes, there is timer functionality, but that is not nearly as cool as tying it into the rest of my smart home. But it has a remote. What if I could find a way to use Home Assistant to send IR commands to the heater? Turns out you can!
Read More