Cloud Security

Send message to encrypted SQS queue in another AWS account in same organizational unit

AWS Organizations help you create and manage an AWS account for each major use case your organization operates in the Cloud. But once you’ve separated use cases into different accounts, you may need to stitch them back together in a limited way so that a service operating in one account can communicate in another. For example, services running in your public facing ‘web’ account may need to talk to internal services running in a ‘shared’ account. You may also want to restrict communication between services to specific organizational units (OUs) within your AWS organization.

Let’s accomplish that using the AWS Simple Queue Service.  We’ll raise the level of difficulty a bit by adding a couple requirements:

  • the SQS queue must encrypt data at rest with a KMS customer managed encryption key
  • any service running in the ‘Dev’ organizational unit of the AWS organization should be able to publish to the queue in the Dev ‘shared’ account, but not the production equivalent

It seems like this should be simple, but it’s not.

Consider an AWS organization that looks like:

A solution that satisfies the requirements looks like this:

The architecture looks simple enough.  The tricky bits are the resource policies.  We need need to work out KMS and SQS resource policies that permit principals in the publisher AWS account to publish messages to a queue in the dev-shared account, as long as the publisher’s IAM role or user is in the Dev OU.

Let’s start with a summary of findings and gotchas:

  • The KMS resource policy works with the aws:PrincipalOrgPaths policy condition
  • The publisher must have permission for kms:Decrypt to send a message
  • The SQS resource policy works with aws:PrincipalOrgId the policy condition, but not aws:PrincipalOrgPaths

Getting Started

In the dev-shared AWS account, create a customer managed KMS key named shared-app. Then create an SQS queue named shared-app-work that encrypts all data with the shared-app key. Don’t encrypt the queue with the default KMS key for SQS because we’ll be customizing the policy for this specific use case. You can use whatever KMS and SQS policy you want for now as long as you retain ability to administer the policies.

Now we need to create and attach KMS and SQS resource policies to permit the cross-account publish.

Organization IDs, OUs, Accounts, Oh my!

First, we need to understand the AWS org and OU structure so that we can specify organization IDs and OU paths in the resource policies.

The AWS organization resource identifiers may look like a bit of a jumble, but the prefixes are different to help you keep them straight. Here’s a resource prefix decoder ring:

  • Organization id: o, e.g.o-abc123
  • Root OU id r, e.g. r-wxyz
  • Organizational unit ids are prefixed by ou and the root OU id’s value (ou-wxyz), e.g. the B2B Dev OU ID is ou-wxyz-km23jDEV

AWS’ Understand the AWS Organizations Entity Path helpfully explains the details of constructing an entity path. Briefly, an AWS Organizations entity path is constructed from the AWS organization id, the root OU id, and all of the OU ids in the path to the OU of interest.

This pseudo code builds an organization entity path:

org_entity_path = "${aws_org_id}/${root_ou_id}"
for ou_id in ous_in_path:
  org_entity_path += "/${ou_id}" 

The organization entity path to the Dev OU in the example B2B business unit is:

o-abc123/r-wxyz/ou-wxyz-jklm1DEV/ou-wxyz-km23jDEV

Append /* so the path matches child OUs and AWS accounts:

o-abc123/r-wxyz/ou-wxyz-jklm1DEV/ou-wxyz-km23jDEV/*

Now let’s build AWS security policy statements that use the organization id and paths as conditions to allow access to the encryption key and queue resources.

Create KMS Key Policy

We can allow limited access to the encryption key used by the SQS queue using a carefully crafted key policy statement. We will limit access using a statement condition that only grants access to the key when the aws:PrincipalOrgPaths matches the B2B Dev OU’s organization entity path.

This KMS key resource policy allows principals in the Dev OU to encrypt and decrypt data with the encryption key:

{

  "Version": "2012-10-17",
"Id": "Share encrypted data across B2B dev OU",
"Statement": [
{
"Sid": "Allow any principal in the b2b dev OU to encrypt data",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey"
],
"Resource": "*",
"Condition": {
"ForAllValues:StringLike": {
"aws:PrincipalOrgPaths": "o-abc123/r-wxyz/ou-wxyz-jklm1DEV/ou-wxyz-km23jDEV/*"
}
},
"StringEquals": {
"kms:ViaService": [
"sqs.amazonaws.com",
"sqs.us-east-1.amazonaws.com"
]
}
},
{
"Sid": "Allow administration of key",
# ... snip ...
}
]
}

The statement allows any AWS principal to encrypt, decrypt, or generate a data key with this customer managed KMS key, but only if the principal matches meets this condition:

"ForAllValues:StringLike": {
  "aws:PrincipalOrgPaths": "o-abc123/r-wxyz/ou-wxyz-jklm1DEV/ou-wxyz-km23jDEV/*"
}

This condition restricts the principals that can use the key to those in the B2B Dev OU.

Further, the principal is only allowed to use the key if the request was made on their behalf by SQS running in us-east-1:

"StringEquals": {
  "kms:ViaService": [
    "sqs.amazonaws.com",
    "sqs.us-east-1.amazonaws.com"
   ]
} 

The need to allow kms:Decrypt is a bit curious given we only want to allow collaborating services to publish messages into the shared account. The answer lies in SQS’ use of envelope encryption (docs):

The security of your encrypted data depends in part on protecting the data key that can decrypt it. Amazon SQS uses the CMK to encrypt the data key and then the encrypted data key is stored with the encrypted message.

SQS acts on behalf of the publisher and must be able to generate a data key, encrypt the published message and the data key, then store the ciphertexts. Next, SQS must decrypt the data key to deliver the message. This causes lots of confusion, even in the AWS CDK.

One implication of this is that we should restrict cross-account access of the queue.

Create SQS Queue Policy

We’d like to allow services in the Dev OU to publish messages to the shared-app-work queue using SQS:SendMessage.

Allowing publishers to only use SQS:SendMessage in the queue’s resource policy makes the access write-only, which should address concerns of being able to call kms:Decrypt with the shared-app key.

But there’s another problem. SQS does not support aws:PrincipalOrgPaths in security policy statement conditions. SQS does support the aws:PrincipalOrgId condition, so let’s use that.

This SQS queue resource policy statement allows the entire AWS Organization to send messages to the queue: 

{
  "Version": "2008-10-17",
  "Id": "Share queue across org",
  "Statement": [ {
    "Sid": "Allow all principals in org to send to this queue",
     "Effect": "Allow",
     "Principal": {
      "AWS": "*"
     },
     "Action": "SQS:SendMessage",
     "Resource": "*",
     "Condition": {
       "ForAllValues:StringEquals": {
         "aws:PrincipalOrgId": "o-abc123"
       }
     }
    },{
      "Sid": "Allow administration of queue",
      # ... snip ...
    }
  ]
} 

Now let’s prove this complicated machinery works.

Prove it works

Try sending a test message using an IAM principal in a publisher account using the AWS cli using a command like:

aws sqs send-message \
  --queue-url https://sqs.us-east-1.amazonaws.com/${SHARED_ACCOUNT_ID}/shared-app-work \
  --message-body "Hello from publisher account!"

When successful you should see a message with random identifiers printed to the console like:

8cfc2854ff2c992bb2c0af92ab76f890	7f59b8d7-7e88-4145-8ce6-eb710f2556e0

Summary

We accomplished the goal of allowing principals in the B2B dev OU to send messages to an SQS queue in a separate shared account, regardless of which Dev account they are in. This was done by combining several AWS security building blocks.

  1. Configured the shared-app-work queue to encrypt its data at rest using a shared-app customer managed key.
  2. Created a KMS key policy that allows any IAM principal in the dev OU to encrypt data using the shared-app key using the aws:PrincipalOrgPaths statement condition.
  3. Created an SQS queue policy that allows any principal in the AWS organization to send messages to the queue using the aws:PrincipalOrgId condition

Together, these resources and security policies control access to the shared queue and the data in it. IAM principals in the dev account can send messages into an SQS queue in another account in the Dev OU, but not access other data in that account or outside of the Dev OU.

Contact Us

Please contact us and we’ll be happy to answer any questions you may have and discuss AWS security problems with you.