AWS IAM Permissions Reference
This page lists the minimum IAM permissions each Benzene AWS package needs, along with the specific AWS SDK call in Benzene's source that drives the requirement. Use it to build a least-privilege execution role rather than reaching for a broad managed policy.
A couple of these are not obvious from AWS's own docs, so they're called out explicitly
below: several event sources (API Gateway, SNS, S3) invoke your Lambda function via a
resource-based permission on the function itself, not an identity-based IAM policy
on the execution role. Adding sqs:*-style permissions for those sources is a common
but unnecessary over-grant.
Lambda execution role baseline
Every Lambda function needs this regardless of event source, so CloudWatch Logs receives your function's logs:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
}
]
}
API Gateway (Benzene.Aws.Lambda.ApiGateway)
No execution-role IAM permissions are needed to receive requests. API Gateway
invokes your function via a resource-based Lambda permission
(AWS::Lambda::Permission with Principal: apigateway.amazonaws.com), configured on
the function itself — not via the execution role's IAM policy. If you're using the
custom authorizer (ApiGatewayCustomAuthorizerContext), the same applies to the
authorizer function.
SQS trigger (Benzene.Aws.Lambda.Sqs)
The Lambda event-source-mapping poller runs under your function's execution role and needs:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes"
],
"Resource": "arn:aws:sqs:REGION:ACCOUNT_ID:QUEUE_NAME"
}
]
}
sqs:DeleteMessage covers both full-batch success and the partial-batch-failure
reporting SqsApplication supports (see docs/getting-started-aws.md).
SNS trigger (Benzene.Aws.Lambda.Sns)
No execution-role IAM permissions are needed to receive notifications. Like API
Gateway, SNS invokes your function via a resource-based permission
(Principal: sns.amazonaws.com, scoped to the specific topic ARN as SourceArn), not
the execution role.
S3 trigger (Benzene.Aws.Lambda.S3)
No execution-role IAM permissions are needed to receive notifications. S3 invokes
via a resource-based permission (Principal: s3.amazonaws.com) plus a bucket
notification configuration — again, not the execution role.
Kafka trigger (Benzene.Aws.Lambda.Kafka)
Note: unlike the sections above, these permissions are configured by AWS's event source mapping rather than driven by an explicit SDK call in Benzene's code, so this section is the least directly source-verified — cross-check against current AWS documentation before using it in production, especially if AWS has changed the required actions since this was written.
For an MSK cluster:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kafka-cluster:Connect",
"kafka-cluster:DescribeGroup",
"kafka-cluster:AlterGroup",
"kafka-cluster:DescribeTopic",
"kafka-cluster:ReadData"
],
"Resource": [
"arn:aws:kafka:REGION:ACCOUNT_ID:cluster/CLUSTER_NAME/*",
"arn:aws:kafka:REGION:ACCOUNT_ID:topic/CLUSTER_NAME/*",
"arn:aws:kafka:REGION:ACCOUNT_ID:group/CLUSTER_NAME/*"
]
},
{
"Effect": "Allow",
"Action": [
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface",
"ec2:DescribeVpcs",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups"
],
"Resource": "*"
}
]
}
The ec2:* block is needed because MSK event source mappings run your function inside
the cluster's VPC. For self-managed (non-MSK) Kafka, you'll additionally need
secretsmanager:GetSecretValue if you're using SASL/SCRAM authentication via a Secrets
Manager secret.
Benzene.Clients.Aws.* outbound calls
These are the permissions your Lambda (or any host) needs when it sends messages via
the AWS outbound clients, as opposed to receiving them. Each client lives in its own
per-transport package (Benzene.Clients.Aws.Sqs, .Sns, .Lambda, .StepFunctions);
Benzene.Clients.Aws is a meta-package that pulls in all of them.
| Client | Package | Action | Source |
|---|---|---|---|
SqsBenzeneMessageClient |
Benzene.Clients.Aws.Sqs |
sqs:SendMessage |
src/Benzene.Clients.Aws.Sqs/SqsClientMiddleware.cs |
SnsBenzeneMessageClient |
Benzene.Clients.Aws.Sns |
sns:Publish |
src/Benzene.Clients.Aws.Sns/SnsClientMiddleware.cs |
AwsLambdaBenzeneMessageClient / AwsLambdaClient |
Benzene.Clients.Aws.Lambda |
lambda:InvokeFunction |
src/Benzene.Clients.Aws.Lambda/AwsLambdaClient.cs |
StepFunctionsClient |
Benzene.Clients.Aws.StepFunctions |
states:StartExecution |
src/Benzene.Clients.Aws.StepFunctions/StepFunctionsClient.cs |
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": "sqs:SendMessage", "Resource": "arn:aws:sqs:REGION:ACCOUNT_ID:QUEUE_NAME" },
{ "Effect": "Allow", "Action": "sns:Publish", "Resource": "arn:aws:sns:REGION:ACCOUNT_ID:TOPIC_NAME" },
{ "Effect": "Allow", "Action": "lambda:InvokeFunction", "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME" },
{ "Effect": "Allow", "Action": "states:StartExecution", "Resource": "arn:aws:states:REGION:ACCOUNT_ID:stateMachine:STATE_MACHINE_NAME" }
]
}
Only include the statements for the clients you actually use.
Benzene.Aws.Sqs standalone consumer
The polling worker (SqsConsumer.StartAsync, in src/Benzene.Aws.Sqs/Consumer/SqsConsumer.cs)
receives and deletes messages as a batch:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:ReceiveMessage",
"sqs:DeleteMessageBatch",
"sqs:GetQueueAttributes"
],
"Resource": "arn:aws:sqs:REGION:ACCOUNT_ID:QUEUE_NAME"
}
]
}
This is a separate package from the Lambda SQS trigger above — it's for a long-running
worker (e.g. behind Benzene.SelfHost/Benzene.HostedService) that polls SQS directly
rather than being invoked by a Lambda event source mapping.