AWS IAM Roles Anywhere with MacOS Keychain

Paul Schwarzenberger
9 min readSep 19, 2023

--

In July 2022 AWS released AWS IAM Roles Anywhere introducing certificate authentication as a new way of connecting from on-premise servers or mobile laptops 🎉 This was followed up in July 2023 with support for operating system certificate stores.

Integration with operating system certificate stores such as MacOS keychain improves the security of the solution, with the certificate private key protected by operating system controls such as MacOS TouchID.

That prompted me to try out AWS IAM Roles Anywhere with MacOS keychain. I started with the excellent Medium article by Roy Ben Yosef: Calling AWS Services from Your On-Premises Servers Using IAM Roles Anywhere. The main additions provided here are:

  • Python code to create test Certificate Authority and client certificate
  • Integration with MacOS Keychain

Contents

  1. Certificate Authority
  2. Create Trust Anchor
  3. Create IAM Role
  4. Create Roles Anywhere Profile
  5. Test Client Certificate and Key in File System
  6. Configure MacOS Keychain
  7. Test Certificate in MacOS KeyChain
  8. Security Considerations

1. Certificate Authority

AWS IAM Roles Anywhere can be set up with AWS Private CA, however the pricing for this service is significant. For test purposes I initially tried using OpenSSL using the script at the end of Ben’s article, however I encountered errors attempting to run this on MacOS.

I therefore wrote some Python code using the well-respected Cryptography module.

⚠️ WARNING: the code referenced below is only for test purposes, and not suitable for a production implementation, because the private key of the Certificate Authority isn’t secured in hardware. With this code, private keys are held locally on disk, and loss of these could potentially lead to takeover of your AWS Accounts.

Clone Repository

All the code referenced in this article is in a GitHub repository:

git clone https://github.com/Celidor/aws-iam-roles-anywhere.git
cd aws-iam-roles-anywhere

Set up Python virtual environment

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Build test certificate authority and issue client certificate

  • create self-signed Certificate Authority
  • create client certificate
  • includes required certificate extensions for AWS IAM Roles Anywhere
python main.py

Inspect the certificates and keys you’ve just created at crypto/ca and crypto/client

2. Create Trust Anchor

  • Log in to your AWS account
  • Search for IAM Roles Anywhere
  • This will take you to IAM, Roles, Roles Anywhere
  • Create a trust anchor
  • Set a name for the trust anchor, e.g. test-ca
  • Select External certificate bundle
  • Paste in the contents of certs/ca/test-ca.pem
  • Leave notification defaults
  • Add tags if desired
  • Press Create a trust anchor
  • Your trust anchor should now be created

3. Create IAM Role

  • Select IAM, Roles, Create role
  • Select Custom Trust Policy
  • Copy and Paste the trust policy below
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "rolesanywhere.amazonaws.com"
},
"Action": [
"sts:AssumeRole",
"sts:TagSession",
"sts:SetSourceIdentity"
],
"Condition": {
"StringEquals": {
"aws:PrincipalTag/x509Subject/OU": "Cloud Security"
}
}
}
]
}

The optional condition statement requires the client certificate to include the Cloud Security OU.

  • Press Next
  • Select the AWS managed policy AmazonS3FullAccess
  • This will allow full access to all S3 buckets in your account, we will narrow this down in the next section
  • Add a role name roles-anywhere-s3-full-access

These permissions will be further restricted by the AWS IAM Roles Anywhere profile.

4. Create Roles Anywhere Profile

The IAM Roles Anywhere profile restricts access to a subset of permissions included in the policy assigned to the IAM role.

  • Search for IAM Roles Anywhere
  • This will take you to IAM, Roles, Roles Anywhere
  • Create a profile
  • This is an optional session policy which further limits permissions
  • Effective permissions are the intersection of the session and role policies
  • Enter a name for the profile, e.g. s3-read-access-single-bucket
  • Select the role you created earlier
  • Copy and Paste the session policy below
  • Replace <BUCKET_ARN> with one of your S3 buckets
{
"Version":"2012-10-17",
"Statement":[
{
"Effect":"Allow",
"Action":"s3:Get*",
"Resource":[
"<BUCKET ARN>",
"<BUCKET ARN>/*"
]
}
]
}
  • Press Create a Profile
  • You will now see both the Trust Anchor and Profile in the Roles Anywhere configuration

5. Test client certificate and key in file system

Download AWS IAM Roles Anywhere Credential Helper

  • Download the AWS IAM Roles Anywhere Credential Helper tool for your operating system
  • If using MacOS or Linux, copy the binary into this repository
  • Alternatively copy to another location, e.g. usr/bin/local and set directory in your PATH by editing your .zshrc file on MacOS

Set file permissions on MacOS

  • remove file name extended attribute: xattr -c aws_signing_helper
  • set as executable: chmod 755 aws_signing_helper

Initial test

  • copy the file examples/my-test.example into the repo directory and rename extension to .sh
./aws_signing_helper credential-process \
--certificate ./crypto/client/test-client.pem \
--private-key ./crypto/client/test-client.key \
--trust-anchor-arn "arn:aws:rolesanywhere:eu-west-2:012345678901:trust-anchor/0b855d05-6a40-4eea-af88-b9e9e0d60156" \
--profile-arn "arn:aws:rolesanywhere:eu-west-2:012345678901:profile/b62e420d-2af7-4f3b-afd1-e631e2242744" \
--role-arn "arn:aws:iam::012345678901:role/roles-anywhere-s3-full-access"
  • customise with your own ARNs
  • copy and paste to your terminal and press Enter
  • you should receive a response like this:
{
"Version":1,
"AccessKeyId":"ASIA2L5BHF3A55HVXRPZ",
"SecretAccessKey":"l06S3HyTG3fR/zZ0qDGzR1a0ziCztD7/97fsUvzX",
"SessionToken":"IQoJb3JpZ2luX2VjEKj//////////wEaCWV1LXdlc3QtMiJHMEUCIQCYykiyQpbdJTSPKl+W9sX55i2VKnqL5CmCrHwlRX7OswIgTIyi+LgJqNBTvBZPzHopm23l3btYt3dDIu7xtjFKprgqmgQIkf//////////ARAEGgw3MTI3NjU4MTI0MTciDFjj6NYsuNV+wYCrtCruA0sdP8rw6xL7KFs7KLtCc1M9mK/2M/+I2ZKm3quoSa1XQYvVLaqQ8uL5qMzx3XRr0D0vDzLlBGb6v4KGT+7jqeGZbfnt2UbYh9Ae6Q2A6/3W3fzCFxAFmVLv8/NlZ9pMPSurv/NZB1BRYb78PBfQYtCeSnbKh8TDX71GxkfN20CZvguiCZjwEXLPxMmDadZOYUkBfAo3zBIhAA1Sp1KSnMCy0Z4f9yb5Zo5fZ0rY2YN/QQmqW3Dz8fXrH+U9bk5L/PhaBZ3eDhbhcw5NYFFe3/nFOIJ5T0z8J2Exqe89Hact+VxCHlsSAYn55Ip2R8ORd6sVglGFgVSCnmFHvmkXuujEhXs5gXZ2AabPl/2jJsZ8e1mV6yVvjmcSn7Khzqmir3mKyBtz5wK1P9/sNchmcnZY+sCGsGXfh0FISjqFXTu+nIAUKA32adYfKepztV30x/v/Z6qk044AQUEuRD4kc7fI9JXKc1WIl5IC1pP45E7jQUOWhWSPAoyqpt+uTvNhix+WsdE8DrhkJEcAUuapafnLOn8lXz2StpW01rz8JP77UX4u7a2lxoGcNGFuq0bbgKvUlxMa3GrW5sa7HkhtIPzGip/KCRIdKt5R/SxSW5mlZbzRjDOeMPlIshqWQJnXQbASMRJLtjSnhsEUADOYMIvGnKgGOvMBRo7IY+bP990QxxIsC4imQxphZmFx93jsDEHLHRGkWsiOhqgzR41FSGb6TZmLj/5qG7rF4RaFmbHnpTLsfw9HVAlVuKKuGJ2+IlztvXlRxwiJEKFds8E93onCpNnYe6cfjmtZUaRugBP4ZUKP8/+humpTDrsLGeYhTbySSr3YEjuCzeXwNcaUU185rPPljGwFdqMthp5jntLbBd6mWSSXPLz32cFT3msuJrmnXCBNRjiDEK6GPZo/qcNdVbLAKaTEP7HUK32OTvWd0JdiFMFYGPIyLUThyyXhLYSrsUHWWIGN6RAPITfjf7782eys6onr48he",
"Expiration":"2023-09-17T17:02:19Z"
}

Set variables automatically

Ben’s article includes a useful script for setting variables automatically.

  • copy the file examples/get-creds.example into the repo directory and rename with a .sh extension
export $(./aws_signing_helper credential-process \
--certificate ./crypto/client/test-client.pem \
--private-key ./crypto/client/test-client.key \
--trust-anchor-arn "arn:aws:rolesanywhere:eu-west-2:012345678901:trust-anchor/0b855d05-6a40-4eea-af88-b9e9e0d60156" \
--profile-arn "arn:aws:rolesanywhere:eu-west-2:012345678901:profile/b62e420d-2af7-4f3b-afd1-e631e2242744" \
--role-arn "arn:aws:iam::012345678901:role/roles-anywhere-s3-full-access" | python get_creds.py)
  • substitute your ARNs
  • copy and paste the script to your terminal
  • press enter

Test access

  • check AWS identity, this should succeed
aws sts get-caller-identity
  • list all S3 buckets, this should fail as access is only allowed to a single bucket
aws s3 ls
An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
  • download a file from the designated S3 bucket, this should succeed
  • replace the example below with your own S3 bucket and object
aws s3 cp s3://celidor-confidential/confidential.png confidential.png
download: s3://celidor-confidential/confidential.png to ./confidential.png

6. Configure MacOS Keychain

The AWS Roles Anywhere Credential Helper now supports operating system certificate stores.

Trust Root CA in MacOS Keychain

  • In MacOS, Keychain, select the System Keychain
  • In MacOS, in Finder, locate this repo
  • Drag the file crypto/client/test-ca.pem into the Certificates tab
  • Double click on Test Company Root CA
  • At the first drop down, select Always Trust
  • Close the window
  • Confirm the change to trust settings with your MacOS password
  • Reopen to confirm that the changes have taken effect

Create Certificate Signing Request in MacOS Keychain

It’s best practice is to create the private / public key pair within MacOS Keychain — then the private key never needs to leave Keychain.

  • Open Keychain, from the menu, select Keychain Access, Certificate Assistant, Request Certificate from Certificate Authority
  • For test purposes, enter e.g. test@example.com for email and e.g. MacOS as the client name
  • select Saved to disk and Let me specify key pair information
  • press Continue
  • save the Certificate Signing Request (CSR) file to this repository under crypto/macos
  • leave settings at 2048 bits key size and RSA
  • press Continue
  • the Certificate Signing Request (CSR) has been created and saved to disk

Issue MacOS client cert

  • in main.py change rebuild_ca to False to prevent rebuilding the CA
  • in main.py change macos_cert to True to issue MacOS client cert
  • create MacOS client certificate
python main.py
  • ensure the new certificate file is present at crypto/macos/macos-cert.pem

Import MacOS client cert to Keychain

  • Open KeyChain and select the Login Keychain
  • Select the My Certificates tab
  • Open Finder, navigate to this repository
  • Drag crypto/macos/macos-client.pem on to Keychain
  • It should now appear in My Certificates
  • Expand details and note the key associated with the certificate

7. Test Certificate in MacOS Keychain

  • Open selector.json
[
{
"Key": "x509Subject",
"Value": "CN=MacOS Client,OU=Cloud Security,O=Test Company,L=London,ST=England,C=GB"
},
{
"Key": "x509Issuer",
"Value": "CN=Test Company Root CA,O=Test Company,L=London,ST=England,C=GB"
},
{
"Key": "x509Serial",
"Value": "7C2218B31614DE69CFB8BA2F54781C6B88C7C48A"
}
]
  • serial number needs to be updated with that of your MacOS client certificate
  • In MacOS Keychain, My Certificates, open MacOS client certificate
  • copy the certificate serial number
  • paste into the x509Serial value field
  • remove spaces

Initial MacOS client test

  • copy examples/macos-test.example into the repo directory
  • rename the file extension to .sh
./aws_signing_helper credential-process \
--cert-selector file://selector.json \
--trust-anchor-arn "arn:aws:rolesanywhere:eu-west-2:012345678901:trust-anchor/0b855d05-6a40-4eea-af88-b9e9e0d60156" \
--profile-arn "arn:aws:rolesanywhere:eu-west-2:012345678901:profile/b62e420d-2af7-4f3b-afd1-e631e2242744" \
--role-arn "arn:aws:iam::012345678901:role/roles-anywhere-s3-full-access"
  • update with your AWS Account number, region and ARNs
  • copy and paste into your terminal
  • press enter
  • temporary AWS credentials should be returned in JSON format:
{
"Version":1,
"AccessKeyId":"ASIA2L5BHF3A55HVXRPZ",
"SecretAccessKey":"l06S3HyTG3fR/zZ0qDGzR1a0ziCztD7/97fsUvzX",
"SessionToken":"IQoJb3JpZ2luX2VjEKj//////////wEaCWV1LXdlc3QtMiJHMEUCIQCYykiyQpbdJTSPKl+W9sX55i2VKnqL5CmCrHwlRX7OswIgTIyi+LgJqNBTvBZPzHopm23l3btYt3dDIu7xtjFKprgqmgQIkf//////////ARAEGgw3MTI3NjU4MTI0MTciDFjj6NYsuNV+wYCrtCruA0sdP8rw6xL7KFs7KLtCc1M9mK/2M/+I2ZKm3quoSa1XQYvVLaqQ8uL5qMzx3XRr0D0vDzLlBGb6v4KGT+7jqeGZbfnt2UbYh9Ae6Q2A6/3W3fzCFxAFmVLv8/NlZ9pMPSurv/NZB1BRYb78PBfQYtCeSnbKh8TDX71GxkfN20CZvguiCZjwEXLPxMmDadZOYUkBfAo3zBIhAA1Sp1KSnMCy0Z4f9yb5Zo5fZ0rY2YN/QQmqW3Dz8fXrH+U9bk5L/PhaBZ3eDhbhcw5NYFFe3/nFOIJ5T0z8J2Exqe89Hact+VxCHlsSAYn55Ip2R8ORd6sVglGFgVSCnmFHvmkXuujEhXs5gXZ2AabPl/2jJsZ8e1mV6yVvjmcSn7Khzqmir3mKyBtz5wK1P9/sNchmcnZY+sCGsGXfh0FISjqFXTu+nIAUKA32adYfKepztV30x/v/Z6qk044AQUEuRD4kc7fI9JXKc1WIl5IC1pP45E7jQUOWhWSPAoyqpt+uTvNhix+WsdE8DrhkJEcAUuapafnLOn8lXz2StpW01rz8JP77UX4u7a2lxoGcNGFuq0bbgKvUlxMa3GrW5sa7HkhtIPzGip/KCRIdKt5R/SxSW5mlZbzRjDOeMPlIshqWQJnXQbASMRJLtjSnhsEUADOYMIvGnKgGOvMBRo7IY+bP990QxxIsC4imQxphZmFx93jsDEHLHRGkWsiOhqgzR41FSGb6TZmLj/5qG7rF4RaFmbHnpTLsfw9HVAlVuKKuGJ2+IlztvXlRxwiJEKFds8E93onCpNnYe6cfjmtZUaRugBP4ZUKP8/+humpTDrsLGeYhTbySSr3YEjuCzeXwNcaUU185rPPljGwFdqMthp5jntLbBd6mWSSXPLz32cFT3msuJrmnXCBNRjiDEK6GPZo/qcNdVbLAKaTEP7HUK32OTvWd0JdiFMFYGPIyLUThyyXhLYSrsUHWWIGN6RAPITfjf7782eys6onr48he",
"Expiration":"2023-09-17T17:02:19Z"
}

Set MacOS client variables automatically

  • copy examples/get-macos-creds.example into the repo directory and rename it with a .sh extension
  • format of file:
export $(./aws_signing_helper credential-process \
--cert-selector file://selector.json \
--trust-anchor-arn "arn:aws:rolesanywhere:eu-west-2:012345678901:trust-anchor/0b855d05-6a40-4eea-af88-b9e9e0d60156" \
--profile-arn "arn:aws:rolesanywhere:eu-west-2:012345678901:profile/b62e420d-2af7-4f3b-afd1-e631e2242744" \
--role-arn "arn:aws:iam::012345678901:role/roles-anywhere-s3-full-access" | python get_creds.py)
  • substitute your ARNs
  • copy and paste the script to your terminal
  • press enter
  • enter your MacOS password
  • press Always Allow

Test MacOS client access

  • check AWS identity, this should succeed
aws sts get-caller-identity
  • list all S3 buckets, this should fail as access is only allowed to a single bucket
aws s3 ls
An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
  • download a file from the designated S3 bucket, this should succeed
  • replace the example below with your own S3 bucket and object
aws s3 cp s3://celidor-confidential/confidential.png confidential.png
download: s3://celidor-confidential/confidential.png to ./confidential.png

View connection details

  • At IAM, Roles, Roles Anywhere, navigate to your region
  • Select Subject Activity
  • A record of connections using the original and MacOS certificates can be seen
  • Click on one of the links to view the certificate used to authenticate

👏 🎉 🎊 Congratulations, you’ve set up and tested AWS IAM Roles Anywhere with MacOS Keychain 🎆 🌟 🎇

8. Security Considerations

AWS IAM Roles Anywhere is a useful additional option for granting on-premise and mobile laptops access to AWS. Effectively it replaces AWS access keys with X.509 certificates.

However it’s important to note that this isn’t necessarily any more secure — a stolen certificate and private key can be used to access AWS just as easily as a stolen access key and secret access key.

Whether this is a secure solution depends very much on how the private key of the client certificate is stored. Support for hardware devices such as Trusted Platform Modules (TPMs) and Yubikeys would be a useful addition to the Credential Helper to further enhance security.

It’s also essential that the Certificate Authority is implemented in a secure manner, with its private key generated within a Hardware Security Module (HSM) and not exportable, and other controls and procedures in place.

Updates

28 March 2024

I’ve recently released an open-source serverless CA on AWS, suitable for use with IAM Roles Anywhere, see this Medium story. The open-source CA is a viable alternative to both AWS Private CA (which is expensive) and the CA code referenced earlier in this article (insecure and only suitable for test purposes).

28 June 2024

I’ve published details of configuring AWS IAM Roles Anywhere with the open-source private CA in this article, which also includes an improved user experience by setting up an AWS CLI profile for the service.

--

--

Paul Schwarzenberger

Paul Schwarzenberger is a cloud security architect and engineer, creator of OWASP Domain Protect, and cloud security trainer.