6

I am trying to have a Github Webhook launch an AWS Lambda I have. The best way I can figure out how to do that is to use AWS API Gateway, the issue is security.

Github Webhooks will only send a secret with the POST call.

I can't find any way to have AWS API Gateway to verify this signature. Or where I can add this functionality.

I assume I can write an AWS Lambda Authorizer. But this is a lot of code in different places, starting to see the need for serverless framework.

Any easier setup within AWS I do not know about for this?

Kyle Calica-St
  • 2,629
  • 4
  • 26
  • 56

2 Answers2

5

Came here because I was trying to integrate a Github webhook with AWS lambda and ran into the same problem as the OP. At the time of writing I believe the best solution is to include verification code in the main lambda, as others have suggested.

On the AWS Computer Blog from September 2017:

Enhanced request authorizer Lambda functions receive an event object that is similar to proxy integrations. It contains all of the information about a request, excluding the body.

Source: Using Enhanced Request Authorizers in Amazon API Gateway (amazon.com)

You can't perform HMAC as recommended by Github, because AWS authorizer lambdas don't give you access to the body of the HTTP request, which you need in order to compare digests.

This is a shame, because HMAC seems to be a pretty standard way of securing an endpoint that responds to a webhook. See for example this blog post, Webhooks do’s and dont’s: what we learned after integrating +100 APIs (restful.io). Twitter and Stripe do something similar:

To make the approach described above work, if you're using API Gateway you'll need to make sure that the header that contains the hash signature is forwarded as part of the event argument to the lambda. To do that, follow these instructions: How do I pass custom headers through Amazon API Gateway to an AWS Lambda function using custom Lambda integration for further processing? (amazon.com)

Oscar Barlow
  • 100
  • 7
  • Actually, I was just at a conference talked to the AWS Authorizer product manager, and he was disappointed to learn he himself couldn't do it due to exactly what you said here (no access to the payload to hash against), his response was to do it in the Lambda. Kinda sad you can't use their system to their best practices. This was an amazing response and you earned this answer! Thank you, hopefully we help many others until AWS thinks of a better way to handle this. – Kyle Calica-St Nov 08 '19 at 18:17
4

I couldn't find a way to do this with API Gateway. I validated within the LAMBDA using (Python).

High level overview : Calculate HMAC signature with GITHUB_SECRET then compare to the signature passed from Github.

You can obviously simplify, intentionally verbose for readability. There may be better ways, but I couldn't find one.

Make sure your Webhook is configured for application/json. Hopefully this helps someone else.

import logging
import json
import hmac
import hashlib
import re
from urllib.parse import unquote

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

GITHUB_SECRET = 'SECRET FROM GITHUB CONSOLE'


def lambda_handler(event, context):
    logger.info("Lambda execution starting up...")

    incoming_signature = re.sub(r'^sha1=', '', event['headers']['X-Hub-Signature'])
    incoming_payload = unquote(re.sub(r'^payload=', '', event['body']))
    calculated_signature = calculate_signature(GITHUB_SECRET, incoming_payload.encode('utf-8'))

    if incoming_signature != calculated_signature:
        logger.error('Unauthorized attempt')
        return {
            'statusCode': 403,
            'body': json.dumps('Forbidden')
        }

    logger.info('Request successfully authorized')

    # do stuff in Lambda

    return {
        'statusCode': 200,
        'body': json.dumps(f'Work in progress')
    }


def calculate_signature(github_signature, githhub_payload):
    signature_bytes = bytes(github_signature, 'utf-8')
    digest = hmac.new(key=signature_bytes, msg=githhub_payload, digestmod=hashlib.sha1)
    signature = digest.hexdigest()
    return signature
  • thanks for sharing this! yup I actually have what you wrote for verifying in a separate lambda but I need it to return policy and a principal Id, I'm very confused on what the AWS policy should I be sending in? Policy to okay the API gateway, or to okay the actual lambda I want background functionality on. – Kyle Calica-St Sep 30 '19 at 20:02
  • 1
    That was what I was working on next. The above assumes you would have the authorization done inline, then run the lambda code (all within the same lambda). I'll share when I get some time to get the lambda to return the IAM policy and a principal identifier. – Justin Daines Oct 01 '19 at 13:05
  • getting an error here :( [ERROR] TypeError: expected string or bytes-like object Traceback (most recent call last): File "/var/task/lambda_function.py", line 21, in lambda_handler incoming_payload = unquote(re.sub(r'^payload=', '', event['body-json'])) File "/var/lang/lib/python3.8/re.py", line 208, in sub return _compile(pattern, flags).sub(repl, string, count) – raitisd Nov 20 '19 at 12:21