How to guide AI agents to use your IAM permission boundaries
Provide AI agents with specific organizational context so that they can write infrastructure as code for IAM resources in the same way your organization does it.
AWS customers with multi-account strategy make use of permission boundaries when they provision developer roles and automation roles for individual AWS accounts. This is a great way to reduce permissions and limit exposure to privilege escalation. IAM permission boundaries, created by the platform teams, are enforced during the creation and modification of IAM roles with IAM conditions in policy.
When attached to the developer and CI/CD roles, the following IAM policy (PlatformPermissionBoundaryPolicy) helps ensure that the developers can create, modify, and update roles, but only when the permissions boundary policy is attached to the new role.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "EnforceIAMRoleActionsHaveBoundary", "Effect": "Deny", "Action": [ "iam:AttachRolePolicy", "iam:CreateRole", "iam:DetachRolePolicy", "iam:PutRolePolicy", "iam:DeleteRolePolicy", "iam:PutRolePermissionsBoundary" ], "Resource": "*", "Condition": { "StringNotLike": { "iam:PermissionsBoundary": "arn:aws:iam::*:policy/PlatformPermissionBoundaryPolicy" } } }, { "Sid": "DenyChangesToBoundaryPolicy", "Effect": "Deny", "Action": [ "iam:DeletePolicy", "iam:CreatePolicyVersion", "iam:CreatePolicy", "iam:DeletePolicyVersion", "iam:SetDefaultPolicyVersion" ], "Resource": "arn:aws:iam::*:policy/PlatformPermissionBoundaryPolicy" } ] }
This IAM best practice poses some challenges for developers using AI coding agents. Most public AWS infrastructure as code samples don't use permission boundaries this way, so when developers want to use out of the box code samples, or use AI agents trained on those samples, they often face permission errors.
Developers can use agent skills specification to teach their coding agents how to work with IAM roles in their organization which can save hours of debugging effort, and results in code that conforms to their organizational patterns.
How to create the skill
Agent skills specification follows a directory structure which contains a SKILL.md file with YAML front-matter for the skill to help AI agents decide when to activate this skill, followed by the markdown content. In this case the SKILL.md is the only file for this skill.
Create the file .skills/aws-iam-permission-boundary/SKILL.md with the following content:
--- name: aws-iam-permission-boundary description: > Enforces organization permission boundary policies on all IAM roles and users created through AWS infrastructure as code. Applies to CloudFormation, CDK, SAM, and Terraform. Use this skill whenever creating, modifying, or reviewing IAM resources to ensure every role includes the required permission boundary. compatibility: > Requires access to file-system read/write tools. Works with any IDE agent that can edit CloudFormation YAML/JSON, CDK TypeScript/Python, SAM YAML, or Terraform HCL files. metadata: category: security cloud: aws --- # AWS IAM Permission Boundary Skill ## Purpose Ensure every IAM role or user created through infrastructure as code includes the organization's permission boundary policy. Omitting the boundary will cause deployment failures and policy violations. ## Permission Boundary ARN Pattern All IAM roles and users MUST attach this permission boundary: ``` arn:aws:iam::<ACCOUNT_ID>:policy/PlatformPermissionBoundaryPolicy ``` Where: - `<ACCOUNT_ID>` is the AWS account ID (resolved at deploy time) ## Rules 1. Every `AWS::IAM::Role`, `AWS::IAM::User`, `aws_iam_role`, or `aws_iam_user` resource MUST have a `PermissionsBoundary` (CloudFormation/SAM/CDK) or `permissions_boundary` (Terraform) property set. 2. The boundary ARN must follow the pattern above exactly. 3. For constructs that create IAM roles implicitly (SAM `AWS::Serverless::Function`, `AWS::Serverless::StateMachine`, CDK L2/L3 constructs like `lambda.Function`), the boundary MUST be applied via `Globals` (SAM) or at the Stack/Stage level (CDK). 4. In Terraform, use a shared module or policy-as-code tool to enforce boundaries since there is no provider-level default. ## How to Apply When you encounter or generate IAM resources, check for the permission boundary. If missing, add it. If present, verify it matches the pattern. Flag any hardcoded account names or IDs. ## Examples ### CloudFormation / SAM ```yaml Resources: MyRole: Type: AWS::IAM::Role Properties: RoleName: my-role PermissionsBoundary: !Sub 'arn:aws:iam::${AWS::AccountId}:policy/PlatformPermissionBoundaryPolicy' AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ``` ### AWS CDK (TypeScript) ```typescript import * as cdk from 'aws-cdk-lib'; import * as iam from 'aws-cdk-lib/aws-iam'; const boundary = iam.ManagedPolicy.fromManagedPolicyArn( stack, 'PermissionBoundary', `arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:policy/PlatformPermissionBoundaryPolicy`, ); const role = new iam.Role(stack, 'MyRole', { roleName: `my-role`, assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), permissionsBoundary: boundary, }); ``` ### AWS CDK (Python) ```python from aws_cdk import ( CfnParameter, Tags, Aws, aws_iam as iam, ) boundary = iam.ManagedPolicy.from_managed_policy_arn( stack, "PermissionBoundary", f"arn:aws:iam::{Aws.ACCOUNT_ID}:policy/PlatformPermissionBoundaryPolicy", ) role = iam.Role( stack, "MyRole", role_name=f"my-role", assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"), permissions_boundary=boundary, ) ``` ### Terraform ```hcl data "aws_caller_identity" "current" {} resource "aws_iam_role" "my_role" { name = "my-role" permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/PlatformPermissionBoundaryPolicy" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } Action = "sts:AssumeRole" }] }) } ``` ## Implicit IAM Roles (Stack-Level Boundaries) Some IaC constructs create IAM roles automatically without the user declaring an `AWS::IAM::Role` resource. You MUST still ensure the permission boundary is applied. ### SAM — `Globals` Section `AWS::Serverless::Function` and `AWS::Serverless::StateMachine` both create execution roles implicitly. Use the `Globals` section to apply the boundary to every function and state machine in the template at once: ```yaml Globals: Function: PermissionsBoundary: !Sub 'arn:aws:iam::${AWS::AccountId}:policy/PlatformPermissionBoundaryPolicy' StateMachine: PermissionsBoundary: !Sub 'arn:aws:iam::${AWS::AccountId}:policy/PlatformPermissionBoundaryPolicy' Resources: MyFunction: Type: AWS::Serverless::Function Properties: Runtime: python3.12 Handler: app.handler CodeUri: ./src # No explicit Role — SAM creates one with the boundary from Globals ``` You can also set `PermissionsBoundary` on individual `AWS::Serverless::Function` or `AWS::Serverless::StateMachine` resources. This only works when SAM generates the role (i.e. you have NOT set the `Role` property). ### CDK — `PermissionsBoundary` on Stack or Stage CDK L2/L3 constructs (e.g. `lambda.Function`, `stepfunctions.StateMachine`) create IAM roles internally. Use `PermissionsBoundary` at the Stack or Stage level to automatically apply the boundary to ALL roles and users created within that scope: TypeScript: ```typescript import { App, Stack, PermissionsBoundary } from 'aws-cdk-lib'; const app = new App(); // Apply to an entire Stage (recommended) const prodStage = new Stage(app, 'Prod', { permissionsBoundary: PermissionsBoundary.fromName('PlatformPermissionBoundaryPolicy'), }); // Or apply to a single Stack const stack = new Stack(app, 'MyStack', { permissionsBoundary: PermissionsBoundary.fromName('PlatformPermissionBoundaryPolicy'), }); ``` Python: ```python from aws_cdk import App, Stack, Stage, PermissionsBoundary app = App() # Apply to an entire Stage (recommended) prod_stage = Stage(app, "Prod", permissions_boundary=PermissionsBoundary.from_name("PlatformPermissionBoundaryPolicy"), ) # Or apply to a single Stack stack = Stack(app, "MyStack", permissions_boundary=PermissionsBoundary.from_name("PlatformPermissionBoundaryPolicy"), ) ``` `PermissionsBoundary.fromName(name)` builds the full ARN using the stack's account. Use `PermissionsBoundary.fromArn(arn)` if you need to specify the full ARN explicitly. The ARN supports placeholders: `${AWS::Partition}`, `${AWS::Region}`, `${AWS::AccountId}`, `${Qualifier}`. ### Terraform — No Provider-Level Equivalent Terraform does not have a provider-level setting to auto-apply permission boundaries. Every `aws_iam_role` and `aws_iam_user` resource must explicitly include `permissions_boundary`. Use a shared Terraform module to enforce this consistently: ```hcl # modules/iam-role/main.tf variable "name" { type = string } variable "assume_role_policy" { type = string } variable "permissions_boundary_arn" { type = string default = null } data "aws_caller_identity" "current" {} locals { boundary = coalesce( var.permissions_boundary_arn, "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/PlatformPermissionBoundaryPolicy" ) } resource "aws_iam_role" "this" { name = var.name assume_role_policy = var.assume_role_policy permissions_boundary = local.boundary } ``` Alternatively, use an OPA/Sentinel policy or a `tflint` rule to detect any `aws_iam_role` missing `permissions_boundary`. ## Common Mistakes to Catch - IAM role or user created without any `PermissionsBoundary` / `permissions_boundary` - Boundary ARN with a hardcoded account ID instead of a dynamic reference - SAM template with `AWS::Serverless::Function` but no `PermissionsBoundary` in `Globals` or on the resource itself - CDK stack creating L2/L3 constructs (e.g. `lambda.Function`) without `permissionsBoundary` set on the Stack or Stage - Terraform module creating `aws_iam_role` without `permissions_boundary`
Conclusion
The example skill helps agents discover how to utilize permission boundaries when they are writing infrastructure code that creates or modifies IAM roles. This skill is focused on permissions boundary implementation across IaC tools. You can modify the policy naming to match your setup. You can also simplify to only include examples for the IaC tools within your organization. You can also extend the skill to include other patterns in the the organization such as resource naming conventions and required tagging strategy.
- Language
- English
Relevant content
- asked 7 months ago
