View on GitHub

Automation for AWS VPC management, built on top of boto.

Using AWS Api Gateway to collect HPKP Reports

So the Chrome team at Google recently announced they’d enabled HPKP, and sort of buried in the announcement is reporting. What this means is that Chrome can now report back to websites when it spots problems with a pinned site. Typical problems would include key mismatches or possibly malformed HPKP headers. It also means that before biting the bullet and enabling ‘hard’ pinning for your production site, you can enable a ‘report-only’ mode that inspects keys and generates reports, but doesn’t actually deny access to sites that have problems with their HPKP configuration.

I’ve really been wanting to check out AWS API Gateway for a few weeks now, but haven’t really come up with an interesting application that I could build as a small, self-contained exercise. When I saw tweets about HPKP reporting today everything clicked & I realized it would be a perfect ‘test drive’ of API GW.

One Small Bummer

When I started sketching out how to build the project, I ran into a little surprise. Boto doesn’t support API GW at all yet. That means that for now theres a bit of tedious pointing and clicking required for the setup. On the bright side this would be the perfect opportunity to learn more about boto internals and maybe implement some basic API GW support in veep using some of the lower-level boto code that communicates with the AWS API.

Requirements

I’ll be using a couple of Python libraries to automate configuration where I can. You’re probably familiar with boto, and on top of boto I’ve written veep, which adds a few convenience functions here and there. Here’s what we’ll need to build:

Using SNS:

Using IAM:

In Part 2:

I’ll be sharing code that will work with cutting and pasting, but you’ll likely want to have some familiarity with at least SNS to configure more advanced ways to handle reports than firing off an email per report. In a production environment, it might make sense to subscribe a Lambda function to the SNS topic, for example, and persist reports in a DynamoDB table.

Creating an SNS Topic

In SNS, a ‘Topic’ is an endpoint that collects notifications. These events are then sent to any ‘Subscriptions’ associated with the topic. SNS supports a bunch of subscription types that turn incoming events into emails, text messages, http calls or messages sent into an SQS queue. To keep things simple, we’ll create a topic with a single email subscription.

import veep
c = veep.SNS.connect_to_region('us-west-2') # (or choose your favorite region)
topic = c.create_topic('hpkp-reports')
topic.subscribe('email', 'your@email.address')

The subscribe call will return “pending confirmation”. Check your email, you should receive a confirmation message from AWS containing an URL. You can either click the confirmation URL, or extract the ‘Token’ from the URL and confirm by running:

topic.confirm('175b...a really long string...85ee5')

(Use the token from the email, mine won’t work for you!)

Your SNS topic and subscription are all set up & ready to go!

Creating the IAM Roles

We need two roles: the first defines the permissions web browsers get when they connect to POST their hpkp reports. The second defines the permissions that API GW has when it talks to SNS. Both are very similar- we just specify what AWS service we’re allowing to assume the role, and what actions we’re permitting the service to run.

Method Caller Role

In API GW, each function callable from an http endpoint is called a “Method”. We need to tell API GW who’s allowed to call this method, and what permissions these callers get.

statement = veep.IAM.Statement(sid='HPKP-allow-post', actions=['apigateway:POST'], resources=['arn:aws:execute-api:us-west-2:*:*'])
statement.pop('Principal')  # (we don't need a principal here)
policy = veep.IAM.Policy(id='HPKP-caller', statements=[statement])

The resource in the policy is a placeholder; once we create our API we can replace it with the correct ARN. Next is the policy that allows API GW to process this request as your account:

assume_statement = veep.IAM.Statement(principal={'Service': 'apigateway.amazonaws.com'}, sid='AssumeRole', actions=['sts:AssumeRole'])
assume_statement.pop('Resource')  # (don't need a resource here)
assume_policy = veep.IAM.Policy(id='HPKP-assume', statements=[assume_statement])

Next, we create the role and attach our policies:

iamconn = veep.IAM.connect()
role = iamconn.create_role('HPKP-Caller', path='/service/', assume_role_policy_document=str(assume_policy))
role.put_policy(policy)

Method Target Role

Half way there! The next role is what API GW uses when it sends events into SNS. First lets create the policy allowing API GW to access SNS:

statement = veep.IAM.Statement(sid='HPKP-allow-publish', actions=['SNS:Publish'], resources=[topic.arn])
statement.pop('Principal')  # (we don't need a principal here)
policy = veep.IAM.Policy(id='HPKP-to-SNS', statements=[statement])

We can re-use the previous assume_policy we built for the method caller role here, too. Now we create the role and attach our policies:

role = iamconn.create_role('HPKP-SNS', path='/service/', assume_role_policy_document=str(assume_policy))
role.put_policy(policy)

Your IAM role is all set! Save its ARN for use later configuring API Gateway:

print role.arn

You should see an arn like:

arn:aws:iam::123456789012:role/service/HPKP-SNS

Save this ARN, we’ll need it in part 2 when we build our API.

Take a break!

Congratulations, you’ve built out the foundations that allow API Gateway to accept reports from the internet, and pass them on to SNS for processing, and all it took was a bit of cut and paste!