Identity, Networking, and Security Foundations
Build secure foundations across IAM, VPC, encryption, and access controls for production serverless systems.
Content
IAM principals, policies, and permission boundaries
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Identity, Networking, and Security Foundations
IAM Principals, Policies, and Permission Boundaries (a.k.a. Who Are You and How Dangerous Are You?)
"Security is just authorization with commitment issues." — a grumpy IAM console, probably
You already met our Serverless Blueprint squad (API Gateway, Lambda, SQS, RDS, SES) and practiced local dev and secret-wrangling. Now we’re asking the question everything in AWS secretly asks you at 3am: who are you, and what are you allowed to touch?
This chapter is about the IAM brain-stem that powers your serverless app:
- Principals: the identities (humans, roles, and sometimes AWS services wearing funny hats)
- Policies: the scripts telling identities what they can/can’t do
- Permission boundaries: the bouncer that looks at both your ID and your vibes and still says "not with those shoes"
1) The Cast: Principals
In AWS, a principal is the thing that calls an API. That “thing” can be:
- IAM user: A long-lived human identity (please prefer SSO/OIDC + roles now).
- IAM role: Short-lived, assumable identity for apps and services (e.g., Lambda execution role).
- Federated identity: You log in via SSO/IdP; STS gives you temporary creds as a role session.
- Service principals: AWS services themselves (e.g., lambda.amazonaws.com) assuming roles.
Think of a principal like a temporary superhero cape (a role session) pinned onto someone (user, service) with a name tag (ARN) and optional flair (session tags).
Trust policy vs. permission policy
- Trust policy: attached to a role. Says who can become the role (sts:AssumeRole).
- Permission (identity) policy: attached to a user/role. Says what this principal can do.
Example: a basic Lambda execution role trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}
]
}
2) The Scripts: Policies (Identity vs Resource vs Boundaries)
Policies are JSON permission spells. Each statement has Effect (Allow/Deny), Action, Resource, and optional Condition.
- Identity-based policy: attached to a user/role. Example: your Lambda execution role allowing sqs:SendMessage.
- Resource-based policy: attached to the thing itself (S3 bucket policy, SQS queue policy, API Gateway resource policy, SES sending authorization). These can explicitly name a Principal and allow cross-account without assuming roles.
- Permission boundary: a cap on the max permissions a role/user can get. It never grants by itself; it only restricts. Think: the "You must be this tall to ride" sign, but for power.
- Session policy: optional extra policy added at session time (federation/STS) to further restrict.
- SCP (Service Control Policy): org-level governor. If SCP says "no," it’s no for everyone in that account/OU.
Quick comparison table
| Thing | Attached to | Grants or limits? | Cross-account? | Use for |
|---|---|---|---|---|
| Identity policy | User/Role | Grants | Indirect (via role assumptions) | Daily least-privilege perms |
| Resource policy | Resource | Grants (to named principals) | Yes | Cross-account access, public blocks |
| Permission boundary | User/Role | Limits | N/A | Guardrails for builders |
| Session policy | Session | Limits | N/A | Temporary, extra-tight sessions |
| SCP | Account/OU | Limits | Org-wide | Global guardrails |
3) The Plot Twist: How Authorization Actually Evaluates
AWS evaluates permissions like a strict parent with a flowchart:
- Start with implicit deny (everything is no).
- If any applicable policy has an explicit Deny, final answer = Deny.
- Otherwise, look for an Allow from either identity or resource policy.
- If there’s an Allow, check boundaries: permission boundaries, session policies, SCPs, and service-specific guards must also Allow.
- If any of these limits say "no," final answer = Deny.
- If all checks pass, you get your Allow.
TL;DR: One Deny ruins the party. Allows must survive all gatekeepers.
4) Our Serverless Blueprint, But With Security Goggles
We’re building on the reference backend (API Gateway → Lambda → SQS/RDS/SES). Let’s wire the permissions like we actually care about prod still being alive tomorrow.
a) Lambda execution role (identity policy)
Keep it scoped by environment and tags you already manage from our secrets/environments module.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["sqs:SendMessage"],
"Resource": "arn:aws:sqs:us-east-1:123456789012:orders-${Environment}*",
"Condition": {
"StringEquals": {"aws:ResourceTag/Environment": "${Environment}"}
}
},
{
"Effect": "Allow",
"Action": ["rds-data:ExecuteStatement"],
"Resource": ["arn:aws:rds:us-east-1:123456789012:cluster:app-${Environment}"],
"Condition": {
"StringEquals": {"aws:PrincipalTag/Environment": "${Environment}"}
}
}
]
}
Note the ABAC vibe: resource and principal tags aligning with Environment.
b) SQS queue policy (resource-based)
If Lambda consumes SQS (event source), SQS needs to trust the Lambda service—optionally pinned to your function.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowLambdaPoll",
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": [
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes",
"sqs:ChangeMessageVisibility"
],
"Resource": "arn:aws:sqs:us-east-1:123456789012:orders-prod",
"Condition": {
"ArnEquals": {"aws:SourceArn": "arn:aws:lambda:us-east-1:123456789012:function:orders-processor"}
}
}
]
}
Identity policy says Lambda can use SQS; resource policy says SQS will accept calls from Lambda. Consent on both sides. Cute.
c) API Gateway resource policy (optional perimeter)
Want your private API to only accept calls from a specific VPC endpoint? Do it at the resource layer:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:us-east-1:123456789012:abc123/*/*/*",
"Condition": {"StringNotEquals": {"aws:SourceVpce": "vpce-0cafe"}}
}
]
}
An explicit Deny at the edge is like a moat with crocodiles wearing "Zero Trust" t-shirts.
d) SES sending authorization (resource policy)
Let only a specific role send from a domain identity:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:role/svc/ses-sender"},
"Action": ["ses:SendEmail", "ses:SendRawEmail"],
"Resource": "arn:aws:ses:us-east-1:123456789012:identity/example.com"
}
]
}
5) Permission Boundaries: Your Safety Net With Teeth
Boundaries are how you let developers move fast without becoming IAM sorcerers. You give them identity policies (what they need), and you attach a boundary (what they may never exceed).
Imagine you allow devs to create/update Lambda and pass only certain roles:
Permission boundary (attached to their role):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RegionsWeBlessed",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["us-east-1", "us-west-2"]
}
}
},
{
"Sid": "LetThemLambda",
"Effect": "Allow",
"Action": [
"lambda:CreateFunction",
"lambda:UpdateFunctionCode",
"lambda:UpdateFunctionConfiguration",
"lambda:PublishVersion",
"lambda:CreateAlias",
"lambda:UpdateAlias"
],
"Resource": "arn:aws:lambda:*:*:function:svc-*"
},
{
"Sid": "PassOnlyServiceRoles",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::*:role/svc/*"
},
{
"Sid": "NoIAMAdminEver",
"Effect": "Deny",
"Action": ["iam:*", "organizations:*"],
"Resource": "*"
}
]
}
Now the devs’ identity policy can say "Allow lambda:CreateFunction"—but they still can’t create random roles or deploy in eu-north-1. The boundary set the sandbox walls.
Pro-tip: Combine with tags and AWS::Serverless transform in CloudFormation/SAM to stamp Environment and Team tags on everything. Then write boundaries and policies that check aws:PrincipalTag and aws:ResourceTag. That’s ABAC—Attribute-Based Access Control—and it vibes perfectly with multi-env setups.
6) SAM/CloudFormation Patterns You’ll Actually Use
- Model trust and permissions separately. In templates, define the role with a tightly-scoped trust policy and attach the minimal identity policy inline or via managed policies.
- Use Path/Prefix conventions: e.g., roles under path /svc/ for pass-role filters.
- Per-environment stacks: dev, stage, prod. Parameterize Environment tag and include it in Conditions for IAM.
- SAM policy templates are great to start, but they’re spicy generalists—override with explicit Resource and Condition where possible.
- For cross-account access (e.g., CI/CD account deploying to prod), prefer: CI role assumes a deployer role in prod; prod resources have resource policies if needed (e.g., S3, SQS) or trust policy on deployer role with ExternalId.
7) Classic Pitfalls (A Comedy of Denies)
- AccessDenied with "not authorized" but your policy looks right? Check:
- Is there a permission boundary?
- Is the org SCP denying it?
- Missing resource policy for a cross-account call?
- Are you using the correct region? (aws:RequestedRegion strikes again)
- iam:PassRole blocked: your identity policy might Allow, but boundary or SCP forbids. Also ensure the role’s trust policy allows your principal to assume it (or the service to use it).
- Lambda-to-SQS confusion: you granted the role sqs:ReceiveMessage, but forgot the SQS queue policy. Resource must allow the service too.
- Star parties: Action: "" or Resource: "" in prod. Please no. Pair with Conditions at minimum.
- Conditions are case-sensitive and picky. Use policy simulator when in doubt.
8) A 60-Second Recipe To Lock Down Your Serverless
- For each Lambda, create a dedicated execution role with a tight trust policy (only lambda.amazonaws.com).
- Attach minimal identity policies with Resource and Condition scoping by tags/env.
- Add resource policies where needed (SQS, API Gateway, SES).
- For builders, attach a permission boundary that:
- Limits regions
- Limits iam:PassRole to /svc/
- Allows only the services they need
- Sprinkles some explicit Denies for high-risk APIs
- Validate with IAM Access Analyzer and policy simulator.
Closing: The Security Mantra
Who can do what, to which resource, under which conditions, and within which boundaries?
If you can answer that sentence for a call path—API Gateway → Lambda → SQS/RDS/SES—you can reason about its security like a pro. It’s not about memorizing every IAM action; it’s about structuring trust and permissions so the happy path is obvious and the risky path is impossible.
Key takeaways:
- Principals wear permissions via identity policies; resources sometimes talk back with resource policies.
- Permission boundaries don’t grant—they cap. Identity Allow ∧ Boundary Allow ∧ No Deny = Success.
- Tag-driven ABAC scales across environments and teams.
- In serverless, most "why does this 403?" mysteries are solved by checking trust policy, resource policy, and boundaries in that order.
Go forth and make your policies so clear your future self tears up a little. You’re not just shipping features—you’re building a fortress with good UX.
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!