Slack is an amazing collaboration tool and one of the things I love about it the most is that it is a powerful platform that allows easy integrations and extensions to the basic functionality. We are using Slack internally and wrote a few extensions to help us use it more effectively when investigating malware samples. At some stage it dawned on us that many if not all Slack users share files and URLs on Slack and unlike other systems (e.g. e-mail) potentially harmful data is not scanned. We thought that a free service checking these files and URLs for malicious content would be a great extension that we can share with everyone using Slack, making their world a little safer. Even better, we made it open-source so developers can validate what we did and contribute.
What’s in this post?
I will cover the basics of creating a Slack integration based on our experience creating DBOT. My code examples will use Golang as it is the language we use internally for a lot of our code. We also wrote a complete Slack API library to use with Golang and have a few examples like a full featured, scriptable, stand-alone command line Slack client and a simple OAuth example we will be using in this post.
Slack API & OAuth
- Prerequisite — create a new app on Slack. This generates a new ClientID and ClientSecret that you can use for your OAuth configuration. This also specifies where Slack should redirect after a successful authorization.
- The button / link you created to start the process sends a request to the server. The server redirects the user to Slack using a specially crafted URL (OAuth).
- Slack presents an approval dialog and allows you to chose a team if you are part of multiple teams.
- If the user approves, Slack redirects the user to the URL you specified in the 1st step with the relevant parameters. If the user cancels, Slack also redirects to the same URL but with different parameters.
- Once you validate the parameters, you can exchange the received code parameter to an OAuth token.
- Now, you can use the token to perform requests on behalf of the user. One of the more interesting requests is to start Real Time Messaging, but I will talk about this later in the post.
Step #1 is pretty self-explanatory. Let’s see how to do step #2.
Of course, the above is a very simplistic way to persist the state between calls. Usually, this can be done via a database or something similar but to keep the example simple we just use a variable. After the user approves the integration (step 3), Slack redirects to the URL you configured when creating the application. Here is a simple example of how to handle the redirect (steps 4,5).
What can we do with the token now? Depends on the type of integration. For our purposes, we want to monitor the conversations the user participates in, which means we want to monitor channels, groups and direct messages. For this, we need to start the RTM (Real-Time Messaging) process and open a web socket to receive the events. Fortunately, our Slack library implements this functionality.
s, err := slack.New(slack.SetToken(…))
in := make(chan *slack.Message)
info, err := s.RTMStart(“Your URL”, in, nil})
Now, you can just start a receive loop on the “in” channel for all messages (notice that everything is a message so expect a lot of traffic here).
The somewhat interesting problem with using RTM is of multiple users adding the integration. Since multiple users can be part of the same channel, the integration will be receiving the same message multiple times (one for each user that added the integration and is part of the channel). In a distributed architecture (which is a must to scale), there must be some sort of a choke point to dedup these messages to make sure you are not replying to a single message multiple times. Luckily for us, most of the messages we want to respond to are of type “message” and have a timestamp that is unique in the channel and can be used as an ID for dedup purposes.
In my next blog post, I will talk about our micro-service architecture using Kubernetes on Google container services (GKE).