Secure your SQS queues at rest and in transit

Posted: | Tags: cloud aws til

AWS allows you to enable server-side encryption (SSE) for you data at rest in your SQS queue. Disabling this option also has an effect on your encryption in transit as well. From the SQS documentation:

All requests to queues with SSE enabled must use HTTPS and Signature Version 4.

In other words disabling SSE also means you can now communicate to the SQS without TLS.

I wrote a Python script to send a message to a queue using the HTTP API and botocore without using the higher level abstractions of boto3. Note the endpoint used uses http and not https.

After creating a standard SQS queue with SSE disabled the script has no issues sending the message to the queue. We receive the MessageId (below) which we can confirm by polling the queue.

{
    "MD5OfMessageBody": "981b7dae8fee37caab68009b00f4c25f",
    "MessageId": "6ffadb14-adeb-401f-8d80-8082da9b5d28"
}

Repeating the test with the same SQS queue but with SSE enabled1 returns the following error:

{
    "__type": "com.amazonaws.sqs#InvalidSecurity",
    "message": "All requests to this queue must use HTTPS and SigV4."
}

This confirms the excerpt from the documentation I quoted above. Now that SSE is enabled we must use HTTPS. Changing http to https in the endpoint allows us to successfully send messages to the queue again.

The SQS documentation (as well as a few AWS blog posts) cover the use of access policies. The DenyUnsecureTransport policy statement can be used to enforce TLS connections to the queue. The policy denies any non-TLS connections by checking the aws:SecureTransport property.

With this policy statement applied to our queue and disabling SSE again, running the test now returns a new error message:

{
    "__type": "com.amazon.coral.service#AccessDeniedException",
    "Message": "User: arn:aws:iam::<account_id>:user/<user> is not authorized to perform: sqs:sendmessage on resource: arn:aws:sqs:<region>:<account_id>:<queue_name> with an explicit deny in a resource-based policy"
}

The access policy works!

Takeaways

If you haven’t already enabled SSE on your queue, do it. Regardless if you have SSE enabled or not it doesn’t hurt to add the DenyUnsecureTransport policy statement to your access policy.

You can apply the policy in several ways:

Python script

from botocore import crt
import botocore.session
import requests
from botocore.awsrequest import AWSRequest
from botocore.credentials import Credentials
import json

account_id = ''
queue_name = ''
region = ''

session = botocore.session.Session()
signer = crt.auth.CrtS3SigV4Auth(session.get_credentials(), 'sqs', region)
endpoint = f'http://sqs.{region}.amazonaws.com'
headers = {
    'Content-Type': 'application/x-amz-json-1.0',
    'X-Amz-Target': 'AmazonSQS.SendMessage',
    'Connection': 'Keep-Alive'
}

data = {
    "QueueUrl": f"http://sqs.{region}.amazonaws.com/{account_id}/{queue_name}/",
    "MessageBody": "Just a test message",
}
data = json.dumps(data)

request = AWSRequest(method='POST', url=endpoint, data=data, headers=headers)
request.context['payload_signing_enabled'] = True
signer.add_auth(request)

prepped = request.prepare()
response = requests.post(prepped.url, headers=prepped.headers, data=data)
print(response.text)

  1. I used SSE-SQS. ↩︎

  2. Disclaimer: the linked aws-sample is co-authored by me. ↩︎


Related ramblings