AWS IAM Roles Anywhere with MacOS Keychain
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
- Certificate Authority
- Create Trust Anchor
- Create IAM Role
- Create Roles Anywhere Profile
- Test Client Certificate and Key in File System
- Configure MacOS Keychain
- Test Certificate in MacOS KeyChain
- 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 yourPATH
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
andLet 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 andRSA
- press Continue
- the Certificate Signing Request (CSR) has been created and saved to disk
Issue MacOS client cert
- in
main.py
changerebuild_ca
toFalse
to prevent rebuilding the CA - in
main.py
changemacos_cert
toTrue
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.