Table of contents

Secure web hooks

Deep Security Smart Check gives you an easy way to ensure that the events that your web server is receiving are coming from Deep Security Smart Check and have not been modified in transit.

When Deep Security Smart Check calls back to your server, it computes a signature (HMAC-SHA-256) for the event using the secret as the key and puts the signature value in the X-Scan-Event-Signature HTTP header.

The request that your server gets look like this:

POST https://server.example.com/ HTTP/1.1
X-Scan-Event-Signature: {signature}
Content-Type: application/json

{
    "event": "scan-completed",
    "source": "/api/webhooks/7a2f1d8c-7780-41d2-821b-7230005d4be8",
    "timestamp": "2018-05-01T00:00:00Z",
    "scan": {
        ...
    }
}

The value of the scan attribute is the scan details object as detailed in the API documentation.

Create the web hook

When you create the web hook, set the secret attribute to a value that only Deep Security Smart Check and your server will know. In this example, we're using correct horse battery staple as our shared secret, but you should use a long string of random characters.

POST https://dssc.example.com/api/webhooks HTTP/1.1
Authorization: Bearer {token}
Content-Type: application/json

{
  "name": "secure webhook",
  "hookUrl": "https://server.example.com/",
  "active": true,
  "events": [ "*" ],
  "secret": "correct horse battery staple",
}

Calculate and check the signature

To check the signature, compute the HMAC-SHA-256 signature of the payload using the secret as the key and compare it with the value in the X-Scan-Event-Signature HTTP header. In this example, the secret is stored in the HMAC_SECRET environment variable.

export HMAC_SECRET="correct horse battery staple"
# Get the secret from the environment -- don't store it in code!
secret = os.environ.get('HMAC_SECRET')

# Get the size of the payload
content_length = int(self.headers.get('Content-Length', 0))
if content_length == 0 or content_length > MAX_PAYLOAD_SIZE:
    raise InvalidPayloadSizeException()

# Read the full event payload from the request
payload = self.rfile.read(content_length)

# Calculate the HMAC-SHA-256 signature of the payload using the secret as the HMAC key
actual = hmac.new(bytes(secret, 'utf-8'),
            msg=bytes(payload),
            digestmod=hashlib.sha256).hexdigest()

# Get the expected HMAC-SHA-256 signature value from the request header
expected = self.headers.get('X-Scan-Event-Signature', '')

# Use compare_digest to reduce vulnerability to timing attacks
if not hmac.compare_digest(actual, expected):
    # the event has been tampered with or is not from the right sender

(full working example)

Send a test event

You can use the web hook ping API endpoint to send a test event to your web hook:

POST https://dssc.example.com/api/webhooks/7a2f1d8c-7780-41d2-821b-7230005d4be8/ping HTTP/1.1
Authorization: Bearer {token}

Deep Security Smart Check will send a sample event that looks similar to this:

POST https://server.example.com/ HTTP/1.1
X-Scan-Event-Signature: {signature}
Content-Type: application/json

{
    "event": "ping",
    "source": "/api/webhooks/7a2f1d8c-7780-41d2-821b-7230005d4be8",
    "timestamp": "2018-05-01T00:00:00Z",
}

and you can confirm that your validation code works as expected.