How To

Tackling Third-Party Risks in your AWS Environment

Learn to identify 3rd party access to Amazon Machine Images (AMIs) and IAM cross-account trusts.

Chris Farris
7 min. read - Apr 12, 2023
Learn to identify 3rd party access to Amazon Machine Images (AMIs) and IAM cross-account trusts.

With supply chain attacks on the rise, it's crucial to know which third-party accounts you trust. In this post we'll show how to identify, at scale, two common forms of third-party trust: IAM Roles that can be assumed by other AWS customers, and disk images that were created by other AWS customers that are running in your environment. This will allow you to audit the third parties that you trust, so you can ensure they've been vetted and meet your organization’s security and compliance objectives.

Ensuring AMIs are from trusted sources

Any AWS customer can publish an Amazon Machine Image (AMI) for other AWS customers to launch instances from. AWS only vets a handful of images in the AWS Marketplace, there is no guarantee that other publicly shared AMIs are free of vulnerabilities or malicious code. While it's common for vendors to share their software as an AMI, it's also possible someone in your organization has launched an instance from a compromised image.

All AMIs are owned by an AWS account. With Steampipe, you can easily pull a report of the AWS accounts that own the AMIs they use to validate they come from trusted sources.

WITH instances AS (
SELECT
instance_id,
instance_type,
account_id,
tags ->> 'Name' AS instance_name,
_ctx ->> 'connection_name' AS account_name,
instance_state,
region,
image_id
FROM
aws_ec2_instance
)
SELECT DISTINCT
aws_ec2_ami_shared.image_id as image_id,
aws_ec2_ami_shared.owner_id as image_owner_id,
aws_ec2_ami_shared.image_owner_alias as image_owner_name,
instances.instance_name,
instances.account_name,
instances.region,
aws_ec2_ami_shared.name as image_name
FROM
instances
LEFT JOIN aws_ec2_ami_shared ON aws_ec2_ami_shared.image_id=instances.image_id
WHERE aws_ec2_ami_shared.image_owner_alias != 'amazon'
AND aws_ec2_ami_shared.image_owner_alias != 'self'
+-----------------------+----------------+------------------+---------------+--------------------+-----------+------------
| image_id | image_owner_id | image_owner_name | instance_name | account_name | region | image_name
+-----------------------+----------------+------------------+---------------+--------------------+-----------+------------
| ami-08ab860352f8b42d5 | 679533241933 | aws-marketplace | <null> | aws_fooli_security | us-west-2 | splunk_AMI_
| ami-0d52da3dadefbb017 | 186678614485 | 186678614485 | SIFT | aws_fooli_security | us-east-1 | sift-2022
+-----------------------+----------------+------------------+---------------+--------------------+-----------+-------------

This query excludes all the AMIs that are published via AWS's official sources, but it doesn't exclude AWS Marketplace images. To exclude marketplace images, add the following to the WHERE clause:

AND aws_ec2_ami_shared.image_owner_alias != 'aws-marketplace'

List all the AWS accounts you trust

Another key third-party trust relationship is via IAM Role cross-account trusts. With AWS IAM, you can grant other AWS customers access to perform actions in your account. Obviously, this has third-party risk-management implications, and all third-party access should be reviewed.

Within an AWS organization, there are often a lot of cross-account trusts. Various accounts are created to audit configuration, write logs, perform auto-remediation tasks, or deploy resources via a centralized pipeline. These are not third-party risks and should be excluded from any list of foreign AWS accounts in your organization.

The following query will pull a list of foreign AWS accounts that are trusted across your AWS organization:

WITH org_accounts AS (
SELECT
id
FROM
aws_payer.aws_organizations_account
),
roles AS (
SELECT
name,
(regexp_match(principals, ':([0-9]+):')) [ 1 ] AS trusted_account,
_ctx ->> 'connection_name' AS account_name
FROM
aws_iam_role AS role,
jsonb_array_elements(role.assume_role_policy_std -> 'Statement') AS statement,
jsonb_array_elements_text(statement -> 'Principal' -> 'AWS') AS principals
)
SELECT
roles.name as role_name,
roles.account_name,
roles.trusted_account
FROM
org_accounts
RIGHT JOIN roles ON org_accounts.id = roles.trusted_account
WHERE
org_accounts.id IS NULL
+---------------------+--------------------------------+-----------------+
| role_name | account_name | trusted_account |
+---------------------+--------------------------------+-----------------+
| TurbotGuardDutyRole | aws_fooli_sandbox | 495636112135 |
| steampipe-cloud | aws_fooli_sandbox | 316881668097 |
| steampipe-cloud | aws_fooli_memefactory | 316881668097 |
| steampipe-cloud | aws_fooli_payer | 316881668097 |
| steampipe-cloud | aws_fooli_prod | 316881668097 |
| steampipe-cloud | aws_fooli_dev | 316881668097 |
| steampipe-cloud | aws_fooli_security | 316881668097 |
+---------------------+--------------------------------+-----------------+

Identifying foreign AWS Accounts

The above two queries provide you a list of the 12-digit account IDs, but that doesn’t tell you who they are. AWS doesn’t publish a list of known account IDs, but the cloud security community does. The CloudMapper project from Duo has a yaml file with a number of known AWS accounts. With the config plugin you can query this resource. First install the Steampipe Config plugin, then create a connection in the config.spc file that looks like this:

connection "cloudmapper" {
plugin = "config"
yml_paths = [ "github.com/duo-labs/cloudmapper//main/vendor_accounts.yaml" ]
}

This query parses the file and returns the name and account Id:

WITH name_data AS (
SELECT
split_part(key_path::text, '.', 1) AS id,
value AS name
FROM
cloudmapper.yml_key_value
WHERE
key_path::text LIKE '%.name%'
), account_data AS (
SELECT
split_part(key_path::text, '.', 1) AS id,
value AS account
FROM
cloudmapper.yml_key_value
WHERE
key_path::text LIKE '%.accounts.%'
)
SELECT
n.name,
a.account
FROM
name_data n
JOIN
account_data a ON n.id = a.id
ORDER BY
n.name, a.account
+-----------------------------------------------------+--------------+
| name | account |
+-----------------------------------------------------+--------------+
| Summit Route | 393727464233 |
| Sumo Logic | 926226587429 |
| Threat Stack | 896126563706 |
| TrendMicro | 147995105371 |
| Turbot | 255798382450 |
| Turbot | 287590803701 |
| Upsolver | 428641199958 |
| VManage | 200235630647 |
| cloudbreak | 755047402263 |
| cloudsploit | 057012691312 |
...
+-----------------------------------------------------+--------------+

Getting an actionable list of trusted third-party accounts

To put all of this together, here is an 82-line SQL query which you can find in our samples repo.

We combine the first two queries (via UNION) into a list of all_foreign_accounts. We join that with the data from CloudMapper, and the final query and results looks something like:

SELECT
all_foreign_accounts.foreign_account_id,
known_aws_accounts.name
FROM
all_foreign_accounts
LEFT JOIN known_aws_accounts
ON all_foreign_accounts.foreign_account_id = known_aws_accounts.account
+--------------------+-----------+
| foreign_account_id | name |
+--------------------+-----------+
| 316881668097 | Steampipe |
| 679533241933 | <null> |
| 525188041748 | <null> |
| 495136121356 | <null> |
| 186678614485 | <null> |
+--------------------+-----------+

What results is a final list of foreign AWS accounts trusted by your organization. You and your third-party risk management team will need to track down the owner of the AWS account and determine if they have been properly vetted.

Key Takeaway

To enhance the protection of your AWS infrastructure from supply chain attacks, it is crucial to actively manage third-party risks. We have shown how Steampipe can help by validating AMIs, and cross-account trusts for IAM roles. To get started in your environment, download Steampipe and join the Slack community to discuss your use cases.