from slack import WebClient
import boto3
import requests
+import hashlib
+import hmac
ssm = boto3.client('ssm')
+
+response = ssm.get_parameter(Name='SLACK_SIGNING_SECRET', WithDecryption=True)
+slack_signing_secret = bytes(response['Parameter']['Value'], 'utf-8')
+
response = ssm.get_parameter(Name='SLACK_BOT_TOKEN', WithDecryption=True)
slack_bot_token = response['Parameter']['Value']
slack_client = WebClient(slack_bot_token)
+def error(message):
+ """Generate an error response for a Slack request
+
+ This will print the error message (so that it appears in CloudWatch
+ logs) and will then return a dictionary suitable for returning
+ as an error response."""
+
+ print("Error: {}.".format(message))
+
+ return {
+ 'statusCode': 400,
+ 'body': ''
+ }
+
+def slack_is_valid_request(slack_signature, timestamp, body):
+ """Returns True if the timestamp and body correspond to signature.
+
+ This implements the Slack signature verification using the slack
+ signing secret (obtained via an SSM parameter in code above)."""
+
+ content = "v0:{}:{}".format(timestamp,body).encode('utf-8')
+
+ signature = 'v0=' + hmac.new(slack_signing_secret,
+ content,
+ hashlib.sha256).hexdigest()
+
+ if hmac.compare_digest(signature, slack_signature):
+ return True
+ else:
+ print("Bad signature: {} != {}".format(signature, slack_signature))
+ return False
+
def turbot_lambda(event, context):
"""Top-level entry point for our lambda function.
- This parses the request and arguments and farms out to supporting
- functions to implement all supported slash commands."""
+ This function first verifies that the request actually came from
+ Slack, (by means of the SLACK_SIGNING_SECRET SSM parameter), and
+ refuses to do anything if not.
+
+ Then this parses the request and arguments and farms out to
+ supporting functions to implement all supported slash commands.
+
+ """
+
+ signature = event['headers']['X-Slack-Signature']
+ timestamp = event['headers']['X-Slack-Request-Timestamp']
+
+ if not slack_is_valid_request(signature, timestamp, event['body']):
+ return error("Invalid Slack signature")
body = parse_qs(event['body'])
command = body['command'][0]
if (command == "/rotlambda" or command == "/rot"):
return rot_slash_command(body, args)
- error = "Command {} not implemented.".format(command)
-
- print("Error: {}".format(error))
-
- return {
- 'statusCode': 404,
- 'body': error
- }
+ return error("Command {} not implemented".format(command))
def rot_slash_command(body, args):
"""Implementation of the /rot command