An AWS Lambda function needs to write objects to S3, and API Gateway needs to be able to invoke it. Which of Lambda's two permission mechanisms — the execution role or the function's resource-based policy — covers each, and how do they differ?
answer
- two directions, not one policy
- one governs outbound calls
- the other governs who invokes
- attached to the function, not a principal
- lambda:InvokeFunction plus aws:SourceArn
basics
~10 sThe execution role says what the function may do, so it grants the S3 write. The function's resource-based policy says who may invoke the function, so it grants API Gateway the lambda:InvokeFunction action.
solid answer
~50 sLambda has two independent permission sides. The **execution role** is an IAM role Lambda assumes on the function's behalf; its credentials are injected into the execution environment and picked up automatically by the AWS SDK, so anything the code calls outbound — `s3:PutObject`, `logs:PutLogEvents`, `dynamodb:PutItem` — must be allowed there. The **resource-based policy** is attached to the function itself and controls inbound invocation: you add a statement allowing the principal `apigateway.amazonaws.com` the `lambda:InvokeFunction` action, normally with an `aws:SourceArn` condition pinning it to your specific API so no one else's API can call your function. Adding a trigger in the console silently writes that statement for you; from the CLI it is `aws lambda add-permission`. For a cross-account call you need both sides: the caller's IAM policy must allow the invoke, and your function's resource policy must allow the caller.
code
json · 10 lines{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}go deeper
Be able to state the split in one sentence: the execution role is what the function may do, the resource-based policy is who may invoke it. Name lambda:InvokeFunction as the inbound action.
Explain the mechanics — Lambda assumes the role and injects temporary credentials the SDK picks up automatically, while the resource policy is managed through add-permission and lives on the function itself.
Show the operational judgment: pin every service-principal grant with aws:SourceArn, know that poller-based sources like SQS use the execution role instead, and recognise a missing logs permission from the symptom.
Own the fleet-level position — whether functions get per-function roles or shared ones, how resource policies are audited across accounts, and why source conditions are a standing guardrail rather than a per-function decision.
## The two sides of a function's permissions Every Lambda function sits between two permission questions, and conflating them is one of the most common interview stumbles: - **Outbound — what may this code do?** Answered by the *execution role*. - **Inbound — who may run this function?** Answered by the function's *resource-based policy*. They are separate documents, evaluated at different moments, by different callers. Neither one can substitute for the other. ## The execution role The execution role is a normal IAM role whose trust policy lets the Lambda service assume it — the principal is `lambda.amazonaws.com` with the `sts:AssumeRole` action. You attach it to the function once; from then on, every time Lambda spins up an execution environment it assumes that role and injects the resulting temporary credentials into the environment as `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN`. The AWS SDKs read those automatically from the default credential chain, which is why handler code that just does `boto3.client("s3").put_object(...)` works with no credentials in the source. So the S3 write in the question is an execution-role matter: the role needs a policy allowing `s3:PutObject` on the target bucket's objects. If it does not, the SDK call fails at runtime with `AccessDenied` — the function still ran, it just could not do its job. The most-used AWS managed policy here is `AWSLambdaBasicExecutionRole`, which grants only the CloudWatch Logs actions (`logs:CreateLogGroup`, `logs:CreateLogStream`, `logs:PutLogEvents`). A classic symptom of a missing or over-restricted execution role is a function that clearly executes but produces no log group at all — logging is not free, it is a permission your role happens to have. There is one important twist. For *poller-based* event sources — SQS queues, Kinesis and DynamoDB Streams — Lambda does not wait to be invoked; it polls the source on your behalf using the **execution role**. So the permission to read the queue (`sqs:ReceiveMessage`, `sqs:DeleteMessage`, `sqs:GetQueueAttributes`) belongs on the execution role, not in a resource policy on the function. Push sources (API Gateway, S3 notifications, SNS, EventBridge) are the opposite: they call `Invoke` against your function and therefore need the resource policy. ## The resource-based policy The resource-based policy is attached to the function, version or alias — not to any principal. It is the only way to let something *outside* your account, or an AWS service principal, invoke the function. You manage it with `aws lambda add-permission`, `remove-permission` and `get-policy`; there is no console editor for the raw document, which is why many engineers never notice it exists. When you click "Add trigger" in the console, the console writes the statement for you. A typical statement allows `lambda:InvokeFunction` to principal `apigateway.amazonaws.com`, conditioned on `aws:SourceArn` matching the ARN of your specific API and stage. That condition matters: without it, the statement says *any* API Gateway API, in any account, may invoke your function. All an attacker needs is your function ARN. This is the confused-deputy problem, and `aws:SourceArn` (or `aws:SourceAccount`) is the fix. The same applies to S3 notifications, SNS subscriptions and EventBridge rules. ```bash aws lambda add-permission \ --function-name process-upload \ --statement-id apigw-prod \ --action lambda:InvokeFunction \ --principal apigateway.amazonaws.com \ --source-arn 'arn:aws:execute-api:eu-west-1:111122223333:abc123/prod/POST/upload' ``` ## Cross-account invocation needs both When account B's role invokes a function in account A, two allows must line up: account B's identity policy must permit `lambda:InvokeFunction` on the function ARN, and account A's function resource policy must permit that principal. Either one missing produces a denial. This "both sides" rule is the same one that governs cross-account S3 access, and interviewers often ask it in whichever service you claim to know best. ## How to talk about it The crisp framing is directional. Execution role = outbound, identity-based, what the code may touch. Resource policy = inbound, attached to the function, who may pull the trigger. Then add the two nuances that show real use: poller-based sources invert the rule and live on the execution role, and every service-principal grant should carry a source condition.
- An SQS queue triggers your function. Where does the permission to read that queue live?On the execution role. SQS, Kinesis and DynamoDB Streams are poller-based: Lambda's event source mapping reads the source using your execution role's credentials, so it needs `sqs:ReceiveMessage`, `sqs:DeleteMessage` and `sqs:GetQueueAttributes`. No resource-based policy on the function is involved, because nothing is pushing an invoke at it — that is the reverse of how API Gateway or S3 notifications work.
- Why does AWS recommend an aws:SourceArn condition on a service-principal statement?Without it, the statement trusts the entire service, not your resource. Allowing `apigateway.amazonaws.com` to invoke with no condition means any API Gateway API in any AWS account can call your function if someone learns its ARN — the confused-deputy problem. `aws:SourceArn` pins the grant to one API and stage; `aws:SourceAccount` is the coarser fallback when the ARN is not known up front.
- A function runs successfully but writes nothing to CloudWatch Logs. What do you check first?The execution role. Logging is not automatic — the role needs `logs:CreateLogGroup`, `logs:CreateLogStream` and `logs:PutLogEvents`, which is exactly what the managed `AWSLambdaBasicExecutionRole` policy grants. If those are missing, or a permissions boundary strips them, the function executes and returns normally while its log group is never created, which looks alarmingly like the function was never invoked.
saying these in an interview costs you the question
- Says the execution role is what lets API Gateway invoke the function
- Claims a resource-based policy can grant the code S3 access
- Believes a console-created trigger adds no policy at all
- Grants a service principal invoke rights with no source condition
- Says cross-account invoke needs only the caller's IAM policy