Hacking, Coding and Gaming | @[email protected]

Recently I wanted to automate some manual checks I was having to perform on client sites, and make these checks accessible to other colleagues - the solution I settled on was using a Slack bot to trigger an AWS Lambda function that runs PhantomJS to perform the checks. Here's a quick crash course on how to set it up (you'll need some AWS knowledge or do some Googling).

I've created a sample code base available at https://github.com/hypn/lambda-slack-bot to build on, using NodeJS for the the Lambda function (because it starts up so quickly - but other supported languages could be used). It defines a "handler" function (that receives the Lambda request) which executes PhantomJS and tells it the file we want it to run ("title.js" in this case, which just returns the webpage's title).

Yes, we really can (and AWS lets us) run custom binaries in AWS Lambda: https://aws.amazon.com/blogs/compute/running-executables-in-aws-lambda/. I had some dependency / shared library issues getting PhantomJS to run, but eventually found a statically compiled version that worked (it's checked in to the repo above). Slack can't run AWS Lambda functions directly, so we'll also need to setup an API Gateway endpoint for it to hit, which will call the Lambda function.

1. Setting up AWS Lambda:

  • checkout the repo above and "npm install" to get all the dependenices
  • set the correct permissions to ensure Lambda can run your .js code, execute PhantomJS, and PhantomJS can execute "title.js":
    chmod 644 *.js
    chmod +x phantomjs
  • zip up all the files somewhere convenient: "zip -r ~/lambda-slack-bot.zip ."
  • create an NodeJS AWS Lambda function (I named mine "lambda-slack-bot")
  • choose to "Upload a .zip file" for the code, and upload the .zip created above
  • create a Lambda test for the function with the below JSON and make sure it works:
    {
        "text": "http://www.example.com",
        "token": "hunter2"
    }
  • then click "Test" and Lambda should give you a response like this:
    {
      "response_type": "in_channel",
      "text": "Response from Lambda: \n```Example Domain\n```"
    }

Setting up AWS Gateway:

  • create a new API Gateway (don't put "internal" functionality on an existing public API gateway!)
  • create a new resource in that gateway (I called mine "slack-bot")
  • create a new method for that Resource as GET
  • select your lambda region and lambda function ("lambda-slack-bot" in my case)
  • modify the "Integration Request" and set the "Body Mapping Template" to content type "application/json" and template to:
    {
        "token": "$input.params('token')",
        "text": "$input.params('text')",
        "user_name": "$input.params('user_name')",
        "response_url": "$input.params('response_url')"
    }

(these fields are automatically sent by Slack so I didn't bother renaming them in the JSON payload, "text" is everything the Slack users typed after the Slack "slash command" which is what we'll use for specifying the URL to run PhantomJS against)

    {
        "response_type": "in_channel",
        "text": "Response from Lambda: \n```Example Domain\n```"
    }

Setting up Slack:

  • you'll need to be an administrator of your Slack account, or have permission to add integrations/apps, and if you're on a free plan you need to have capacity to add another integration
  • navigate to https://YOURSUBDOMAIN.slack.com/apps/A0F82E8CA-slash-commands and click on "Add configuration" to add a "slash command" to your Slack
  • choose a command to use (I went with "/slack-bot")
  • set the URL for Slack to call (the one used above with postman, eg: https://abc1234def.execute-api.eu-west-1.amazonaws.com/dev/slack-bot)
  • set the method to GET
  • copy the "Token" value, modify my code's "index.js" and replace "hunter2" in the "TOKEN" line at the top of the file with this value (it's a unique identifier Slack will send to your endpoint)... then zip up your code and uploaded to it AWS again (sorry), and consider putting the token in your Lambda test and making sure it works and/or changing the token in Postman
  • you can configure the rest of the fields and needed
  • run "/slack-bot http://www.example.com" (or whatever your chosen command was) and you should get a response back:
    Slack Bot APP [10:57 AM]
    Response from Lambda:
        Example domain

While that technically has you up and running, there's probably a few things you should know:

  • Slack slash commands require your URL to reply within 3 seconds, otherwise you get a "Darn - that slash command didn't work (error message: Timeout was reached)" error back, the way I worked around this (but didn't include in my repo or this blog post due to complexity) was to have the Lambda function Slack calls use the "AWS-SDK" to execute another Lambda function asynchronously (which runs PhantomJS)), and reply immediately saying the job was queued. Slack provides a "response_url" (which you may notice the gateway mapping above) which allows you to post additional responses to the initial trigger... the 2nd Lambda (that actually runs PhantomJS) simply posts its response to that URL once done :)
  • The "response_type" in the returned payload tells Slack whether the response should only be visible to the person running the command, or anyone else in the chat/channel - in my case it was the latter. You can read more about this, and formatting settings, at https://api.slack.com/slash-commands
  • If you get a "Missing Authentication Token" error back in Postman, or Slack complains about an HTTP 403 response, make sure you have the correct API Gateway URL and method - you can't make GET requests to an endpoint if you created it as a POST in Gateway
  • The reason I went with the "GET" method instead of POST (Slack's default) is because I didn't want to fight with the form-encoded payload they send (though apparently API Gateway turns it in to JSON automatically... it didn't work for me)