Lambda Role Changes Don't Revoke Resource-Policy Invoke Access
A team revokes an external partner's Lambda access by editing the execution role, but the invoke grant actually lives in a separate resource-based policy and remains active until removed directly.
Operational summary
At a glance
- Symptom
- An AWS account team revokes an external partner's access to a production Lambda function by stripping a policy statement from the function's execution role…
- Likely cause
- AWS Lambda enforces two independent permission layers, and only one of them was touched.
- Impact
- The blast radius is high because the retained invoke grant keeps a function callable by a principal the team believed had been cut off, and every successful invocation…
- Verification signal
- Validation is complete only when the resource-based policy no longer lists the removed statement and the previously authorised principal is actually denied on attempted invocation.Re-run aws lambda get-policy…
- Safe correction
- The fix is to remove the specific invoke-permission statement from the function's resource-based policy, not to make further changes to the execution role.aws lambda remove-permission --function-name payment-webhook-handler --statement-id…
- Rollback or recovery
- Rollback means restoring the exact resource-based policy statement that was removed, using the JSON captured before the change.aws lambda add-permission --function-name payment-webhook-handler --statement-id partner-invoke-2024 --action lambda:InvokeFunction --principal --source-arn…
Symptom
An AWS account team revokes an external partner’s access to a production Lambda function by stripping a policy statement from the function’s execution role, yet the partner’s system keeps invoking the function successfully days later. CloudTrail shows continued lambda:InvokeFunction calls from the partner’s AWS account, and the security team cannot explain why a role change had no effect on who can call the function.
False Assumption
The team assumed AWS Lambda’s execution role is the single control point for access to a function, so removing an IAM policy statement from that role would also remove an external account’s ability to invoke it. This treats “who can call the function” and “what the function’s code is allowed to do once it runs” as the same permission boundary, when AWS Lambda keeps them separate.
Root Cause
AWS Lambda enforces two independent permission layers, and only one of them was touched. The execution role is an identity-based policy: it governs the actions the function’s own code can take against other AWS services, such as writing to CloudWatch Logs or reading from a database. Invocation rights are governed by a separate resource-based policy attached directly to the function, managed with add-permission and remove-permission or the console’s function-level permissions view. A cross-account or service-to-service invoke grant, once added to that resource-based policy, remains in force until it is explicitly removed from that policy, regardless of any change made to the execution role.
Impact
The blast radius is high because the retained invoke grant keeps a function callable by a principal the team believed had been cut off, and every successful invocation still runs with the function’s live execution-role permissions. In a production account handling partner integrations, this can mean continued unauthorised access, unexpected cost from unbilled or unexpected invocations, or execution of business logic against live data long after the team believes access has been withdrawn. The exposure persists silently because no error, alert or failed deployment signals that the wrong policy layer was edited.
Diagnosis
Confirm the split by inspecting both policy layers rather than only the one that was changed.
aws lambda get-policy --function-name payment-webhook-handler
The returned resource-based policy document lists every statement with its principal, action and any source-arn or source-account condition. Compare the statement IDs and principals against the list of invokers the team believes are authorised. A statement referencing the partner’s account or role ARN that the team never removed is direct evidence of the retained grant.
aws iam get-role --role-name payment-webhook-handler-role
This confirms the execution role change was applied correctly and shows that it has no bearing on the resource-based statements found above; the two outputs describe different, non-overlapping permission surfaces. The console equivalent is comparing the function’s “Permissions” resource-based policy tab against the execution role’s attached policies side by side.
Correction
The fix is to remove the specific invoke-permission statement from the function’s resource-based policy, not to make further changes to the execution role.
aws lambda remove-permission --function-name payment-webhook-handler --statement-id partner-invoke-2024
Before running this, save the exact JSON of the statement being removed from the earlier get-policy output. If any access should be retained for a narrower set of callers, add it back deliberately with an explicit --source-arn or --source-account condition rather than leaving a broad or unconditioned grant in place.
Validation
Validation is complete only when the resource-based policy no longer lists the removed statement and the previously authorised principal is actually denied on attempted invocation.
- Re-run
aws lambda get-policyand confirm the statement ID is absent from the output. - From an isolated test copy of the calling principal, not the live partner integration, attempt an invocation and confirm an AccessDeniedException is returned.
- Check CloudTrail or the function’s invocation logs over the following hours to confirm no further successful calls arrive from the removed principal.
Rollback
Rollback means restoring the exact resource-based policy statement that was removed, using the JSON captured before the change.
aws lambda add-permission --function-name payment-webhook-handler --statement-id partner-invoke-2024 --action lambda:InvokeFunction --principal <saved-principal> --source-arn <saved-source-arn>
Only restore the grant if removal turns out to have broken a legitimate, still-required integration; if the grant was correctly identified as unauthorised, do not restore it, and instead treat the rollback path as a documented option rather than a default action.
Prevention
Treat the resource-based policy and the execution role as two halves of one access-control decision, not sequential steps. Manage both under the same infrastructure-as-code definition, such as a single Lambda module that declares the execution role permissions and any resource-based policy statements together, so a review of one forces a review of the other. Schedule a periodic get-policy audit against the list of principals that should be able to invoke each production function, independent of any execution-role review, and require explicit sign-off before adding a new invoke grant with a broad or unconditioned principal.
Apply the safer control
Before you change production
Confirm the affected scope, export the current configuration, and test the replacement control in a non-production environment first.
Fix commands and configuration
lambda:InvokeFunctionadd-permissionremove-permissionVerify, roll back or escalate
Verify
Validation is complete only when the resource-based policy no longer lists the removed statement and the previously authorised principal is actually denied on attempted invocation.Re-run aws lambda get-policy and confirm the statement ID is absent from the output.From an isolated…
Rollback
Rollback means restoring the exact resource-based policy statement that was removed, using the JSON captured before the change.aws lambda add-permission --function-name payment-webhook-handler --statement-id partner-invoke-2024 --action lambda:InvokeFunction --principal <saved-principal> --source-arn <saved-source-arn>Only restore the grant if removal turns out to have broken…
Escalate
Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.