Built in the Unix philosophy, this is a small tool that does one thing well: explain IAM actions with wildcards.
Use this to:
- Expand wildcards when you are not allowed to use them in your policies.
- Get an exhaustive list of actions that are included in a policy to quickly search it for interesting actions.
- Investigate where interesting or dubious actions are being used in your policies.
Extended demo on YouTube.
Published as an npm package in ESM and CommonJS plus available as a CLI.
All information is sourced from @cloud-copilot/iam-data which is updated daily.
iam-expand
intends to only return valid, actual actions, if any invalid values are passed in such as an invalid format or a service/action that does not exist, they will be left out of the output. There are options to override this behavior.
http://iam.cloudcopilot.io/tools/iam-expand
There is a CLI! The examples folder has examples showing how to use the CLI to find interesting actions in your IAM policies.
You can install it globally. This also works in the default AWS CloudShell!
npm install -g @cloud-copilot/iam-expand
Depending on your configuration sudo may be required to install globally.
You can also install the CLI in a project and run it with npx
.
npm install @cloud-copilot/iam-expand
# Run with npx inside your project
npx @cloud-copilot/iam-expand
The simplest usage is to pass in the actions you want to expand.
iam-expand s3:Get*Tagging
# Outputs all Get*Tagging actions
s3:GetBucketTagging
s3:GetJobTagging
s3:GetObjectTagging
s3:GetObjectVersionTagging
s3:GetStorageLensConfigurationTaggin
iam-expand s3:Get*Tagging s3:Put*Tagging
# Outputs the combination of Get*Tagging and Put*Tagging actions deduplicated and sorted
s3:GetBucketTagging
s3:GetJobTagging
s3:GetObjectTagging
s3:GetObjectVersionTagging
s3:GetStorageLensConfigurationTagging
s3:PutBucketTagging
s3:PutJobTagging
s3:PutObjectTagging
s3:PutObjectVersionTagging
s3:PutStorageLensConfigurationTaggin
Use this to find all actions that are not in a set of patterns
iam-expand --invert s3:Get*Tagging s3:Put*Tagging
#Outputs all actions that are not Get*Tagging or Put*Tagging
a2c:GetContainerizationJobDetails
a2c:GetDeploymentJobDetails
a2c:StartContainerizationJob
a2c:StartDeploymentJob
a4b:ApproveSkill
a4b:AssociateContactWithAddressBook
...
Run the command with no options to show usage:
iam-expand
By default, a single *
will not be expanded. If you want to expand a single *
you can set this flag.
iam-expand "*"
# Returns the asterisk
*
iam-expand --expand-asterisk "*"
# Returns very many strings, very very fast. 📚 🚀
By default, if an invalid format is passed in, such as:
s3Get*Tagging
(missing a separator) ors3:Get:Tagging*
(too many separators)
it will be silenty ignored and left out of the output. If you want to throw an error when an invalid format is passed in you can set this flag.
iam-expand "s3Get*Tagging"
# Returns nothing
iam-expand --error-on-invalid-format "s3Get*Tagging"
# Throws an error and returns a non zero exit code
# Invalid action format: s3Get*Tagging
By default, if a service is passed in that does not exist in the IAM data, it will be silently ignored and left out of the output. If you want to throw an error when a service is passed in that does not exist you can set this flag.
iam-expand "r2:Get*Tagging"
# Returns nothing
iam-expand --error-on-invalid-service "r2:Get*Tagging"
# Throws an error and returns a non zero exit code
# Service not found: r2
By default, if an action is passed in that does not exist in the IAM data, it will be silently ignored and left out of the output. There are two options to override this behavior: error
and include
.
iam-expand "ec2:DestroyAvailabilityZone"
# Returns nothing
iam-expand --invalid-action-behavior=remove "ec2:DestroyAvailabilityZone"
# Returns nothing
iam-expand --invalid-action-behavior=error "ec2:DestroyAvailabilityZone"
# Throws an error and returns a non zero exit code
# Invalid action: ec2:DestroyAvailabilityZone
iam-expand --invalid-action-behavior=include "ec2:DestroyAvailabilityZone"
# Returns the invalid action
ec2:DestroyAvailabilityZone
Use this to find all actions that are not in a set of patterns. Only works for actions passed as arguments, or unstructured content from stdin.
iam-expand --invert s3:Get*Tagging s3:Put*Tagging
#Outputs all actions that are not Get*Tagging or Put*Tagging
a2c:GetContainerizationJobDetails
a2c:GetDeploymentJobDetails
a2c:StartContainerizationJob
a2c:StartDeploymentJob
a4b:ApproveSkill
a4b:AssociateContactWithAddressBook
...
This operates only on JSON input. It will recursively search the JSON document for any NotAction
that is a string or an array of strings. The NotAction
will be replaced with an Action
key that is the inverse of the NotAction
actions or patterns.
cat policy.json | iam-expand --invert-not-actions
See Read from Stdin for more details
Show the version of the data that is being used to expand the actions and exit.
iam-expand --show-data-version
@cloud-copilot/iam-data version: 0.3.202409051
Data last updated: Thu Sep 05 2024 04:46:39 GMT+0000 (Coordinated Universal Time)
Update with either:
npm update @cloud-copilot/iam-data
npm update -g @cloud-copilot/iam-data
When reading from stdin (see below) the CLI will wait 10 seconds for the first byte to be read before timing out. This is enough time for most operations. If you want to wait longer you can set this flag to the number of milliseconds you want to wait.
cat policy.json | iam-expand
# Will wait up to 10 seconds for input to start, which is plenty of time for a local file.
curl "https://government-secrets.s3.amazonaws.com/secret-policy.json" | iam-expand --read-wait-ms=20_000
# Will wait up to 20 seconds to receive first byte from curl before timing out. Adjust as needed
If no actions are passed as arguments, the CLI will read from stdin.
If the input is a valid json document, the CLI will find every instance of Action
and NotAction
that is a string or an array of strings and expand them. This is useful for finding all the actions in a policy document or set of documents.
Given policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:Get*Tagging",
"Resource": "*"
},
{
"Effect": "Deny",
"NotAction": ["s3:Get*Tagging", "s3:Put*Tagging"],
"Resource": "*"
}
]
}
cat policy.json | iam-expand > expanded-policy.json
Gives this file in expanded-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
// Was "s3:Get*Tagging"
"Action": [
"s3:GetBucketTagging",
"s3:GetJobTagging",
"s3:GetObjectTagging",
"s3:GetObjectVersionTagging",
"s3:GetStorageLensConfigurationTagging"
],
"Resource": "*"
},
{
"Effect": "Deny",
// Was ["s3:Get*Tagging", "s3:Put*Tagging"]
"NotAction": [
"s3:GetBucketTagging",
"s3:GetJobTagging",
"s3:GetObjectTagging",
"s3:GetObjectVersionTagging",
"s3:GetStorageLensConfigurationTagging",
"s3:PutBucketTagging",
"s3:PutJobTagging",
"s3:PutObjectTagging",
"s3:PutObjectVersionTagging",
"s3:PutStorageLensConfigurationTagging"
],
"Resource": "*"
}
]
}
You can also invert the NotAction
using --invert-not-actions
. This will replace the NotAction
element with an Action
element that is the inverse of actions listed in the NotAction
.
cat policy.json | iam-expand --invert-not-actions > inverted-policy.json
Gives this file in inverted-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
// Was "s3:Get*Tagging"
"Action": [
"s3:GetBucketTagging",
"s3:GetJobTagging",
"s3:GetObjectTagging",
"s3:GetObjectVersionTagging",
"s3:GetStorageLensConfigurationTagging"
],
"Resource": "*"
},
{
"Effect": "Deny",
// Was NotAction: ["s3:Get*Tagging", "s3:Put*Tagging"]
// Now is Action: everything but the Get*Tagging and Put*Tagging actions
"Action": [
"a2c:GetContainerizationJobDetails",
"a2c:GetDeploymentJobDetails",
"a2c:StartContainerizationJob",
...
"xray:UntagResource",
"xray:UpdateGroup",
"xray:UpdateSamplingRule",
],
"Resource": "*"
}
]
}
You can also use this to expand the actions from the output of commands.
aws iam get-account-authorization-details --output json | iam-expand --read-wait-ms=20_000 > expanded-authorization-details.json
# Now you can search the output for actions you are interested in
grep -n "kms:DisableKey" expanded-authorization-details.json
If the input from stdin is not json, the content is searched for IAM actions then expands them. Throw anything at it and it will find all the actions it can and expand them.
You can echo content:
echo "s3:Get*Tagging" | iam-expand
You can pull out part of a json file and pipe it in:
cat policy.json | jq '.Statement[].Action' | iam-expand
Or some Terraform:
cat main.tf | iam-expand
Or some CloudFormation:
cat template.yaml | iam-expand
Or even some HTML:
curl "https://docs.aws.amazon.com/aws-managed-policy/latest/reference/ReadOnlyAccess.html" | iam-expand
Or the output of any command.
Because of the likelyhood of finding an aseterik *
in the input; if the value to stdin is not a valid json document the stdin option will not find or expand a single *
even if --expand-asterisk
is passed.
Please give this anything you can think of and open an issue if you see an opportunity for improvement.
npm install @cloud-copilot/iam-expand
import { expandIamActions } from '@cloud-copilot/iam-expand';
expandIamActions('s3:Get*Tagging')
[
's3:GetBucketTagging',
's3:GetJobTagging',
's3:GetObjectTagging',
's3:GetObjectVersionTagging',
's3:GetStorageLensConfigurationTagging'
]
expandIamActions(['s3:Get*Tagging', 's3:Put*Tagging'])
[
's3:GetBucketTagging',
's3:GetJobTagging',
's3:GetObjectTagging',
's3:GetObjectVersionTagging',
's3:GetStorageLensConfigurationTagging',
's3:PutBucketTagging',
's3:PutJobTagging',
's3:PutObjectTagging',
's3:PutObjectVersionTagging',
's3:PutStorageLensConfigurationTagging'
]
expandIamActions(actionStringOrStrings: string | string[], overrideOptions?: Partial<ExpandIamActionsOptions>)
is the main function that will expand the actions of the IAM policy. Takes a string or array of strings and returns an array of strings that the input matches.
expandIamActions
intends to only return valid actual actions, if any invalid values are passed in such as an invalid format or a service/action that does not exist, they will be left out of the output. There are options to override this behavior.
Any escaped unicode characters will be converted to their original character as part of the process. So s3:\\u0067et*
will be converted to s3:Get*
before processing. Even something like s3:\\u0067etBucket
will be converted to s3:GetBucket
even though it has no wildcards..
expandIamActions
an optional second argument that is an object with the following options:
By default, a single *
will not be expanded. If you want to expand a single *
you can set this option to true
.
import { expandIamActions } from '@cloud-copilot/iam-expand';
//Returns the unexpanded value
expandIamActions('*')
['*']
//Returns the expanded value
expandIamActions('*', { expandAsterisk: true })
[
//Many many strings. 🫢
]
By default, if an invalid format is passed in, such as:
s3Get*Tagging
(missing a separator) ors3:Get:Tagging*
(too many separators)
it will be silenty ignored and left out of the output. If you want to throw an error when an invalid format is passed in you can set this option to true
.
import { expandIamActions } from '@cloud-copilot/iam-expand';
//Ignore invalid format
expandIamActions('s3Get*Tagging')
[]
//Throw an error on invalid format
expandIamActions('s3Get*Tagging', { errorOnInvalidFormat: true })
//Uncaught Error: Invalid action format: s3Get*Tagging
By default, if a service is passed in that does not exist in the IAM data, it will be silently ignored and left out of the output. If you want to throw an error when a service is passed in that does not exist you can set this option to true
.
import { expandIamActions } from '@cloud-copilot/iam-expand';
//Ignore invalid service
expandIamActions('r2:Get*Tagging')
[]
//Throw an error on invalid service
expandIamActions('r2:Get*Tagging', { errorOnInvalidService: true })
//Uncaught Error: Service not found: r2
By default, if an action is passed in that does not exist in the IAM data, it will be silently ignored and left out of the output. There are two options to override this behavior: Error
and Include
.
import { expandIamActions, InvalidActionBehavior } from '@cloud-copilot/iam-expand';
//Ignore invalid action by default
expandIamActions('ec2:DestroyAvailabilityZone')
[]
//Ignore invalid action explicitly
expandIamActions('ec2:DestroyAvailabilityZone', { invalidActionBehavior: InvalidActionBehavior.Remove })
[]
//Throw an error on invalid action
expandIamActions('ec2:DestroyAvailabilityZone', { invalidActionBehavior: InvalidActionBehavior.Error })
//Uncaught Error: Invalid action: ec2:DestroyAvailabilityZone
//Include invalid action
expandIamActions('ec2:DestroyAvailabilityZone', { invalidActionBehavior: InvalidActionBehavior.Include })
['ec2:DestroyAvailabilityZone']
invertIamActions(actionString: string): string
will take an action string and return all actions not matching . For example s3:Get*Tagging
will return all actions from all services except those s3 actions that match the pattern Get*Tagging
.