Vocabulary

Attention

This is an early version of the package. The API might change when new features are implemented. Therefore make sure you use an exact version in your package.json/requirements.txt before it reaches 1.0.0.

IAM Floyd provides a fluid interface and enables you to define policy statements in a human readable and easy to understand phrase.

allow | deny (Effect)

The methods allow() and deny() control the Effect of the statement.

The default effect of any statement is Allow, so it’s not mandatory to add either of these methods to the method chain. Though it is recommended to improve readability:

const s1 = new Statement.Ec2() //
  .allow()
  .toStartInstances();

const s2 = new Statement.Ec2() //
  .deny()
  .toStopInstances();

to (Action)

Every available IAM action is represented by a distinct method. These methods start with to. You allow/deny to do something

new Statement.Ec2() //
  .allow()
  .toStartInstances()
  .toStopInstances()

In case of missing actions, you can just add any action key yourself via to():

new Statement.Ec2() //
  .allow()
  .to('missingAction')

all (Action)

While methods starting with to add a single action to a statement, methods starting with all add multiple actions.

allActions

This method adds all actions of the related service to the statement, e.g. ec2:*

new Statement.Ec2() //
  .allow()
  .allActions()

allMatchingActions

Adds all actions matching regular expressions to the statement.

Attention

The list of actions is compiled at run time. The generated statement object contains an exact list of actions that matched when you build it. If AWS later adds/removes actions that would match the regular expression, you need to re-generate the statements.

The regular expressions need to be in Perl/JavaScript literal style and need to be passed as strings:

new Statement.Ec2() //
  .deny()
  .allMatchingActions('/vpn/i')

Access levels

To add all actions of a certain access level to the statement use the below methods.

Attention

The list of actions is compiled at run time. The generated statement object contains an exact list of actions that matched when you build it. If AWS later adds/removes actions or changes the level, you need to re-generate the statements.

Note

When working with access levels the policy size limits may be exceeded quickly, just because there are so many actions available for some services like EC2.

In these cases you should use the compact method, to compile the action list to a list of wildcard patterns.

allListActions

Adds all actions with access level list to the statement.

new Statement.S3() //
  .allow()
  .allListActions()

allReadActions

Adds all actions with access level read to the statement.

new Statement.S3() //
  .allow()
  .allReadActions()

allWriteActions

Adds all actions with access level write to the statement.

new Statement.S3() //
  .allow()
  .allWriteActions()

allPermissionManagementActions

Adds all actions with access level permission management to the statement.

new Statement.S3() //
  .allow()
  .allPermissionManagementActions()

allTaggingActions

Adds all actions with access level tagging to the statement.

new Statement.S3() //
  .allow()
  .allTaggingActions()

if (Condition)

Every available IAM condition key is represented by a distinct method. These methods start with if. You allow/deny something if a condition is met.

Every statement provider (e.g. Ec2) brings its unique conditions. Global condition context keys start with ifAws.

Note

Multiple conditions on a statement all have to be true.

When you have multiple values on a single condition, one of them has to be true.

Other than that, IAM has no concept of OR. You need to define multiple statements for each OR branch.

new Statement.Ec2()
  .allow()
  .toStartInstances()
  .ifEncrypted()
  .ifInstanceType(['t3.micro', 't3.nano'])
  .ifAssociatePublicIpAddress(false)
  .ifAwsRequestTag('Owner', 'John')

Every if method has a default operator. For instance, conditions which operate on strings usually have StringLike as default. Most methods allow you to pass an operator as last argument.

Note

Operators can be passed as string, though it is recommended to use the Operators provided by the package.

new Statement.Ec2()
  .allow()
  .toStartInstances()
  .ifAwsRequestTag('TagWithSpecialChars', '*John*', 'StringEquals')

In case of missing conditions, you can define just any condition yourself via if():

new Statement.Ec2()
  .allow()
  .toStartInstances()
  .if('ec2:missingCondition', 'some-value')

on (Resource)

Every available IAM resources key is represented by a distinct method. These methods start with on. You allow/deny something on a specific resource (or pattern).

new Statement.S3()
  .allow()
  .allActions()
  .onBucket('example-bucket')
  .onObject('example-bucket', 'some/path/*')

In case of missing resources or if you already have an ARN ready, use the on() method:

new Statement.S3() //
  .allow()
  .allActions()
  .on(
    'arn:aws:s3:::example-bucket', //
    'arn:aws:s3:::another-bucket',
  )

Non-global resource ARNs contain the region and/or account. Generally all ARNs contain the partition. In cdk-iam-floyd the account, region and partition default to the values provided by the stack. In iam-floyd the partition defaults to aws and the account and region default to *.

The on*() methods take optional parameters to override the default values:

new Statement.Lambda()
  .allow()
  .toUpdateFunctionCode()
  .onFunction('my-function', '098765432109', 'us-east-1', 'aws')

If you want to override the defaults for the whole statement, see in (ARN defaults).

in (ARN defaults)

The on* methods generate ARNs which contain partition and potentially region and account. The in()* methods can be used to override the defaults for all consecutively added resources. You allow/deny something on resources in a specific account, region and partition.

Note

The in*() methods do not by themselves modify the statement. They just set the defaults for the resource added consecutively to the statement. Therefore make sure to call the in*() methods before adding resources via on*().

  new Statement.Lambda()
    .allow()
    .toUpdateFunctionCode()
    .inAccount('098765432109')
    .inRegion('us-east-1')
    .inPartition('aws')
    .onFunction('my-function-1')
    .onFunction('my-function-2')

There also is a shorthand function to set all defaults at once:

  new Statement.Lambda()
    .allow()
    .toUpdateFunctionCode()
    .in('098765432109', 'us-west-1', 'aws')
    .onFunction('my-function-1')
    .onFunction('my-function-2')

Since these methods set defaults for consecutively added resources, you can also override the defaults for additional resource in the same statement:

  new Statement.Lambda()
    .allow()
    .toUpdateFunctionCode()
    .in('098765432109', 'us-west-1', 'aws')
    .onFunction('my-function-1')
    .in('123456789012', 'us-east-1', 'aws')
    .onFunction('my-function-2')

for (Principal)

Note

If you use the CDK variant of the package, don’t attempt to create an assume policy with this package. Assume policies have to be of type IPrincipal and can easily be created with the iam package.

Every possible principal is represented by a distinct method. These methods start with for. You allow/deny something for a specific principal.

const s1 = new Statement.Sts()
  .allow()
  .toAssumeRole()
  .forAccount('1234567890');

const s2 = new Statement.Sts()
  .allow()
  .toAssumeRoleWithSAML()
  .forService('lambda.amazonaws.com');

const s3 = new Statement.Sts()
  .allow()
  .toAssumeRole()
  .forUser('1234567890', 'Bob');

const s4 = new Statement.Sts()
  .allow()
  .toAssumeRole()
  .forRole('1234567890', 'role-name');

const s5 = new Statement.Sts()
  .allow()
  .toAssumeRoleWithSAML()
  .forFederatedCognito();

const s6 = new Statement.Sts()
  .allow()
  .toAssumeRoleWithSAML()
  .forFederatedAmazon();

const s7 = new Statement.Sts()
  .allow()
  .toAssumeRoleWithSAML()
  .forFederatedGoogle();

const s8 = new Statement.Sts()
  .allow()
  .toAssumeRoleWithSAML()
  .forFederatedFacebook();

const s9 = new Statement.Sts()
  .allow()
  .toAssumeRoleWithSAML()
  .forSaml('1234567890', 'saml-provider');

const s10 = new Statement.Sts() //
  .allow()
  .toAssumeRole()
  .forPublic();

const s11 = new Statement.Sts()
  .allow()
  .toAssumeRole()
  .forAssumedRoleSession('123456789', 'role-name', 'session-name');

const s12 = new Statement.Sts()
  .allow()
  .toAssumeRole()
  .forCanonicalUser('userID');

const s13 = new Statement.Sts() //
  .allow()
  .toAssumeRole()
  .for('arn:foo:bar');

Some of the for* methods accept multiple values at once:

const s1 = new Statement.Sts()
  .allow()
  .toAssumeRole()
  .forAccount('1234567890', '0987654321');

// when you already have a list:
const accounts = ['1234567890', '0987654321'];
const s2 = new Statement.Sts()
  .allow()
  .toAssumeRole()
  .forAccount(...accounts);

const s3 = new Statement.Sts()
  .allow()
  .toAssumeRole()
  .forUser('1234567890', 'Bob', 'John');

// when you already have a list:
const users = ['Bob', 'John'];
const s4 = new Statement.Sts()
  .allow()
  .toAssumeRole()
  .forUser('1234567890', ...users);

The CDK variant of the package has an additional method forCdkPrincipal, which takes any number of iam.IPrincipal objects:

new Statement.Sts()
  .allow()
  .toAssumeRole()
  .forCdkPrincipal(
    new iam.ServicePrincipal('sns.amazonaws.com'),
    new iam.ServicePrincipal('lambda.amazonaws.com'),
  )

not (notAction, notResource and notPrincipal)

Warning

Make sure, you well understand the concepts of notAction, notResource and notPrincipal. This is where things quickly go wrong, especially when used in combination.

notAction

Switches the policy provider to use NotAction.

new Statement.S3()
  .allow()
  .notAction()
  .toDeleteBucket()
  .onBucket('example-bucket')

notResource

Switches the policy provider to use NotResource.

new Statement.S3()
  .allow()
  .notResource()
  .toDeleteBucket()
  .onBucket('example-bucket')

notPrincipal

Switches the policy provider to use NotPrincipal.

new Statement.S3()
  .deny()
  .allActions()
  .notPrincipal()
  .forUser('1234567890', 'Bob')
  .onObject('example-bucket', '*')

compact

This method can be used to convert a list of actions down to a list of wildcard patterns. This can be handy to reduce the policy size, especially when you work with Access levels.

Attention

When AWS later adds new actions, the patterns might match additional actions.

new Statement.Ec2() //
  .allow()
  .allReadActions()
  .allListActions()
  .compact()