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: