SSH tunneling and dealing with webhooks that require a trusted SSL certificate when developing locally with Django
I'm currently working on version 2 of our Shopify app where we'll be adding custom Slack slash commands. When adding slash commands to a custom app, Slack requires the receiving server to have a trusted (not self-signed) SSL certificate.
Normally, what we do is simply create a reverse SSH tunnel to our stage server which is publicly accessible to receive webhooks, for example:
ssh root@stage.example.com -R 8000:localhost:80
The above example will basically forward any traffic on port 8000 of the remote server to port 80 of my local machine.
So say if you go to http://stage.example.com:8000, that request will then get forwarded to my local web server running on port 80 and return the response back to the user.
Now the issue is the SSL part, and the solution turned out to be actually quite simple: add an upstream server in the Nginx configuration of the stage server pointing to 127.0.0.1:8000 and temporarily pass the requests there.
Here's an example config:
upstream slacky_wsgi_server { server unix:/webapps/slacky/run/gunicorn.sock fail_timeout=0; } upstream localtunnel { server 127.0.0.1:8000; } server { listen 80; server_name stage.example.com; rewrite ^ https://$server_name$request_uri? permanent; } server { listen 443; server_name stage.example.com; ssl on; ssl_certificate /etc/ssl/example.crt; ssl_certificate_key /etc/ssl/example.key; ... location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_set_header Host $http_host; proxy_redirect off; if (!-f $request_filename) { proxy_pass http://localtunnel; #http://slacky_wsgi_server; break; } } ... }
The main things to note here are the upstream localtunnel and the proxy_pass http://localtunnel sections.
So now, if someone visits https://stage.example.com, it will hit the Nginx server which is using a trusted SSL cert and then forwards the requests to my local web server via the reverse SSH tunnel.
This specific example of course takes down our stage site, which is fine in our case as no one else is actively using it. But a way around it is simply creating separate Nginx virtual hosts/configs.
For example, if you have two devs Tom and Jerry who both need to use the server to test webhooks, you can create two additional virtual hosts on the same server: tom.example.com, jerry.example.com. Now you tell Tom to always use port 8001 and Jerry port 8002 so there won't be any conflicts. Then in the configs, add the upstream sections for 127.0.0.1:8001 in one config and 127.0.0.1:8002 in another config, then set the proxy_pass to each one. Add the DNS records and get free trusted certs from Let's Encrypt.
I'm a bit disappointed of myself for not thinking of this solution before. This actually solved one other issue on our side when using Shopify's Embedded App SDK as it loads our app inside an iframe and the web browser blocks the content for security reasons because our local web server wasn't using SSL.
This setup just made developing Shopify apps and dealing with other external integrations so much easier :D.