Calling AWS Services from Your On-Premises Servers Using IAM Roles Anywhere

Roy Ben Yosef
CyberArk Engineering
11 min readJul 26, 2022

Updates:

Permissions in AWS are easy: You just slap some IAM role on to something and you’re done. Well, not really, but you get the point — working with permissions in the AWS ecosystem is built into the system.

It gets more complicated when you want to allow access to a client outside of the AWS cloud. For example, an on-premises server that needs access to your cloud account.

Now, you have a new option. With the recently released “AWS IAM Roles Anywhere” which allows you to provide temporary credentials to hosts outside the AWS cloud.

Note: This blog assumes basic knowledge of the PKI system and x.509 certificates, and also AWS IAM Roles and Policies.

Table Of Contents:
-What Is “AWS IAM Roles Anywhere?”
-IAM Roles Anywhere Certificate Authority
-Private Key Protection
-Setting up IAM Roles Anywhere
-Putting All the Parts we Created to Work
-Narrowing Down Permissions
-How to Monitor IAM Roles Anywhere
-CRL Support
-Infrastructure as Code with CDK
-Appendix: Creating a CA and Client Certificates with OpenSSL

What Is “AWS IAM Roles Anywhere?”

Simply put, you can use IAM Roles Anywhere to get temporary AWS credentials that allow access to your AWS resources.

It gives this access based on x.509 certificates and the PKI, where your on-premises servers use client certificates, signed by a certificate authority (CA) that you control and which you registered in “AWS IAM Role Anywhere”.

The use of IAM Roles Anywhere itself is free of charge, but if you use “AWS Private CA”, it has its own costs.

IAM Roles Anywhere Certificate Authority

When using IAM Roles Anywhere, you are required to have a CA that will sign x.509 client certificates.

For this, you generally have two options:

  • Use the “AWS Private CA” (formerly AWS Certificate Manager Private Certificate Authority — ACM PCA).
  • Roll your own certificate authority using OpenSSL, for example.

Using “AWS Private CA” is certainly the easiest way, but you need to consider its pricing model.

For completeness, I will show you how to roll your own CA and sign client certificates using OpenSSL.

Note: Rolling your own CA requires careful attention to details. This blog post is not intended to be a full guide, and the example below of creating a CA is only for testing purposes. In case you do go down this path, I encourage you to do your research and invest in implementing it correctly. My example is intended to be a minimal one for working with IAM Roles Anywhere.

Private Key Protection

Working with a CA and creating client certificates, you also create private keys. Those keys must be kept secret and you should protect them as best you can.

Keep in mind:

  • If you roll your own CA, you are responsible for keeping the private key secure. Anyone with access to the CA private key can sign client certificates on your behalf and use them to gain access to your AWS account.
  • In addition to their certificate, your clients have a private key, which also must be kept safe. Anyone with access to this private key will be able to gain access just like your server does.
Photo by Andrik Langfield on Unsplash

Setting up IAM Roles Anywhere

Let’s set up IAM Roles Anywhere to allow an on-premises server access to the S3 buckets in our account. We’ll do this step-by-step and explain each part of IAM Roles Anywhere in the process.

Certificate Authority and Client Certificates Requirements

A fully working example of creating the CA and client certs for testing purposes only can be found below.

Your client certificates must have the following constraints:

  • Must be an x.509 v3 certificate.
  • Must include CA: false in its basic constraints.
  • Key usage must include Digital Signature.
  • Must use SHA256 or a stronger signing algorithm.

Your CA certificates must satisfy the above requirements, and also:

  • Key usage must include Digital Signature, Certificate Sign and CRL Sign.
  • Must include CA: true (instead of false) in its basic constraints.

Create the Trust Anchor

The trust anchor represents your CA — either an “AWS Private CA” certificate authority or your own. Your clients will authenticate using a client certificate signed by this CA.

  • Open the AWS IAM console, go to “Roles” and at the bottom, under the “Roles Anywhere” section, click “Manage.”
  • Click “Create a trust anchor” and fill in the anchor name.
  • In our example, we will use our own CA, so select “External certificate bundle.”
  • Under “External certificate bundle,” paste your CA certificate, which, in our example, you can get from the content of rootCA.pem (see bottom section).
  • Click “Create trust anchor.”

Create a Role That Trusts the IAM Roles Anywhere Service Principal

Next, we create an IAM role that trusts the IAM Roles Anywhere service and provides clients with permissions to AWS services in our account. This allows IAM Roles Anywhere to assume the role and provide temporary AWS credentials.

We can narrow down permissions by allowing only client certificates with “OU=Innovation” in their subject (for example).

  • Go to IAM, “Roles,” “Create Role.”
  • Select “Custom Trust Policy” and paste the following policy as the “Custom trust policy”:
{
"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": "Innovation"
}
}
}
]
}
  • Click “Next,” search for the managed policy “AmazonS3FullAccess” and select it. Click “Next” (we will narrow down these permissions later).
  • Name your role and click “Create Role.”

Create a Profile to Manage Client Roles and Permissions

The last piece of the puzzle is to create a profile in which we specify which IAM roles in our account we want to allow clients to assume via temporary credentials.

A profile will also allow us to limit or narrow down the permissions that the original role provides (as the docs say “Session policies limit the permissions for a created session, but do not grant permissions”.)
We could and should, for example, use conditions here to limit access to certain IP ranges and/or by the needed resource.

  • Back in the “Roles Anywhere” console, click “Create a profile” and provide a name for our profile.
  • Under “Roles,” select your newly created role. You can also add multiple roles here.

Note: If you can’t see your role, make sure you properly configured the trust policy for AWS IAM Roles Anywhere in your role.

  • Under “Session policies,” “Managed policies,” select the “AmazonS3ReadOnlyAccess” policy. As an example of how we can narrow down the permissions, we will only allow read access to a specific s3 bucket.
  • For now, we will not use the “Inline policy,” so click “Create a profile.”
  • IMPORTANT! In this example, anyone with our client certificate will have read access to ALL buckets in your account. You will later set this to be least privileged by giving access only to the needed resources. Also, remember to clean up by deleting all unused resources after you are finished.
Photo by Ashkan Forouzani on Unsplash

Putting All the Parts we Created to Work

Update: AWS Open sourced the helper tool here, and also updated the docs with instructions on how to generate credentials with IAM Roles Anywhere. So it is possible to do this in code if you prefer, instead of using the helper tool.

It’s finally time to put everything to the test! To use our client certificate to obtain temporary credentials, we have to use the “AWS Signing Helper utility”, which you can download here.

Collect the ARNs for the trust anchor, profile and role you created before. Assuming you followed the steps in the example below, run the following command (replace <anchor-arn>, <profile-arn> and <role-arn> with your values):

./aws_signing_helper credential-process \
--certificate ./server.pem --private-key ./server.key \
--trust-anchor-arn <anchor-arn> \
--profile-arn <profile-arn> \
--role-arn <role-arn>

For details and other options (like session duration), see the docs.

The output will include your temporary credentials: AccessKeyId, SecretAccessKey and SessionToken, with a default session duration of one hour.

Using the Temporary Credentials

You can, among other ways, use the temporary credentials by setting the following environment variables to their corresponding values (“set” for windows instead of export):

export AWS_ACCESS_KEY_ID=<AccessKeyId value>
export AWS_SECRET_ACCESS_KEY=<SecretAccessKey value>
export AWS_SESSION_TOKEN=<SessionToken value>

You can now try calling an AWS service, for example, with AWS cli. Notice how putting an object will fail because we set the read-only managed policy at the profile level, which narrowed down the role’s full access policy.

aws s3 ls
aws s3api list-objects --bucket <bucket-name>
aws s3api put-object --bucket <bucket-name> --key testobj (FAILS!)

Set Environment Variables One-Liner

If you want a simple way to set the environment variables, you can create this python script named get_creds.py:

import json
import sys
creds = json.loads(sys.stdin.read())print(f"AWS_ACCESS_KEY_ID={creds['AccessKeyId']} AWS_SECRET_ACCESS_KEY={creds['SecretAccessKey']} AWS_SESSION_TOKEN={creds['SessionToken']}")

And then run this one-liner to set all three variables:

export $(./aws_signing_helper credential-process \
--certificate ./server.pem --private-key ./server.key \
--trust-anchor-arn <anchor-arn> \
--profile-arn <profile-arn> \
--role-arn <role-arn> | python get_creds.py)

Narrowing Down Permissions

Ideally, we will now further narrow down access to our account by only allowing access from specific IP or IP ranges (find more info here) and only to a specific bucket.

Open your profile, click “Edit,” and under “Session policies,” “Inline policy,” paste the following (replace <1.1.1.1> with your public IP and <BUCKET ARN> with a bucket in your account):

{
"Version":"2012-10-17",
"Statement":[
{
"Effect":"Deny",
"Action":"*",
"NotResource":"<BUCKET ARN>"
},
{
"Effect":"Deny",
"Action":"*",
"Resource": "*",
"Condition": {
"NotIpAddress": {
"aws:SourceIp": "<1.1.1.1>"
}
}
}
]
}

Create a new session by obtaining new temporary credentials verifying that everything works. You can now try a client certificate from a different IP to verify that the condition is effective (or simply set another IP in the policy).

Note that you no longer can list buckets, or perform any operation on any bucket other than those specified in the inline policy (e.g. read on a specific bucket, like list-objects).

Use this inline policy to further narrow down permissions as needed.

I used the “Deny” effect, but there are other way to limit permissions. For more information about how to narrow permissions in session policies see: Session policies and Create fine-grained session permissions.

How to Monitor IAM Roles Anywhere

There are several ways to monitor IAM Roles Anywhere: You can set alerts by Cloudwatch metrics, for example, when CA certificates are about to expire, or on failed session attempts. You can also subscribe to EventBridge events for this purpose, and use CloudTrail to monitor the relevant API calls.
In addition, the console provides the ability to inspect per-subject information under “Subject activity.”

For more information, see the monitoring overview guide.

CRL Support for Lost or Compromised Certificates

IAM Roles Anywhere also supports certificate revocation lists (or CRLs), allowing you to revoke lost or compromised certificates. For details, see the API for ImportCrl.

Infrastructure as Code with CDK

As always, we would like to do things as code. Here’s some sample code to do this with CDK.
Since IAM Roles Anywhere is new, I used the escape hatches for deploying its resources. As soon as higher-level constructs are available, I will update this code. Note the use of “Innovation” for the OU, and implement get_ca_cert, get_allowed_ips and get_allowed_bucket_arn.

Summary: Connect Your Devices with AWS IAM Roles Anywhere

If you need a way to connect devices, which are outside the AWS ecosystem (like on-premises servers), AWS IAM Roles Anywhere is a great new offering from AWS, and I recommend kicking the tires on it.
As always, when looking into an AWS service, you should always take a look at service quotas and pricing, but this one is free (assuming you are rolling your own CA).

Remember to clean up by deleting any unused resources!

Appendix: Creating a Certificate Authority and Client Certificates with OpenSSL

Note: This is only for testing purposes! By no means should this implementation be used for anything other than that. In case you are rolling your own private CA, take the time to investigate your needs and requirements before deciding how to implement it. THIS IS ONLY FOR TESTING PURPOSES!

Let’s create a simple certificate authority (CA) using OpenSSL, and use it to sign client certificates, which we can use to test IAM Roles Anywhere. Note that IAM Roles Anywhere requires that we use a version 3 CA certificate.

  1. Generate an elliptic curve key for the certificate authority:
openssl ecparam -genkey -name secp384r1 -out rootCA.key

2. Create a file called root_request.config, which will serve as the config for the CA certificate signing request (CSR). Paste the following into it:

[req]
prompt = no
string_mask = default
distinguished_name = req_dn
[req_dn]
countryName = US
stateOrProvinceName = NY
localityName = New York
organizationName = Acme Inc.
organizationalUnitName = AC
commonName = Acme CA

3. Create the CSR for the CA certificate:

openssl req -new -key rootCA.key -out rootCA.req -nodes -config root_request.config

4. To create the CA certificate, we require creation of a database file and a serial file (the CA requires those for signing certs), run the following commands:

touch index
echo 01 > serial.txt

5. Create the CA certificate config file named root_certificate.config and paste the following into it:

# This is used with the 'openssl ca' command to sign a request
[ca]
default_ca = CA
[CA]
# Where OpenSSL stores information
dir = .
certs = $dir
crldir = $dir
new_certs_dir = $certs
database = $dir/index
certificate = $certs/rootcrt.pem
private_key = $dir/rootprivkey.pem
crl = $crldir/crl.pem
serial = $dir/serial.txt
RANDFILE = $dir/.rand
# How OpenSSL will display certificate after signing
name_opt = ca_default
cert_opt = ca_default
# How long the CA certificate is valid for
default_days = 3650
# The message digest for self-signing the certificate
default_md = sha256
# Subjects don't have to be unique in this CA's database
unique_subject = no
# What to do with CSR extensions
copy_extensions = copy
# Rules on mandatory or optional DN components
policy = simple_policy
# Extensions added while singing with the `openssl ca` command
x509_extensions = x509_ext
[simple_policy]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = optional
domainComponent = optional
emailAddress = optional
name = optional
surname = optional
givenName = optional
dnQualifier = optional
[ x509_ext ]
# These extensions are for a CA certificate
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
basicConstraints = critical, CA:TRUEkeyUsage = critical, keyCertSign, cRLSign, digitalSignature

6. Create the CA certificate (answer yes to all prompts):

openssl ca -out rootCA.pem -keyfile rootCA.key -selfsign -config root_certificate.config  -in rootCA.req

7. Examine your CA certificate details. Note that the version is 3 (0x2):

openssl x509 -noout -text -in rootCA.pem

8. Create the client key:

openssl ecparam -genkey -name secp384r1 -out server.key

9. Create the client CSR config file named server_request.config. This is the most basic and minimal CSR configuration, just for testing purposes. Paste the following:

[ req ]
prompt = no
distinguished_name = dn
[ dn ]
C = US
O = Acme
CN = Acme.com
OU = Innovation

Note how the OU is “Innovation” to comply with our trust policy.

10. Create the client CSR:

openssl req -new -sha512 -nodes -key server.key -out server.csr -config server_request.config

11. Create the client certificate config file named server_cert.config. Paste the following:

basicConstraints = critical, CA:FALSE
keyUsage = critical, digitalSignature

12. Create the CA signed client certificate:

openssl x509 -req -sha512 -days 365 -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.pem -extfile server_cert.config

13. Verify the client certificate:

openssl verify -verbose -CAfile rootCA.pem server.pem

This section was inspired by the answers to this question: https://serverfault.com/questions/979094/openssl-keeps-creating-v1-certificate-instead-of-v3

--

--

Roy Ben Yosef
CyberArk Engineering

Sr. Software architect at CyberArk’s Technology Office. Into code, architecture and problem solving. Like to build and fix stuff. Usually late at night.