Permalink
Find file Copy path
401cdc2 Nov 10, 2018
2 contributors

Users who have contributed to this file

@martindekov @alexellis
194 lines (137 sloc) 6.08 KB

Lab 11 - Advanced feature - Trust with HMAC

Before starting this lab create a new folder

mkdir -p lab11 \
   && cd lab11

also make sure your faas-cli version is 0.7.4 or above with the following command:

$ faas-cli version

What is HMAC

Without any form of authentication or trust our functions may be exposed to anyone who can guess their URL. If our functions are accessible on the Internet or the local network then they could be invoked by a bad actor. By default functions respond to any request. However, if we want to control access to functions we can use Hash-based Message Authentication Code (HMAC) to validate the source of information.

From alexellis/hmac:

HMAC uses a symmetric key that both sender/receiver share ahead of time. The sender will generate a hash when wanting to transmit a message - this data is sent along with the payload. The recipient will then sign payload with the shared key and if the hash matches then the payload is assumed to be from the sender.

This way we close our functions from being invoked with invalid or even dangerous information.

Using HMAC

We will use the --sign flag provided by faas-cli to send a header containing the hashed message created with the shared key which we send with the --key flag.

Note: Both --sign and --key must be present.

Let's first inspect what the flag does by deploying the env function which will print all of the environmental variables accessible inside the function:

$ faas-cli deploy --name env --fprocess="env" --image="functions/alpine:latest"
  • Invoke the function without --sign flag:
$ echo "The message" | faas-cli invoke env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=d2c1a2cb20c2
fprocess=env
HOME=/root
Http_X_Call_Id=b84947c6-2970-4fcf-ba3b-66dde6943999
Http_X_Forwarded_For=10.255.0.2:34974
Http_X_Forwarded_Host=127.0.0.1:8080
Http_Content_Length=0
Http_Accept_Encoding=gzip
Http_Content_Type=text/plain
Http_User_Agent=Go-http-client/1.1
Http_X_Start_Time=1538725657952768349
...
  • Invoke the function again but this time with --sign flag:
$ echo -n "The message" | faas-cli invoke env --sign=HMAC --key=cookie
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=d2c1a2cb20c2
fprocess=env
HOME=/root
Http_User_Agent=Go-http-client/1.1
Http_Content_Length=0
Http_Accept_Encoding=gzip
...
Http_Hmac=sha1=9239edfe20185eafd7a5513c303b03d207d22f64
...

We see the HMAC being provided as the environmental variable Http_Hmac. The generated value is the hash of The message after being signed with the key cookie, which is then prepended with the hashing method sha1.

HMAC in action

For our purpose we are going to create a new Python 3 function. Let’s call it hmac-protected:

$ faas-cli new --lang python3 hmac-protected --prefix="<your-docker-username>"

Add payload-secret which will serve as the key that will hash the payload.

Create payload-secret like we did in lab10:

$ echo -n "<your-secret>" | docker secret create payload-secret -

Note: Remember the string you put in place of <your-secret>

Our hmac-protected.yml should look like:

provider:
  name: faas
  gateway: http://127.0.0.1:8080

functions:
  hmac-protected:
    lang: python3
    handler: ./hmac-protected
    image: <your-docker-username>/hmac-protected:latest
    secrets:
      - payload-secret

Replace the content of the handler.py with the following code:

import os, hmac, hashlib

def validateHMAC(message, secret, hash):

    # GitHub and the sign flag prefix the hash with "sha1="
    receivedHash = getHash(hash)

    # Hash message with secret
    expectedMAC = hmac.new(secret.encode(), message.encode(), hashlib.sha1)
    createdHash = expectedMAC.hexdigest()

    return receivedHash == createdHash

def getHash(hash):
    if "sha1=" in hash:
        hash=hash[5:]
    return hash

def handle(req):
    # We receive the hashed message in form of a header
    messageMAC = os.getenv("Http_Hmac")

    # Read secret from inside the container
    with open("/var/openfaas/secrets/payload-secret","r") as secretContent:
        payloadSecret = secretContent.read()

    # Function to validate the HMAC
    if validateHMAC(req, payloadSecret, messageMAC):
        return "Successfully validated: " + req
    return "HMAC validation failed."

The source code is also available at hmac-protected/hmac-protected/handler.py

  • Build, push and deploy the function in one command by using faas-cli up :
$ faas-cli up -f ./hmac-protected.yml

Invoke function

We will invoke the function by sending two values:

  • The normal request message

  • A header containing the hash of the same message, when signed with the value of the --key flag

On receipt of the request, the function will use payload-secret to sign the request message in the same way as the sender did. This will create a second HMAC which is compared to the transmitted header value, 'Http-Hmac`.

Here we compare the generated and received hashes:

...
    if hmacDigest == cleanHash:
        return True
    return False
...
  • Invoke the function with the flag:
$ echo -n "This is a message" | faas-cli invoke hmac-protected --sign hmac --key=<your-secret>

Check the response and confirm it matches the conveyed message. In our case we should get:

Successfully validated: This is a message
  • Invoke the function with the wrong --key and check the failure message:
$ echo -n "This is a message" | faas-cli invoke hmac-protected --sign hmac --key=wrongkey
HMAC validation failed.

As a follow-up task you could apply HMAC to secure your endpoint on issue-bot from lab 5

You have completed the labs and can return to the main page.