| name | iam-floyd-usage |
| description | Expert guidance for using the iam-floyd and cdk-iam-floyd libraries to generate AWS IAM policy statements with a fluent, type-safe interface in Node.js/TypeScript projects. Use this skill whenever a user asks how to write IAM policies with iam-floyd, wants to allow or deny AWS actions, needs to scope policies to specific resources, wants to add conditions to IAM statements, is working on least-privilege IAM in AWS CDK, or is trying to understand the library's API (toXxx, onXxx, ifXxx, allow/deny, allActions, etc.). Also trigger for questions about semantic policy classes, subclassing Statement providers, using Collections, or converting JSON policies to iam-floyd code.
|
Using iam-floyd / cdk-iam-floyd
IAM Floyd is a fluent, type-safe AWS IAM policy statement generator. Instead of
writing error-prone JSON, you call strongly-typed methods and let the library build
the correct policy structure.
Installation
npm install iam-floyd
npm install cdk-iam-floyd
Pin the version in package.json (e.g. "iam-floyd": "0.762.0") — the package
is pre-1.0.0 and the API may change. CDK v2 requires cdk-iam-floyd >= 0.300.0.
Basic import
import { Statement } from 'iam-floyd';
import { Statement } from 'cdk-iam-floyd';
const { Statement } = require('iam-floyd');
Core vocabulary — the fluent chain
Every statement follows this readable pattern:
new Statement.<Service>() // pick the AWS service
.allow() | .deny() // effect (allow is the default)
.toXxx() // actions ("to do something")
.onXxx() // resources ("on a specific resource")
.ifXxx() // conditions ("if a condition is met")
Effect — allow() / deny()
The default effect is Allow, so .allow() is optional but improves readability.
new Statement.Ec2().allow().toStartInstances();
new Statement.Ec2().deny().toStopInstances();
Actions — to*()
Every documented IAM action has a typed to*() method:
new Statement.Ec2()
.allow()
.toStartInstances()
.toStopInstances()
.toDescribeInstances();
Bulk action helpers:
| Method | Effect |
|---|
allActions() | Adds ec2:* |
allListActions() | All actions with access level List |
allReadActions() | All actions with access level Read |
allWriteActions() | All actions with access level Write |
allPermissionManagementActions() | All permission-management actions |
allTaggingActions() | All tagging actions |
allMatchingActions('/pattern/flags') | Regex match (JS literal style, as string) |
allMatchingActions and access-level methods expand to an explicit list at
call time — they won't auto-update if AWS adds new actions later.
Use .compact() after access-level methods to collapse the list into wildcard
patterns (reduces policy size, but future AWS actions may match unexpectedly):
new Statement.Ec2().allow().allReadActions().allListActions().compact();
Missing action? Use the generic to() escape hatch:
.to('missingAction')
Resources — on*()
Every resource type has an on*() method that builds the correct ARN:
new Statement.S3()
.allow()
.allActions()
.onBucket('example-bucket')
.onObject('example-bucket', 'some/prefix/*');
new Statement.Lambda().allow().toInvokeFunction().onFunction('my-function');
new Statement.Ec2()
.allow()
.toStartInstances()
.toStopInstances()
.onInstance('i-1234567890abcdef0');
In cdk-iam-floyd, omitting account/region defaults to the CDK stack's values.
In iam-floyd (standalone), they default to *.
Override defaults for multiple resources using in*() / in():
new Statement.Lambda()
.allow()
.toUpdateFunctionCode()
.inAccount('098765432109')
.inRegion('us-east-1')
.inPartition('aws')
.onFunction('my-function-1')
.onFunction('my-function-2')
.in('098765432109', 'us-east-1', 'aws')
.onFunction('my-function-1')
.in('111111111111', 'eu-west-1', 'aws')
.onFunction('cross-account-function');
in*() methods set defaults only for resources added after them — call them
before the on*() calls they should affect.
Already have an ARN? Use the generic on():
.on('arn:aws:s3:::my-bucket', 'arn:aws:s3:::another-bucket')
Conditions — if*()
Every documented condition key has an if*() method. Global AWS conditions start
with ifAws*.
new Statement.Ec2()
.allow()
.toStartInstances()
.ifEncrypted()
.ifInstanceType(['t3.micro', 't3.nano'])
.ifAssociatePublicIpAddress(false)
.ifAwsRequestTag('Owner', 'alice');
Multiple conditions on one statement are AND-ed together. For OR logic, use
multiple statements.
Override the operator (last argument):
.ifAwsRequestTag('Env', '*John*', 'StringEquals')
import { Operator } from 'iam-floyd';
.ifAwsRequestTag('Env', '*John*', Operator.stringEquals)
Deny + condition operator trap: When using deny() with a tag condition, the
operator direction matters a lot. The default operator on most if*() methods is
StringLike. So .deny().toStopInstances().ifResourceTag('Owner', 'alice') means
"deny stop when the tag matches alice" — which denies the owner, not others.
To deny everyone except the owner, flip it:
.deny().toStopInstances().ifResourceTag('Owner', 'alice', Operator.stringNotEquals)
Complex operators (ForAllValues, ForAnyValue, IfExists):
import { Operator } from 'iam-floyd';
new Statement.Dynamodb()
.allow()
.toGetItem()
.onTable('Thread')
.ifAttributes(
['ID', 'Message'],
new Operator().stringEquals().forAllValues(),
);
new Statement.Ec2()
.allow()
.toStartInstances()
.ifAwsRequestTag(
'Environment',
['Prod', 'Dev'],
new Operator().stringEquals().ifExists(),
);
Missing condition? Use the generic if():
.if('ec2:missingCondition', 'some-value')
Principals — for*()
In cdk-iam-floyd, avoid building assume-role policies with iam-floyd — use
CDK's iam.PolicyDocument / iam.ManagedPolicy with a principal instead. iam-floyd's
for*() methods are mainly useful for resource-based policies (S3 bucket policies, etc.)
new Statement.Sts()
.allow()
.toAssumeRole()
.forAccount('123456789012')
.forUser('123456789012', 'Alice', 'Bob')
.forRole('123456789012', 'my-role')
.forService('lambda.amazonaws.com')
.forFederatedCognito()
.forPublic()
.for('arn:foo:bar');
new Statement.Sts()
.allow()
.toAssumeRole()
.forCdkPrincipal(
new iam.ServicePrincipal('sns.amazonaws.com'),
new iam.ServicePrincipal('lambda.amazonaws.com'),
);
NotAction / NotResource / NotPrincipal
Understand these well before using — they can be surprisingly permissive.
new Statement.S3()
.allow()
.notAction()
.toDeleteBucket()
.onBucket('example-bucket');
new Statement.S3()
.allow()
.notResource()
.toDeleteBucket()
.onBucket('example-bucket');
new Statement.S3()
.deny()
.allActions()
.notPrincipal()
.forUser('1234567890', 'Bob')
.onObject('example-bucket', '*');
Statement SID
new Statement.Ec2('AllowEC2ReadOnly').allow().allReadActions();
Building a complete policy
Standalone (iam-floyd)
import { Statement, Operator } from 'iam-floyd';
const policy = {
Version: '2012-10-17',
Statement: [
new Statement.Ec2()
.allow()
.toStartInstances()
.ifAwsRequestTag('Owner', '${aws:username}'),
new Statement.Ec2()
.deny()
.toStopInstances()
.ifResourceTag('Owner', '${aws:username}', Operator.stringNotEquals),
new Statement.Ec2().allow().allListActions().allReadActions(),
],
};
CDK (cdk-iam-floyd)
import { Statement } from 'cdk-iam-floyd';
import * as iam from 'aws-cdk-lib/aws-iam';
const policy = new iam.ManagedPolicy(this, 'Policy', {
statements: [
new Statement.S3()
.allow()
.toGetObject()
.toPutObject()
.onObject('my-bucket', '*'),
new Statement.S3().allow().toListBucket().onBucket('my-bucket'),
],
});
role.addToPolicy(
new Statement.Lambda().allow().toInvokeFunction().onFunction('my-function'),
);
In CDK, onFunction() / onBucket() etc. automatically use the stack's
account and region when you omit those parameters.
Collections — reusable statement groups
Collections are pre-built groups of statements for common scenarios:
import { Collection } from 'iam-floyd';
const policy = {
Version: '2012-10-17',
Statement: [...new Collection().allowEc2InstanceDeleteByOwner()],
};
Currently available: allowEc2InstanceDeleteByOwner (allows start/stop EC2
instances tagged with the caller's username).
Advanced: Semantic policy classes
A powerful pattern to bundle related permissions under a meaningful name. This
creates reusable, self-documenting policy building blocks.
import { Statement } from 'cdk-iam-floyd';
class MyEcr extends Statement.Ecr {
constructor() {
super();
}
toPushImage(): this {
return this.toGetAuthorizationToken()
.toBatchCheckLayerAvailability()
.toInitiateLayerUpload()
.toUploadLayerPart()
.toCompleteLayerUpload()
.toPutImage();
}
}
new MyEcr().allow().toPushImage().onRepository('my-repo');
Why this matters:
- The method name expresses business intent (
toPushImage), not raw AWS actions
- Reusable across stacks / projects without copy-pasting action lists
- The chain still works — all
on*(), if*() etc. methods are still available
You can do this for any grouping that makes sense in your domain:
class AppBucket extends Statement.S3 {
toReadApp(): this {
return this.toGetObject().toListBucket();
}
toWriteApp(): this {
return this.toPutObject().toDeleteObject();
}
}
Real-world CDK example: CFN deployment role
import { Statement } from 'cdk-iam-floyd';
const policy = {
Version: '2012-10-17',
Statement: [
new Statement.Cloudformation()
.allow()
.allActions(),
new Statement.All()
.allow()
.allActions()
.ifAwsCalledVia('cloudformation.amazonaws.com'),
new Statement.S3()
.allow()
.allActions()
.on('arn:aws:s3:::cdktoolkit-stagingbucket-*'),
new Statement.Account()
.deny()
.allPermissionManagementActions()
.allWriteActions(),
new Statement.Organizations()
.deny()
.allPermissionManagementActions()
.allWriteActions(),
],
};
Tips & gotchas
API reference
Full documentation: https://iam-floyd.readthedocs.io/en/latest/