- 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 TypeScript, JavaScript, Python, Java, C#/.NET and Go 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, policy documents with size limits and splitting, 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.
It comes in two variants with the same API, released together with the same
version number in every language:
- **`iam-floyd`** (standalone) creates statements as JSON, for the AWS SDKs,
CloudFormation, Terraform or anything else that takes an IAM policy. The packages
for Python, Java, .NET and Go are native: no Node.js, no dependencies.
- **`cdk-iam-floyd`** creates AWS CDK v2 statements: every statement is an
`iam.PolicyStatement`. The packages for Python, Java, .NET and Go are built with
jsii, like the AWS CDK itself, and need Node.js.
The examples in this skill are TypeScript. The API is the same in every language,
with names adapted to the language, see [Other languages](#other-languages).
## Installation
| Language | `iam-floyd` | `cdk-iam-floyd` |
| ----------------------- | ------------------------------------------------ | --------------------------------------------------- |
| TypeScript / JavaScript | `npm install iam-floyd` | `npm install cdk-iam-floyd` |
| Python (3.9+) | `pip install iam-floyd` | `pip install cdk-iam-floyd` |
| Java (11+) | Maven `com.udondan:iam-floyd` | Maven `com.udondan:cdk-iam-floyd` |
| C# / .NET (8+) | `dotnet add package IAM.Floyd` | `dotnet add package CDK.IAM.Floyd` |
| Go (1.21+) | `go get udondan.github.io/iam-floyd/go/iamfloyd` | `go get udondan.github.io/iam-floyd/go/cdkiamfloyd` |
> **Pin the exact version** (e.g. `"iam-floyd": "0.850.0"` in `package.json`,
> `iam-floyd==0.850.0` for pip) — the package is pre-1.0.0 and the API may change.
> A new version is released every Monday. CDK v2 requires `cdk-iam-floyd >= 0.300.0`.
>
> The Go modules are not on GitHub, but on a module proxy under
> `udondan.github.io/iam-floyd/go`, which keeps the newest 30 releases. `go get`
> finds it without any configuration.
## Basic import
```typescript
// TypeScript / ESM
import { Statement } from 'iam-floyd';
import { Statement } from 'cdk-iam-floyd'; // CDK variant
// CommonJS
const { Statement } = require('iam-floyd');
```
The statement providers (`Ec2`, `S3`, ...) live in a sub-package or namespace
`statement`. The core classes (`Operator`, `Collection`, the policy documents,
`AwsManagedPolicy`, `AwsServicePrincipal`) live in the package itself:
| Language | `iam-floyd` | `cdk-iam-floyd` |
| -------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Python | `from iam_floyd import Statement` | `from cdk_iam_floyd import Statement` |
| Java | `com.udondan.iamFloyd.statement.Ec2`, core `com.udondan.iamFloyd.*` | `com.udondan.iamFloyd.cdk.statement.Ec2`, core `com.udondan.iamFloyd.cdk.*` |
| C# | `using Statement = IAM.Floyd.Statement;`, core `using IAM.Floyd;` | `using Statement = CDK.IAM.Floyd.Statement;`, core `using CDK.IAM.Floyd;` |
| Go | `.../go/iamfloyd/statement`, core `.../go/iamfloyd`, `.../collection` | `.../go/cdkiamfloyd/statement`, core and collection `.../go/cdkiamfloyd` |
In Go, both imports are needed: `statement.NewEc2(nil)` and
`iamfloyd.NewManagedPolicyDocument(...)`.
---
## Core vocabulary — the fluent chain
Every statement follows this readable pattern:
```text
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.
```typescript
new Statement.Ec2().allow().toStartInstances();
new Statement.Ec2().deny().toStopInstances();
```
### Actions — `to*()`
Every documented IAM action has a typed `to*()` method:
```typescript
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):
```typescript
new Statement.Ec2().allow().allReadActions().allListActions().compact();
// produces: ["ec2:Describe*", "ec2:Get*", ...]
```
**Missing action?** Use the generic `to()` escape hatch:
```typescript
.to('missingAction') // becomes ec2:missingAction
```
### Resources — `on*()`
Every resource type has an `on*()` method that builds the correct ARN:
```typescript
new Statement.S3()
.allow()
.allActions()
.onBucket('example-bucket')
.onObject('example-bucket', 'some/prefix/*');
new Statement.Lambda().allow().toInvokeFunction().onFunction('my-function'); // optional: account, region, partition params
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 `*`.
In **cdk-iam-floyd**, the `on*()` methods also take a CDK construct (L1 or L2) in place
of the name, if aws-cdk-lib has a reference interface for it (`aws-cdk-lib/interfaces`,
e.g. `IBucketRef`). The ARN of the construct is used; account, region and partition
are ignored:
```typescript
const bucket = new s3.Bucket(this, 'Bucket');
new Statement.S3().allow().toListBucket().onBucket(bucket);
new Statement.Lambda().allow().toInvokeFunction().onFunction(myFunction); // lambda.IFunction
```
**Override defaults for multiple resources** using `in*()` / `in()`:
```typescript
new Statement.Lambda()
.allow()
.toUpdateFunctionCode()
.inAccount('098765432109')
.inRegion('us-east-1')
.inPartition('aws')
.onFunction('my-function-1')
.onFunction('my-function-2')
// Shorthand:
.in('098765432109', 'us-east-1', 'aws')
.onFunction('my-function-1')
// Switch context mid-statement:
.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()`:
```typescript
.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*`.
```typescript
new Statement.Ec2()
.allow()
.toStartInstances()
.ifEncrypted() // Bool condition
.ifInstanceType(['t3.micro', 't3.nano']) // StringLike with multiple values
.ifAssociatePublicIpAddress(false) // Bool: false
.ifAwsRequestTag('Owner', 'alice'); // Tag condition
```
Multiple conditions on one statement are AND-ed together. For OR logic, use
multiple statements.
**Override the operator** (last argument):
```typescript
.ifAwsRequestTag('Env', '*John*', 'StringEquals')
// Or use the typed Operator class:
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):
```typescript
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()`:
```typescript
View on GitHub