Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions bedrock-iam-cost-allocation-cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
build
cdk.out
*.js
*.d.ts
2 changes: 2 additions & 0 deletions bedrock-iam-cost-allocation-cdk/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build
cdk.out
81 changes: 81 additions & 0 deletions bedrock-iam-cost-allocation-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Amazon Bedrock IAM Cost Allocation with Lambda

This pattern deploys two Lambda functions with separately tagged IAM roles that invoke Amazon Bedrock. Costs are automatically attributed to each IAM principal in AWS Cost and Usage Report (CUR 2.0) and Cost Explorer.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/bedrock-iam-cost-allocation-cdk

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.

## Requirements

* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Node and NPM](https://nodejs.org/en/download/) installed
* [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed

## How it works

Amazon Bedrock now supports cost allocation by IAM principal (April 2026). This pattern demonstrates:

- **Two Lambda functions** with separate IAM roles, each tagged with `team` and `cost-center`
- **Team A** (data-science, cost-center 10001) and **Team B** (engineering, cost-center 10002)
- Both invoke Bedrock — costs are automatically attributed to the calling IAM role in CUR 2.0
- After activating cost allocation tags in AWS Billing, you can filter by team in Cost Explorer

```
Team A Lambda (role: team=data-science) → Bedrock → CUR 2.0: attributed to Team A role
Team B Lambda (role: team=engineering) → Bedrock → CUR 2.0: attributed to Team B role
```

## Deployment Instructions

1. Clone the repository and navigate to the pattern directory:
```bash
git clone https://github.com/aws-samples/serverless-patterns
cd serverless-patterns/bedrock-iam-cost-allocation-cdk
```

2. Install dependencies:
```bash
npm install
```

3. Deploy the stack:
```bash
cdk deploy
```

## Testing

Invoke each team's function and compare costs in CUR 2.0:

```bash
# Team A invocation
aws lambda invoke --function-name <TeamAFunctionName> \
--payload '{"prompt":"Explain serverless cost optimization in 2 sentences"}' \
--cli-binary-format raw-in-base64-out output.json && cat output.json | python3 -m json.tool

# Team B invocation
aws lambda invoke --function-name <TeamBFunctionName> \
--payload '{"prompt":"What is Amazon Bedrock? Answer in 2 sentences"}' \
--cli-binary-format raw-in-base64-out output.json && cat output.json | python3 -m json.tool
```

## Activating Cost Allocation Tags

1. Open the [AWS Billing console](https://console.aws.amazon.com/billing/)
2. Navigate to **Cost allocation tags**
3. Find tags `team` and `cost-center` under the **IAM** category
4. Select and **Activate** them
5. Costs appear in Cost Explorer and CUR 2.0 within 24-48 hours

## Cleanup

```bash
cdk destroy
```

----
Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
6 changes: 6 additions & 0 deletions bedrock-iam-cost-allocation-cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { BedrockIamCostAllocationStack } from '../lib/bedrock-iam-cost-allocation-stack';

const app = new cdk.App();
new BedrockIamCostAllocationStack(app, 'BedrockIamCostAllocationStack');
9 changes: 9 additions & 0 deletions bedrock-iam-cost-allocation-cdk/cdk.context.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"acknowledged-issue-numbers": [
33623,
34635,
33623,
34635,
32775
]
}
3 changes: 3 additions & 0 deletions bedrock-iam-cost-allocation-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "npx ts-node bin/app.ts"
}
38 changes: 38 additions & 0 deletions bedrock-iam-cost-allocation-cdk/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"title": "Amazon Bedrock IAM Cost Allocation with Lambda",
"description": "Deploy tagged IAM roles for per-team Bedrock cost attribution in AWS Cost Explorer and CUR 2.0",
"language": "TypeScript",
"level": "200",
"framework": "CDK",
"introBox": {
"headline": "How it works",
"text": [
"This pattern creates two Lambda functions with separately tagged IAM roles that invoke Amazon Bedrock.",
"Each role is tagged with team and cost-center attributes. When the functions invoke Bedrock, costs are automatically attributed to the IAM principal in CUR 2.0.",
"After activating cost allocation tags in the AWS Billing console, you can filter and group Bedrock inference costs by team, project, or cost center in Cost Explorer."
]
},
"gitHub": {
"template": "https://github.com/aws-samples/serverless-patterns/tree/main/bedrock-iam-cost-allocation-cdk",
"templateURL": "serverless-patterns/bedrock-iam-cost-allocation-cdk"
},
"resources": {
"bullets": [
{ "text": "Introducing granular cost attribution for Amazon Bedrock", "link": "https://aws.amazon.com/blogs/machine-learning/introducing-granular-cost-attribution-for-amazon-bedrock/" },
{ "text": "IAM principal cost allocation documentation", "link": "https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/iam-principal-cost-allocation.html" }
]
},
"deploy": {
"text": ["cdk deploy"]
},
"cleanup": {
"text": ["cdk destroy"]
},
"authors": [
{
"name": "Nithin Chandran R",
"bio": "Technical Account Manager at AWS",
"linkedin": "nithin-chandran-r"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export class BedrockIamCostAllocationStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const modelId = new cdk.CfnParameter(this, 'BedrockModelId', {
type: 'String',
default: 'us.anthropic.claude-sonnet-4-20250514-v1:0',
description: 'Bedrock model ID (inference profile)',
});

// Team A role — tagged for cost allocation
const teamARole = new iam.Role(this, 'TeamABedrockRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')],
});
cdk.Tags.of(teamARole).add('team', 'data-science');
cdk.Tags.of(teamARole).add('cost-center', '10001');

teamARole.addToPolicy(new iam.PolicyStatement({
actions: ['bedrock:InvokeModel'],
resources: ['*'],
}));

// Team B role — tagged for cost allocation
const teamBRole = new iam.Role(this, 'TeamBBedrockRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')],
});
cdk.Tags.of(teamBRole).add('team', 'engineering');
cdk.Tags.of(teamBRole).add('cost-center', '10002');

teamBRole.addToPolicy(new iam.PolicyStatement({
actions: ['bedrock:InvokeModel'],
resources: ['*'],
}));

// Team A Lambda
const teamAFn = new lambda.Function(this, 'TeamAFunction', {
runtime: lambda.Runtime.NODEJS_22_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('src'),
role: teamARole,
timeout: cdk.Duration.seconds(30),
environment: {
MODEL_ID: modelId.valueAsString,
TEAM_NAME: 'data-science',
},
});

// Team B Lambda
const teamBFn = new lambda.Function(this, 'TeamBFunction', {
runtime: lambda.Runtime.NODEJS_22_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('src'),
role: teamBRole,
timeout: cdk.Duration.seconds(30),
environment: {
MODEL_ID: modelId.valueAsString,
TEAM_NAME: 'engineering',
},
});

new cdk.CfnOutput(this, 'TeamAFunctionName', { value: teamAFn.functionName });
new cdk.CfnOutput(this, 'TeamBFunctionName', { value: teamBFn.functionName });
new cdk.CfnOutput(this, 'CostAllocationNote', {
value: 'Activate cost allocation tags (team, cost-center) in AWS Billing console. Costs appear in CUR 2.0 within 24-48 hours.',
});
}
}
21 changes: 21 additions & 0 deletions bedrock-iam-cost-allocation-cdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "bedrock-iam-cost-allocation-cdk",
"version": "0.1.0",
"bin": {
"bedrock-iam-cost-allocation-cdk": "bin/app.js"
},
"scripts": {
"build": "tsc",
"cdk": "cdk"
},
"devDependencies": {
"@types/node": "22.7.9",
"aws-cdk": "2.1003.0",
"ts-node": "^10.9.2",
"typescript": "~5.6.3"
},
"dependencies": {
"aws-cdk-lib": "2.189.1",
"constructs": "^10.0.0"
}
}
28 changes: 28 additions & 0 deletions bedrock-iam-cost-allocation-cdk/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { BedrockRuntimeClient, InvokeModelCommand } = require('@aws-sdk/client-bedrock-runtime');

const client = new BedrockRuntimeClient();

exports.handler = async (event) => {
const prompt = event.prompt || 'What is Amazon Bedrock? Answer in 2 sentences.';
const teamName = process.env.TEAM_NAME || 'unknown';

const response = await client.send(new InvokeModelCommand({
modelId: process.env.MODEL_ID,
contentType: 'application/json',
accept: 'application/json',
body: JSON.stringify({
anthropic_version: 'bedrock-2023-05-31',
max_tokens: 256,
messages: [{ role: 'user', content: prompt }],
}),
}));

const body = JSON.parse(new TextDecoder().decode(response.body));
return {
statusCode: 200,
team: teamName,
costAllocationTags: { team: teamName, 'cost-center': process.env.COST_CENTER || 'tagged-on-role' },
response: body.content[0].text,
note: 'This invocation is attributed to the IAM role tagged with team and cost-center. Check CUR 2.0 for line_item_iam_principal.',
};
};
24 changes: 24 additions & 0 deletions bedrock-iam-cost-allocation-cdk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["es2020"],
"declaration": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"outDir": "./build",
"rootDir": "."
},
"exclude": ["node_modules", "build"]
}