Python SlackClient with Proxy Server Support

While writing my first Slack bot using some of the tutorials found here, I ran into a big problem. My bot will live in a locked-down environment where the only outbound access is through a proxy server.

I had to do quite a bit of searching through the source code, because, for whatever reason, I could not find how to use a proxy server with the slackclient python library in the documentation or in any simple google searches. So, I am writing this entry in hopes to save someone else the headache of having to track down the nuances of proxy usage in the urllib3 and the websocket libraries.

TLDR;

The urllib3 library is looking for a dictionary entry index “https” while the websocket-client library is looking for an index called “http“.

So, when invoking an instance of SlackClient, include a dictionary something like the proxies dictionary below.

from slackclient import SlackClient

# My proxy server is at 192.168.15.15:8080
proxies = dict(http="192.168.15.15:8080", https="192.168.15.15:8080")
bot_token = "abc123"

# Connect to slack.com
client = SlackClient(bot_token, proxies=proxies)

try:
   # Create the websocket and start the firehose!
   if client.rtm_connect():
     logger.warn("SlackBot is connected and running!")
     while True:
        command, channel = drink_from_firehose(client.rtm_read())
        if command and channel:
          handle_command(command, channel)
        time.sleep(READ_WEBSOCKET_DELAY)
   else:
     logger.error("Connection failed. Invalid Slack token or bot ID?")
except Exception as e:
   logger.error("Exception thrown: %s" % e)

Not to worry, because even though the websocket-client library is looking for an index called “http”, the websocket itself is still sent over SSL.

What? What is the proxy server actually doing?

I’m sure that you are familiar with the HTTP GET and POST methods. Well, when using a proxy server, the urllib3 sends an HTTP method CONNECT to the proxy server. See this article for more details on the CONNECT method.

In the example below, the proxy server will broker an SSL tunnel between slack.com and the SlackClient instance. Then, once the client calls rtm_connect() to “open the firehose”, the websocket library opens a websocket to slack-msgs.com for the data feed in a new TCP connection and a new SSL tunnel brokered through the proxy server with another CONNECT request.

Note that this is NOT a man-in-the-middle SSL proxy, meaning the proxy server itself cannot see ANY of the SSL tunnel traffic as it is a forwarding proxy only. The proxy server can ONLY see the CONNECT strings that are sent in clear text, which does NOT contain your SlackClient token or any of the slack messages.

In the above packet capture, you can see that the CONNECT call to the proxy server is in clear text but the proxy server knows to create an HTTPS tunnel between the slack.com endpoint and the SlackClient thanks to the port designation :443 in the URL of the CONNECT request. 

Per this article from Slack, for full bot functionality, your proxy server will need to allow access to the following domains:

*.slack.com
*.slack-msgs.com
*.slack-files.com
*.slack-imgs.com
*.slack-edge.com
*.slack-core.com
*.slack-redir.net

I hope this small article will help someone trying to write a bot that must consume or “pull” an RTM feed from #Slack via a local proxy server!

 

Leave a comment