Thursday, May 23, 2019

Let's Encrypt for Meraki Webhooks and Scanning API

Yesterday Meraki sent out a notification to their customers that they were updating their Scanning API and webhooks features to only allow HTTPS based receivers. No more unencrypted HTTP. This, naturally, makes sense since there's potentially sensitive customer and configuration data being sent over the Internet with these features, so I applaud Meraki on taking this step.

But, I've done a bunch of testing and PoC work with those APIs using HTTP. Shame on me for doing it unencrypted, but, well, things are hard. OK, not really. So I'm glad Meraki has pushed me to tighten up my own testing environment. Since before today I didn't have a certificate I could use to do this, and I spent the morning hammering this all out, I thought I'd put this here in case you find it useful.

For posterity, here's the notice that Meraki sent out.



For a little background I've spent some time experimenting with the Meraki Scanning API as a way to get user data for numerous WiFi installations I work with. A bit more recently Meraki released a new webhooks alerting feature and I built a little red and blue LED rig that sits on my monitor and flashes at me any time a change is made to a couple of my Meraki networks that I care about (silly tricks really, but made for a fun project for a day). These, and a few other miscellaneous things I've done with these Meraki features, have all been based on work that Meraki's Cory Guynn has put out on Github. Specifically, this tool: https://github.com/dexterlabora/cmxreceiver-python

The cmxreceiver-python tools do not implement SSL. But luckily, they are all based on Flask, which is SSL capable. Updating these tools to be in line with the new Meraki policy isn't going to be difficult.

Encrypt All The Things

I'd heard about Let's Encrypt before, and (like many, many things) I had it on my list of stuff to check out one day. As it turns out, today was the day.

Let's Encrypt is a free public CA service that let's normal people like you and I obtain cryptographic certificates for use on our own websites for free. They require you to prove to them that you own, or have administrative access, to a website or domain, and will then grant you a certificate so you can enable SSL encryption.

Yup, this sounds like what I need.

I'm not going to run through all the possible ways you can request and install a certificate as Let's Encrypt has a tool (called Certbot) that can accommodate numerous web servers (Apache, NGINX) hosting platforms (AWS, Google), operating systems (*nix's galore) and other platforms.

For me, I'm not using a web server or a hosting service, I'm using Flask on my Raspberry Pi. As well, for reasons, I wanted to do the certificate requesting from a box other than my Pi. After looking over the Let's Encrypt Docs I decided that I would like a wildcard certificate, and that the manual method would be appropriate as I wanted to do the certificate requesting on one box, and then move the cert to my Pi.

Certbot

With that decided it was time to install Certbot. This part is being done on my Debian Stretch box.

sudo apt install certbot

Neato. I now have me a Certbot.

Next up was to use my shiny new Certbot to request me a certificate. I elected to use a DNS challenge to prove ownership to Let's Encrypt for two reasons:
  1. I own my own domain making it trivial to add a TCT record for verification
  2. A DNS challenge allows you to request a wildcard certificate
Using the manual method, and requesting a DNS challenge via Certbot is done with this command:

sudo certbot certonly --manual --preferred-challenges dns

At this point Certbot launches into a series of questions asking you for your contact email, agreement to some ToS, the domain you wish to request the cert for, etc. Nothing complicated here.

After you answer all the questions Certbot has for you it gives you information to create a TXT record for your domain that it will use to verify you have administrative access, and then pause waiting for you to hit enter after you've completed your end of the bargain. It will look something like this:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Please deploy a DNS TXT record under the name
_acme-challenge.brokennetwork.ca with the following value:

2_MBfK5gT4D<SecrEt_StUFf_HeRe>eow1kJXazWJH9H1I

Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Press Enter to Continue

With this I headed over to my DNS hosting service and set up the TXT Record.


And since we always verify our work:



Cool. I went back to my terminal and hit Enter.

Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/brokennetwork.ca/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/brokennetwork.ca/privkey.pem
   Your cert will expire on 2019-08-21. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le


Nice! I got my cert! That was super easy.

Handle with Care

The next steps involve me moving my new cert and private key from the machine I requested them on to my Raspberry Pi I do my testing on. I'm not going to detail the SCP'ing a couple files from one place to another, and you should note that the paths and locations I'm referencing in the next configurations aren't the actual values I used. Please, consider that the private key you're working with here is the proverbial keys to the kingdom. Make sure you store it in an appropriate location, with proper permissions set on the file. 

Cert, Chain, or FullChain?

Certbot will leave you with a few files in the directory it references in the final output. It also says that the "certtificate and chain have been saved" to this location. It's important to mention here that the Meraki Dashboard requires you to use the fullchain.pem file you will find in that location. 

I originally used the cert.pem file when I did the steps that are comping up next to set up Flask, and it tested perfectly using the Chrome browser, but when I went to change the receiver URLs in the Meraki dashboard it failed on both the verification for the Scanning API, and the test webhook for webhooks. Once I swapped out the cert.pem for the fullchain.pem the Dashboard stopped complaining and everything started working exactly as it should

Flask and SSL

With the certificate in hand the next part is to modify the cmxreceiver-python script to use my new cert. This, as it turns out, is even easier than getting the cert. 

Flask has built in support for SSL. If you have Flask installed you likely already have what you need. If not, when you run this you'll get an error telling you what you're missing. For me, my previous Flask install was good to go so I'm assuming yours will be to.

Inside my modified cmxreceiver-python script I added two variables for the paths to my cert and private key in the "User Defined Settings" section:

cert = "/etc/certs/fullchain.pem"
privkey = "/etc/certs/privkey.pem"

And then modify the app.run call that gets Flask up and running from this:

if __name__ == '__main__':
    main(sys.argv[1:])
    app.run(host='0.0.0.0',port=5000,debug=False)

To this:

if __name__ == '__main__':
    context = (cert, privkey)
    main(sys.argv[1:])
    app.run(host='0.0.0.0',port=5000,debug=False, ssl_context=context)

That's it! The modifications are complete, so let's run this thing!

j@RaspPi01:~/git/cmxreceiver-python $ sudo python3 cmxreceiver_https.py 
[sudo] password for j: 
validator: <snipped>
secret: <snipped>
 * Serving Flask app "cmxreceiver_https" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on https://0.0.0.0:5000/ (Press CTRL+C to quit)

You'll notice the last line there clearly states "Running on https://". 

The only thing left now to to change the Dashboard settings for Scanning API or webhooks to use HTTPS instead of HTTP. Reference the docs I linked above to find those settings and get that part completed.


No comments:

Post a Comment