When a serverless function is granted a blanket AdministratorAccess policy, the blast radius of a compromised function is the entire account. In 2025, a handful of high‑profile breaches were traced back to overly permissive IAM roles attached to Lambda, Cloud Functions, or Azure Functions. The fix is simple: reduce the scope of the role to the minimum set of actions required by the function. Below is a practical walkthrough that blends AWS, Azure, and GCP examples, with a focus on Linux sysadmins who are comfortable with CLI, JSON, and policy evaluation.
1. Identify the Function’s Real Dependencies
Start by cataloguing every resource the function touches. For a Lambda that processes S3 events, the dependencies are:
s3:GetObjecton the source buckets3:PutObjecton the destination bucketlogs:CreateLogGroup,logs:CreateLogStream,logs:PutLogEventsfor CloudWatch
Use the AWS CLI to list the function’s environment variables and the aws lambda get-function-configuration command to see attached layers and VPC settings. In Azure, az functionapp config appsettings list gives you the same view. GCP’s gcloud functions describe shows the service account used.
aws lambda get-function-configuration --function-name my-processor | jq '.Environment.Variables'
2. Create a Baseline Policy
Instead of starting from AdministratorAccess, draft a policy that lists only the actions identified. Use the AWS Policy Generator or the IAM Policy Simulator to validate the policy against the function’s code. A minimal policy for the Lambda example:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:s3:::source-bucket/*",
"arn:aws:s3:::destination-bucket/*",
"arn:aws:logs:*:*:*"
]
}
]
}
For Azure Functions, the equivalent is a custom role definition:
{
"Name": "MinimalFunctionRole",
"IsCustom": true,
"Description": "Least‑privilege role for function app",
"Actions": [
"Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read",
"Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write",
"Microsoft.Insights/diagnosticSettings/write"
],
"NotActions": [],
"AssignableScopes": ["/subscriptions/<sub-id>/resourceGroups/<rg>"]
}
3. Attach the Role to the Function
AWS: aws iam put-role-policy or create a new role and attach it.
aws iam create-role --role-name MyFunctionRole --assume-role-policy-document file://trust-policy.json
aws iam put-role-policy --role-name MyFunctionRole --policy-name MinimalLambdaPolicy --policy-document file://minimal-policy.json
aws lambda update-function-configuration --function-name my-processor --role arn:aws:iam::<acct-id>:role/MyFunctionRole
Azure: az role assignment create --assignee <app-id> --role "MinimalFunctionRole" --scope /subscriptions/<sub-id>/resourceGroups/<rg>
GCP: gcloud projects add-iam-policy-binding <project-id> --member=serviceAccount:<sa>@<project-id>.iam.gserviceaccount.com --role=roles/storage.objectAdmin
4. Verify Functionality
Run a test event and watch CloudWatch or Azure Monitor logs. If the function fails, the error message will point to a missing permission. Add the missing action to the policy, but keep the resource scope tight. For example, if the function needs to read from a new bucket, add s3:GetObject only for that bucket, not for all buckets.
5. Automate Policy Drift Detection
Over time, developers may add new dependencies without updating the IAM role. Use a CI pipeline that runs aws iam simulate-principal-policy against the function’s role and a set of test events. Fail the build if the simulation shows denied actions.
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::<acct-id>:role/MyFunctionRole \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::new-bucket/*
If the result is Deny, the policy needs updating.
6. Trade‑offs and Caveats
| Trade‑off | Practical Impact | Mitigation |
|---|---|---|
| Reduced flexibility | Function updates may require policy changes, adding overhead. | Use a lightweight policy‑management tool (e.g., Terraform modules) to version policies. |
| Potential for “permission creep” | Developers may grant broad permissions during debugging. | Enforce code reviews that check IAM changes; use Git hooks to flag AdministratorAccess. |
| Audit noise | Frequent policy updates can flood CloudTrail. | Aggregate changes into a single commit per feature; enable CloudTrail log filtering. |
| Cross‑region access | Some services need global permissions (e.g., CloudWatch). | Scope the policy to the necessary regions or use wildcard resources with caution. |
7. Common Pitfalls
- Assuming “least privilege” means “no permissions.” A function must have at least the permissions to read its own code and write logs.
- Over‑broad resource ARNs.
arn:aws:s3:::*grants access to every bucket. Narrow to the specific bucket or prefix. - Neglecting VPC access. If the function runs in a VPC, ensure the role has
ec2:DescribeNetworkInterfacesandec2:CreateNetworkInterfacepermissions. - Missing service‑specific actions. For example, DynamoDB requires
dynamodb:Queryanddynamodb:PutItem; forgetting one breaks the function.
8. Extending Hardening to the Host Level
Once the IAM role is tightened, the function’s runtime environment can be hardened further:
- Use a minimal base image (e.g.,
amazonlinux:2023-alpinefor Lambda) to reduce attack surface. - Enable runtime security tools like Falco or Sysdig to detect anomalous system calls.
- Apply SELinux or AppArmor profiles to the underlying host if the function runs in a container orchestrator (EKS, AKS, GKE).
9. Monitoring and Alerting
Set up CloudWatch Alarms for AWS::Lambda::Function metrics such as Errors and Duration. In Azure, use Azure Monitor alerts on FunctionApp logs. For GCP, configure Cloud Logging alerts. Combine these with IAM audit logs to detect unauthorized role changes.
aws cloudwatch put-metric-alarm \
--alarm-name LambdaErrorRate \
--metric-name Errors \
--namespace AWS/Lambda \
--statistic Sum \
--period 300 \
--threshold 1 \
--comparison-operator GreaterThanThreshold \
--alarm-actions arn:aws:sns:us-east-1:<acct-id>:LambdaAlerts
10. Final Checklist
- Role contains only required actions.
- Resource ARNs are as specific as possible.
- Function is tested after policy changes.
- CI pipeline simulates policy for new code.
- Monitoring alerts are in place for IAM changes and function errors.
- Host-level hardening (minimal image, runtime security, SELinux/AppArmor) is applied.
By following this workflow, a Linux sysadmin can bring serverless functions up to a defensible security posture without sacrificing agility. The key is to treat IAM roles as first‑class artifacts that evolve with the code, not as static permissions granted once and forgotten.
See also
- The Cost of Over‑Broad Retention: How a 30‑Day Backup Policy Exposed 2 TB of PHI
- Turning a Splunk dashboard into a hands‑on threat‑hunting playbook: a step‑by‑step recipe for real‑world detection agility
- Using Sysmon Event ID 1 Patterns to Spot Silent Credential Dumping in Windows 10 Workstations
- How to Disable Unnecessary Systemd Units with a One‑Line Command and Keep Logs Clean
- Freeing up 5 GB of disk space by pruning old journalctl logs on a 20 GB VPS