IAM aws:SourceIp Condition Not Working? VPC Endpoints, Private IPs, aws:ViaAWSService, NAT and IPv6, S3 Bucket Policies and Lockout Recovery
An IAM policy with an aws:SourceIp condition usually "doesn't work" for one of six reasons. The request came through a VPC endpoint, where aws:SourceIp is not present at all (use aws:VpcSourceIp, aws:SourceVpc or aws:SourceVpce instead). The address is private: the key only ever holds public IPs, so 10.x or 192.168.x ranges never match. An AWS service made the call on your behalf (CloudFormation, Athena, EC2 using KMS for encrypted disks), so the source address is AWS's, not yours: exclude those calls with aws:ViaAWSService or aws:PrincipalIsAWSService. Your traffic leaves from a different public IP than you think (NAT gateway, VPN, proxy, or IPv6). The operator is wrong: use IpAddress or NotIpAddress, not StringEquals. Or another policy denies or fails to allow the action anyway. CloudTrail's sourceIPAddress field shows the address AWS actually saw. Below: the working deny-by-IP policy, S3 bucket policies, VPC endpoints, the lockout fix, and how to test without locking yourself out.
Ethan wanted a simple rule for the shop's AWS account: nobody uses it from anywhere but the office. He wrote an IAM policy that denied everything unless the request came from the office's public IP, attached it to the team's group, and felt good about it for nearly an hour. Then a CloudFormation update failed with Access Denied, an Athena query failed, and the backup job on an EC2 instance, which had worked for two years, could no longer write to S3. All from inside the office network. The policy was doing exactly what it said; it just did not say what Ethan meant.
New to IAM policies? The cards below explain policies, explicit denies, VPC endpoints and team sign-in in plain English.
How aws:SourceIp actually works
Every AWS API call arrives with a request context: who is calling, what they want, and facts about the request, such as the time and the network it came from. A condition in a policy compares one of those facts with the values you wrote. aws:SourceIp is the fact "which IP address did this request come from", as seen by AWS at the edge of its network. Our IAM policy explainer covers conditions in general; four details about this one cause nearly every surprise:
- It is the address AWS sees, not the address your computer has. Behind a home router, an office firewall, a NAT gateway or a VPN, that is the public address the traffic leaves from.
- It only holds public addresses. The key can only be used for public IP ranges, so a condition on 10.0.0.0/8 or 192.168.0.0/16 never matches a normal request.
- It is missing for requests through a VPC endpoint. When an instance reaches S3 or another service through a VPC endpoint, the request never touches the public internet, and
aws:SourceIpis simply not in the request context. - For calls an AWS service makes on your behalf, it is the service's address. When CloudFormation creates resources, or Athena reads S3 for your query, part of the work is a separate call made by that service, and the network details of your original request are not carried over.
Keep those four in mind and every symptom below makes sense.
Jake: "So the rule said 'only from the office', and the services weren't in the office?"
Ethan: "Exactly. CloudFormation calls from AWS's own network. As far as the rule knew, it was a stranger."
How IAM conditions combine: AND, OR and missing keys
Most IP-policy bugs are really condition-logic bugs, so it is worth knowing the four rules IAM applies to every Condition block:
- Different operators are combined with AND. In
"NotIpAddress": {...}, "Bool": {...}, both must be true for the statement to apply. - Different keys under one operator are also AND.
"StringEquals": {"aws:SourceVpc": "...", "aws:PrincipalTag/team": "ops"}needs both to match. - Several values for one key are OR.
"aws:SourceIp": ["203.0.113.0/24", "198.51.100.25/32"]matches either range. This is how you list several offices. - A missing key makes ordinary operators false and negated ones true.
IpAddresson a request withoutaws:SourceIpis false;NotIpAddressis true. The...IfExistsvariants treat a missing key as a match instead, which is why they appear in the endpoint examples.
Put together, "deny if not from our IPs" is one statement with NotIpAddress and a list of ranges, and "allow from the office OR the VPC" cannot be written as a single Allow with two conditions, because those would be ANDed. It becomes either two Allow statements, one per condition, or a single Deny whose conditions are all negated, as in the examples below. Most people find the Deny form easier to reason about: it lists the situations in which a request is refused, and everything else falls through to the normal Allow policies.
Condition keys come in two kinds. Global keys such as aws:SourceIp, aws:SourceVpc and aws:PrincipalArn start with aws: and work across services, though not every key is present in every request. Service keys such as s3:prefix or kms:ViaService belong to one service and only make sense with its actions. The service authorization reference lists which keys each action supports.
Why your aws:SourceIp condition is not working
| Symptom | Cause | Fix |
|---|---|---|
| CloudFormation, Athena, Glue or other services fail with Access Denied; direct CLI calls work | Calls made by a service on your behalf come from AWS addresses | Add "Bool": {"aws:ViaAWSService": "false"} to the Deny |
| EC2 instances with encrypted volumes terminate on launch (Client.InternalError) | EC2 calls KMS for you; an IP condition on the KMS key or a deny-by-IP blocks it | Exclude service calls; use kms:ViaService on keys |
| Instance in a private subnet is denied S3 access | Traffic goes through an S3 gateway endpoint, so aws:SourceIp is absent and NotIpAddress is true | Allow the endpoint with aws:SourceVpce or aws:SourceVpc |
| Condition lists 10.0.0.0/16 and never matches | Private ranges never appear in aws:SourceIp | Use the NAT gateway's Elastic IP, or aws:VpcSourceIp with an endpoint |
| Denied from the office, with the office IP in the policy | Traffic leaves through a VPN, proxy or a second internet line with another IP | Check the real address (CloudTrail or checkip); list every egress IP |
| Works for some services and not others from the same machine | Some endpoints are reached over IPv6, so the source is an IPv6 address | Add your IPv6 prefix to the condition |
| Condition never matches at all | StringEquals or StringLike used for an IP; or a typo in the key name | Use IpAddress / NotIpAddress and aws:SourceIp exactly |
| An Allow with IpAddress still lets requests from other IPs through | Another policy (an admin policy, a group policy) allows the action without the condition | Use an explicit Deny with NotIpAddress instead |
| Policy has no effect on the role your app uses | It is attached to a user or group, but the app assumes a role | Attach it to the role (or use an SCP for the whole account) |
| Lambda or other serverless code denied | Lambda outside a VPC uses AWS-owned addresses that change | Exclude the function's role, or put it in a VPC behind a NAT with a fixed IP |
Find the IP address AWS actually saw
Guessing wastes time; CloudTrail records the address of every management API call. To find the failed call:
- Open the CloudTrail console and choose Event history.
- Filter by Event name (for example
PutObjectis a data event and needs a trail with S3 data events;CreateStack,RunInstancesand most other calls appear by default) or by User name. - Open the event and look at
sourceIPAddressanderrorCode(AccessDeniedor similar). - If
sourceIPAddressis a service name such ascloudformation.amazonaws.comorathena.amazonaws.com, the call was made by that service on your behalf. If it is an IP address, compare it with your policy. If the event shows avpcEndpointId, the call came through a VPC endpoint.
From the command line, the same lookup:
aws cloudtrail lookup-events \ --lookup-attributes AttributeKey=Username,AttributeValue=ethan \ --max-results 20 \ --query "Events[].[EventTime,EventName,CloudTrailEvent]" --output text
Each CloudTrailEvent is the full JSON record, including sourceIPAddress and errorMessage. The error message also says whether the denial was an explicit deny and, for many services, which policy type caused it. Our guide to "explicit deny" messages explains how to read them.
To check your own public address from a machine, curl https://checkip.amazonaws.com prints the IPv4 address your traffic leaves from. If it differs from the one in your policy, you have found the problem. On a corporate network, ask your network team for every egress address: offices often have a backup line, a separate proxy for web traffic, or a VPN that sends some traffic elsewhere.
The deny-by-IP policy that does not break AWS services
This is the pattern for "nobody may use these credentials except from our addresses". It is a Deny, so it works alongside whatever other policies allow, and it excludes calls that AWS services make on the user's behalf:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyOutsideOfficeIPs",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"NotIpAddress": {
"aws:SourceIp": ["203.0.113.0/24", "198.51.100.25/32", "2001:db8:1234::/48"]
},
"Bool": {"aws:ViaAWSService": "false"}
}
}]
}
How to read it: deny everything if the request is not from one of these addresses and it is not a call an AWS service made for you. Both conditions must be true for the deny to apply, because conditions in one statement are combined with AND.
aws:ViaAWSServiceis true when a service uses your credentials to make a further request for you (CloudFormation creating a bucket, Athena reading S3, EC2 asking KMS to decrypt a volume). Setting it to false in the deny exempts those calls; the original request you made still has to come from an allowed address.- Attach it where it counts. As an identity policy on a user, group or role, it covers those identities. To cover everyone in an account except a break-glass role, AWS Organizations service control policies use the same statement, plus a condition excluding the emergency role.
- Combine with an Allow. A deny never grants anything; the identity still needs normal Allow policies for what it does.
For resource policies (an S3 bucket policy, a KMS key policy) the better fit is aws:PrincipalIsAWSService, which is true when an AWS service principal such as CloudTrail or Config writes to your resource directly. Adding "BoolIfExists": {"aws:PrincipalIsAWSService": "false"} to the deny stops it from blocking those services.
Ethan's version, with aws:ViaAWSService added, fixed CloudFormation and Athena straight away. The backup job needed the next section.
When an Allow with IpAddress is enough
A Deny is the safe default because other policies cannot override it. But there is one situation where a plain Allow with an IP condition is cleaner: an identity that has no other permissions for the action. A contractor's user who may only download reports, and only from their own office, is the typical case:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "ReportsFromContractorOfficeOnly",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::shop-reports/contractor/*",
"Condition": {"IpAddress": {"aws:SourceIp": ["198.51.100.0/28"]}}
}]
}
Because this is the user's only S3 permission, a request from any other address finds no Allow at all and is refused by default. The moment someone attaches a broader policy to that user, though, for example a group with read access to all buckets, the IP condition stops meaning anything, since the broader Allow does not carry it. That is the trade-off: Allow-with-condition is tidy for single-purpose identities, while a Deny keeps working however the other permissions change.
VPC endpoints: why aws:SourceIp disappears, and what to use instead
The shop's backup instance lives in a private subnet and writes to S3 through an S3 gateway endpoint, which keeps the traffic inside AWS's network and avoids NAT gateway charges. A request through a VPC endpoint has no public source address, so aws:SourceIp is not in the request context. Then a subtle rule takes over: when a condition key is missing, negated operators such as NotIpAddress evaluate to true. So "deny if not from the office IP" denies every request through the endpoint.
The fix is to also allow the endpoint, using the keys that are present for endpoint traffic:
aws:SourceVpce: the VPC endpoint ID, for examplevpce-0a1b2c3d4e5f67890.aws:SourceVpc: the VPC the endpoint belongs to.aws:VpcSourceIp: the private IP of the caller inside the VPC, for when you want "only these subnets".
Because you want "allow if from the office or from our endpoint", and conditions in one statement are combined with AND, the clean approach uses ...IfExists operators so each key is only checked when it is present:
{
"Sid": "DenyUnlessOfficeOrOurVpc",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"NotIpAddressIfExists": {"aws:SourceIp": ["203.0.113.0/24"]},
"StringNotEqualsIfExists": {"aws:SourceVpc": ["vpc-0abc1234def567890"]},
"Bool": {"aws:ViaAWSService": "false"}
}
}
Walk through the cases: a request from the office has aws:SourceIp in range (first condition false, no deny). A request through your endpoint has no aws:SourceIp (first condition true because of IfExists) but its aws:SourceVpc matches (second condition false, no deny). A request from anywhere else on the internet has a non-matching aws:SourceIp and no aws:SourceVpc (both true), so it is denied. Before you rely on it, run it through the policy simulator with each case (below), because IfExists logic is easy to get backwards. Our S3 VPC endpoint Access Denied guide covers the endpoint policy side of the same problem.
Interface endpoints (PrivateLink, used for most services other than S3 and DynamoDB gateway endpoints) behave the same way: no aws:SourceIp, but aws:SourceVpce, aws:SourceVpc and aws:VpcSourceIp are present. Our VPC endpoint pricing guide explains which kind you have.
Jake: "So the backup job got locked out because it was being more private, not less?"
Ethan: "Yes. The rule only knew how to recognize the office's public address. The private path had no address for it to check."
aws:SourceIp in S3 bucket policies
Bucket policies are the other common home for IP conditions, for example "only our office and our VPC may read this bucket". The same rules apply, plus one S3-specific point: the bucket policy applies to every principal, including other AWS services delivering logs and your own console sessions.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyOutsideOfficeAndVpc",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::shop-backups", "arn:aws:s3:::shop-backups/*"],
"Condition": {
"NotIpAddressIfExists": {"aws:SourceIp": ["203.0.113.0/24"]},
"StringNotEqualsIfExists": {"aws:SourceVpce": ["vpce-0a1b2c3d4e5f67890"]},
"BoolIfExists": {"aws:PrincipalIsAWSService": "false"}
}
}]
}
Three cautions before you save a policy like this:
- You can lock yourself out of the bucket. A deny on
s3:*includess3:PutBucketPolicyands3:DeleteBucketPolicy. If you save it from an address that is not allowed, you cannot change it back. The account's root user can still delete a bucket policy, which is the way out. - Console browsing counts. Viewing the bucket in the S3 console from home, on mobile data or over a VPN with another exit address is denied too.
- Services that read the bucket for you (Athena, Glue, Redshift Spectrum, CloudFront origins) may call from AWS addresses. Test each one you use after applying the policy.
For data that should only ever be reached from inside AWS, aws:SourceVpce alone is the stronger rule: IP addresses can be shared or reassigned, endpoint IDs cannot be used from outside your VPC.
Applying it to a whole account with an SCP, safely
An identity policy covers only the users, groups and roles it is attached to, and new roles slip through. In an AWS Organizations setup, a service control policy applies the same rule to every principal in the member accounts it is attached to, which is the reliable way to say "this whole account is office-only." The version that does not break services or lock out the rescuers:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyOutsideOfficeExceptBreakGlass",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"NotIpAddressIfExists": {"aws:SourceIp": ["203.0.113.0/24", "2001:db8:1234::/48"]},
"StringNotEqualsIfExists": {"aws:SourceVpc": ["vpc-0abc1234def567890"]},
"Bool": {"aws:ViaAWSService": "false"},
"ArnNotLike": {"aws:PrincipalArn": [
"arn:aws:iam::*:role/BreakGlass",
"arn:aws:iam::*:role/aws-service-role/*"
]}
}
}]
}
Each line has a job. The IP and VPC conditions describe the allowed places. aws:ViaAWSService keeps CloudFormation, Athena and friends working. The aws:PrincipalArn exclusion leaves a break-glass role and AWS's service-linked roles alone; without the second entry, services that act through their own service-linked roles, such as Auto Scaling or Elastic Load Balancing, can fail in ways that are hard to trace back to this policy.
Roll it out to a test account in an organizational unit of its own first, and work through a normal day there: console, CLI, deployments, scheduled jobs. SCPs do not apply to the management account, so it remains a way back in if a member account is locked out.
Restricting a website or API by IP: the right tools
A frequent mix-up: people try to limit who can open a website hosted on AWS with an IAM aws:SourceIp condition. IAM controls calls to AWS APIs with AWS credentials; website visitors make no such calls. For websites and APIs, the IP controls live elsewhere:
- Security groups on a load balancer or instance allow traffic only from listed address ranges. Simple, and right for an internal tool reached from the office.
- AWS WAF IP sets on CloudFront, an Application Load Balancer or API Gateway block or allow ranges at the edge, alongside other rules such as rate limits.
- API Gateway resource policies do use
aws:SourceIp, because they are evaluated on requests to the API: a Deny withNotIpAddresslimits a REST API to listed ranges, andaws:SourceVpcedoes the same for private APIs. - S3 bucket policies, covered above, apply
aws:SourceIpto anonymous website visitors too, for buckets served directly.
If a page says "your IP address is not permitted" or "IP address not allowed," it is one of these layers refusing the request, usually a WAF rule or an API Gateway resource policy, not an IAM policy. The fix is to add the address to the WAF IP set or the resource policy, then check CloudFront or the load balancer logs for the address the request really came from.
IPv6, NAT gateways, VPNs and Lambda: when your address is not what you think
- IPv6.
aws:SourceIpsupports IPv6 addresses, for services that support IPv6. If your network has IPv6 and you use a dual-stack endpoint (for example S3's dual-stack endpoints, or a service whose default endpoint answers over IPv6), the source address is IPv6, and a policy listing only IPv4 ranges denies it. Add your IPv6 prefix, written in standard CIDR form such as2001:db8:1234::/48. - NAT gateways. An instance in a private subnet without an endpoint reaches AWS APIs through the NAT gateway, so AWS sees the NAT gateway's Elastic IP. Put that address (one per NAT gateway, usually one per Availability Zone) in the policy, not the instances' private addresses.
- Instances with public IPs use their own public IPv4 address. Unless it is an Elastic IP, it changes when the instance stops and starts.
- VPNs and proxies. A full-tunnel VPN sends traffic out through the company's exit address; a split-tunnel VPN may send AWS traffic straight out of your home connection. A corporate web proxy may use different addresses from other office traffic.
- Lambda. A function outside a VPC calls AWS from AWS-owned addresses that are not fixed or published for your function. If a deny-by-IP covers the function's role, either exclude that role from the deny, or run the function in a VPC with a NAT gateway (fixed Elastic IP) or VPC endpoints.
- CloudShell runs in AWS's network, so its address is not your office's either.
Targeted rules: protect the dangerous actions, not everything
A blanket "deny everything outside the office" is the strictest rule and the one most likely to get in the way. Often the better design is narrower: let people work from anywhere, but keep the few actions that could do real damage tied to known places. For example, deleting backups or changing who can access the account:
{
"Sid": "DestructiveActionsOnlyFromOffice",
"Effect": "Deny",
"Action": [
"s3:DeleteObject*", "s3:DeleteBucket", "s3:PutBucketPolicy",
"iam:CreateAccessKey", "iam:AttachUserPolicy", "iam:PutUserPolicy",
"kms:ScheduleKeyDeletion", "ec2:DeleteSnapshot"
],
"Resource": "*",
"Condition": {
"NotIpAddressIfExists": {"aws:SourceIp": ["203.0.113.0/24"]},
"Bool": {"aws:ViaAWSService": "false"}
}
}
Day-to-day work, such as reading logs, running queries and deploying through the pipeline, continues from home or on the road, while a stolen laptop's session cannot quietly delete the backups or create new access keys from a cafΓ©. This pattern also makes the blast radius of a mistake in the IP list small: if the office address changes, a handful of rare actions stop working, not the whole account.
The IfExists form keeps requests through your VPC endpoints from being caught. For the same reason as before, test each listed action in the policy simulator from an allowed and a blocked address before relying on it.
Home workers, changing addresses and Identity Center
Most home internet connections get a public address that changes from time to time, so listing home IPs in a policy turns into weekly maintenance. Three ways teams deal with it:
- A VPN with a fixed exit address. Everyone connects to the company VPN (or AWS Client VPN with a NAT gateway behind it), and AWS sees one stable address for all of them. The IP rule then lists only that address.
- Identity-based controls instead. With IAM Identity Center and MFA, sign-in itself is the strong check, and IP rules are reserved for the destructive actions above. Identity Center permission sets accept inline policies, so the same deny statements can be attached there.
- Location rules at the identity provider. If sign-in goes through Microsoft Entra ID, Okta or another provider, its conditional access policies can require a trusted network or a managed device before anyone reaches AWS at all. That stops the session from starting, rather than refusing each API call afterwards.
Ethan chose the second route for the shop: Identity Center with MFA for everyone, and the office IP rule only on deletes and IAM changes. Jake can check the dashboards from home again, and the backups can only be deleted from the back office.
Jake: "So I can look at things from home, but not break things from home?"
Ethan: "That's the whole design."
Watching for denials after the rollout
For the first weeks after an IP policy goes live, look at what it refuses. CloudTrail records each denied management call with an errorCode such as AccessDenied and, for explicit denies, a message saying so. A daily look at those events, filtered to your new statement's actions, shows which legitimate jobs the policy is catching before someone reports a broken process.
If your trail delivers to CloudWatch Logs, a metric filter on { $.errorCode = "AccessDenied" } turns denials into a number you can alarm on; if it delivers to S3, an Athena query over the trail logs grouped by useridentity.arn, eventname and sourceipaddress gives the same picture. Unknown addresses in that list are worth a second look: they are either a forgotten office line or exactly the kind of access the rule exists to stop.
Which address AWS sees, by where the request starts
A cheat sheet for building the address list, since the same person can appear from several addresses in one day:
| Request starts from | aws:SourceIp is | What to allow |
|---|---|---|
| Laptop in the office | The office's public exit address | Every office line and proxy address |
| Laptop at home or on mobile data | The home or mobile carrier's address, often changing | A VPN exit address instead |
| EC2 in a public subnet | The instance's public IP | An Elastic IP, so it never changes |
| EC2 in a private subnet, via NAT | The NAT gateway's Elastic IP | Each NAT gateway's address |
| EC2 or Lambda via a VPC endpoint | Absent | aws:SourceVpc or aws:SourceVpce |
| Lambda outside a VPC, CloudShell | An AWS-owned address that changes | Exclude the role, or move it into a VPC |
| A service acting for you (CloudFormation, Athena) | The service's address | aws:ViaAWSService false in the Deny |
Go down the table for every way your team and your systems reach AWS, and the address list writes itself. Anything you cannot pin to a stable address belongs in the "exclude" column, not in a growing list of ranges that will be out of date next month.
Locked yourself out: how to recover
A deny-by-IP attached to the wrong group, or a typo in the address, can lock out every administrator. Recovery depends on what still works:
- Try from an allowed address if there is one: the office, a bastion host with an Elastic IP in the policy, or an instance in the allowed VPC.
- Use another identity the policy does not cover: a break-glass admin role or user kept outside the group for exactly this.
- Use the root user. Identity policies do not apply to the root user, so root can sign in and detach or edit the policy. (A service control policy from AWS Organizations can restrict root in member accounts, in which case the management account must change the SCP.)
- For a bucket policy that denies everyone, the root user of the bucket's account can delete the bucket policy even when it denies all principals.
Afterwards, add a break-glass exception before you put the policy back, for example a condition that excludes one emergency role by its ARN with "ArnNotLike": {"aws:PrincipalArn": "arn:aws:iam::111122223333:role/BreakGlass"}, and keep that role's credentials somewhere safe and monitored.
Testing an IP policy without locking yourself out
The IAM policy simulator lets you try a policy with any source IP before you attach it:
- Open the IAM console, choose Policy simulator (or go to policysim.aws.amazon.com).
- Select the user, group or role, or paste the new policy as a draft.
- Choose a service and actions, for example S3
PutObjectand EC2RunInstances. - Expand Simulation settings and fill in global condition keys: set
aws:SourceIpto an office address, run, then set it to an outside address and run again. Leave it blank and setaws:SourceVpcto test the endpoint case. - Read the result for each action: allowed, implicitly denied, or explicitly denied, and which statement decided it.
Then roll out carefully: attach the policy to one test user first, try the console, the CLI and the services your team uses (CloudFormation, Athena, anything that deploys or queries), and only then attach it to the group. Keep a second browser session open as an unaffected admin while you do, so a mistake can be undone at once.
The network condition keys, side by side
| Key | Present when | Holds | Operator |
|---|---|---|---|
aws:SourceIp | Requests not through a VPC endpoint | Public IPv4 or IPv6 address of the caller | IpAddress, NotIpAddress |
aws:VpcSourceIp | Requests through a VPC endpoint | Private IP of the caller in the VPC | IpAddress, NotIpAddress |
aws:SourceVpc | Requests through a VPC endpoint | VPC ID | StringEquals, StringNotEquals |
aws:SourceVpce | Requests through a VPC endpoint | VPC endpoint ID | StringEquals, StringNotEquals |
aws:ViaAWSService | Always (true or false) | Whether a service made this call with your credentials | Bool |
aws:CalledVia | Service-made calls | Which services the call passed through | ForAnyValue:StringEquals |
aws:PrincipalIsAWSService | Signed requests | Whether an AWS service principal is calling directly | Bool |
For KMS keys specifically, kms:ViaService (for example ec2.us-east-1.amazonaws.com) limits a key to requests that come through a named service, which is the right tool when you want "only EC2 may use this key for our volumes" rather than an IP rule. That is also the fix when an IP condition on a key policy makes encrypted instances fail to launch, as covered in our encrypted AMI guide.
The reason IP rules and KMS clash so often: most uses of a KMS key are made by another service for you. When S3 encrypts an object with SSE-KMS, or EC2 attaches an encrypted volume, it is S3 or EC2 that calls KMS, from AWS's network, and aws:SourceIp on that call is not your address. A key policy that says "only from the office" therefore breaks encryption for everything that uses the key indirectly. Keep IP conditions off key policies; describe the allowed path with kms:ViaService and the allowed account with kms:CallerAccount instead, and put any IP rule on the identity that starts the request.
The five most common IP policy mistakes, in order
- Forgetting that AWS services call on your behalf, and leaving out
aws:ViaAWSService. - Forgetting the VPC endpoints, so every private request is denied because
aws:SourceIpis missing. - Listing the wrong addresses: private ranges, an old office line, or only IPv4 when the network also uses IPv6.
- Using an Allow with a condition while another policy grants the same action without it.
- Rolling it out to everyone at once, with no break-glass role and no second admin session open.
Every story of an IP policy going wrong, including Ethan's afternoon, is some combination of these five. Checking each one before saving the policy takes five minutes.
Ethan's own list matched almost exactly: number one broke CloudFormation and Athena, number two broke the backup job, and number five is why the team spent an hour working out who could still sign in. Numbers three and four were the ones he caught in the simulator before they reached anyone, which is the best argument for testing first.
aws:SourceAccount, aws:SourceArn and aws:SourceOwner: a different "source"
Searches for aws:SourceIp often land on keys with similar names that solve a different problem: the confused deputy. When a service such as SNS, S3 event notifications or CloudWatch Logs writes to your resource (an SQS queue, a Lambda function, a bucket), the resource policy allows that service principal. Without more conditions, any customer's use of the same service could, in principle, direct it at your resource. These keys pin the access to your own account or resource:
aws:SourceAccount: the account that owns the resource making the service act, for example the account of the bucket sending an event.aws:SourceArn: the exact resource, such as the ARN of one SNS topic or one bucket, for a tighter rule.aws:SourceOwner: an older key with the same purpose, still used in some services' examples; preferaws:SourceAccountwhere the service supports it.
{
"Sid": "AllowOurTopicOnly",
"Effect": "Allow",
"Principal": {"Service": "sns.amazonaws.com"},
"Action": "sqs:SendMessage",
"Resource": "arn:aws:sqs:us-east-1:111122223333:orders",
"Condition": {"ArnEquals": {"aws:SourceArn": "arn:aws:sns:us-east-1:111122223333:order-events"}}
}
Nothing about these keys involves IP addresses. They describe which resource caused a service to act, and they matter in resource policies that name a service as the principal. If you came here because a queue or bucket policy example used aws:SourceArn, that is the distinction.
One more look-alike: source_profile in your ~/.aws/config is not a policy key at all. It tells the AWS CLI and SDKs which profile's credentials to use when assuming the role a profile names, as in role_arn = ... plus source_profile = default. If an assumed-role profile fails with AccessDenied on sts:AssumeRole, our AssumeRole guide covers that side.
When an IP rule is the wrong tool
IP restrictions are a reasonable extra layer for a small team with a fixed office address. They get harder to live with as soon as people travel, work from home, or use mobile data, and they do nothing if credentials leak to someone working from inside your network. Stronger options to put first:
- Short-lived credentials through IAM Identity Center sign-in instead of long-lived access keys; our Identity Center explainer covers the setup.
- MFA for every human, with
aws:MultiFactorAuthPresentconditions where you need them. - VPC endpoints and
aws:SourceVpcefor machine access to data, which cannot be spoofed from outside your VPC. - Least privilege: narrow Allow policies matter more than where the request comes from.
Used together with those, a deny-by-IP for human users is a sensible belt-and-braces rule. Used alone, it mostly creates the kind of afternoon Ethan had.
Frequently asked questions
Why is my aws:SourceIp condition not working?
Usually because the request came through a VPC endpoint, used a private address, was made by an AWS service on your behalf, or left from a different public IP than the one in the policy.
Does aws:SourceIp work with private IP addresses?
No. It only holds public IP addresses. For private addresses through a VPC endpoint, use aws:VpcSourceIp.
Is aws:SourceIp present for requests through a VPC endpoint?
No. Use aws:SourceVpce, aws:SourceVpc or aws:VpcSourceIp for endpoint traffic.
Why does CloudFormation fail after adding an IP restriction?
CloudFormation makes some calls on your behalf from AWS addresses. Add "Bool": {"aws:ViaAWSService": "false"} to the Deny statement.
What is aws:ViaAWSService?
A condition key that is true when an AWS service makes a request using your credentials on your behalf, such as CloudFormation or Athena.
How do I restrict AWS access to my office IP address?
Attach a Deny statement for all actions with NotIpAddress on aws:SourceIp listing your office ranges, plus aws:ViaAWSService false so services still work.
Does aws:SourceIp support IPv6?
Yes, for services that support IPv6. Add your IPv6 prefix in CIDR form if your network uses it.
How do I see which IP address AWS saw?
Open the failed call in CloudTrail Event history and read the sourceIPAddress field.
Which condition operator should I use for IP addresses?
IpAddress or NotIpAddress, with CIDR ranges. StringEquals does not compare address ranges.
Why is my EC2 instance in a private subnet denied?
Through a NAT gateway, AWS sees the NAT gateway's Elastic IP; through a VPC endpoint, aws:SourceIp is absent. Allow the right one.
I locked myself out with an IP policy. How do I fix it?
Use an allowed address, another admin identity the policy does not cover, or the root user to detach or edit the policy.
Can I use aws:SourceIp in an S3 bucket policy?
Yes, but it also blocks console access from other addresses and requests through VPC endpoints unless you allow those separately.
Why does an Allow with IpAddress not block other addresses?
Another policy allows the same action without the condition. Use an explicit Deny with NotIpAddress to enforce it.
What does IfExists do in a condition?
It checks the key only when it is present in the request; if the key is missing, that condition is treated as true.
Does an IP restriction affect AWS Lambda functions?
Yes, if it covers the function's role: Lambda outside a VPC uses changing AWS-owned addresses. Exclude the role or use a VPC with a NAT gateway.
Are multiple IPs in aws:SourceIp combined with OR?
Yes. Several values for one key match if any one matches. Different keys or operators in the same condition block are combined with AND.
How do I restrict a whole AWS account to office IPs?
Use a service control policy with a Deny on NotIpAddressIfExists, aws:ViaAWSService false, and an aws:PrincipalArn exclusion for a break-glass role and service-linked roles.
Can I restrict a website on AWS by IP with IAM?
No. Use security groups, AWS WAF IP sets, or API Gateway resource policies. IAM conditions apply to AWS API calls, not website visits.
What is the difference between aws:SourceIp and aws:SourceArn?
aws:SourceIp is the caller's public IP address. aws:SourceArn is the resource that caused a service to act, used against confused-deputy access.
What is aws:SourceAccount used for?
In resource policies that allow a service principal, it limits access to service actions caused by resources in your own account.
Is source_profile an IAM condition key?
No. It is an AWS CLI config setting naming the profile whose credentials assume the role of another profile.
Why does "your IP address is not permitted" appear on an AWS-hosted site?
A WAF rule, API Gateway resource policy or security group is refusing your address. Add it to that layer's allowed list.
Can I allow work from home but block risky actions outside the office?
Yes. Put only destructive actions, such as deletes and IAM changes, in the Deny with NotIpAddressIfExists, and leave everyday work unrestricted.
How do I handle home workers with changing IP addresses?
Route them through a VPN with a fixed exit address, or rely on Identity Center with MFA and keep IP rules for destructive actions only.
Can IAM Identity Center restrict sign-in by IP?
Use your identity provider's conditional access for sign-in location. Permission sets can carry inline deny-by-IP statements for API calls.
Why does my KMS key break when I add an IP condition?
Services such as S3 and EC2 call KMS for you from AWS addresses. Use kms:ViaService and kms:CallerAccount on keys instead of IP rules.
Should I use Deny or Allow for IP restrictions?
Deny with NotIpAddress for most cases, because no other policy can override it. An Allow with IpAddress only works for identities with no other permissions for that action.
What IP does AWS see from CloudShell?
An AWS-owned address, not your office's. Exclude CloudShell users' roles or run those tasks from an allowed network.
How do I see requests my IP policy denied?
Filter CloudTrail for errorCode AccessDenied, or add a CloudWatch Logs metric filter and alarm on it during the rollout.
How do I test an IP-based IAM policy?
Use the IAM policy simulator and set aws:SourceIp in the global condition keys to an allowed and a blocked address.
For readers preparing for an AWS certification
We have been covering AWS certifications alongside our fixes, so here is a short extra for readers studying for one. If you only came for the fix, you already have it; feel free to skip this part.
Exams that test this topic: Security – Specialty (SCS-C03) • Solutions Architect – Associate (SAA-C03) • CloudOps Engineer – Associate (SOA-C03).
Here are the exam-style questions this topic tends to raise, with the reasoning you need to answer them:
1. A bucket policy denies requests not from the office IP range. An EC2 instance using an S3 gateway endpoint is now denied. Why?
aws:SourceIp is not present for requests through a VPC endpoint; allow the endpoint with aws:SourceVpce or aws:SourceVpc.
2. A deny-by-IP identity policy breaks CloudFormation deployments made from an allowed IP. What condition fixes it?
Add "Bool": {"aws:ViaAWSService": "false"} so calls services make on the user's behalf are not denied.
3. Which condition key holds the private IP address of a caller using a VPC endpoint?
aws:VpcSourceIp.
4. Which condition operator compares an IP address with a CIDR range?
IpAddress (or NotIpAddress).
5. Where can you see the source IP address AWS recorded for a denied API call?
The sourceIPAddress field of the event in AWS CloudTrail.
6. An instance in a private subnet reaches AWS APIs through a NAT gateway. Which address does aws:SourceIp contain?
The NAT gateway's Elastic IP address.
7. How do you limit a KMS key to use through a specific AWS service, instead of by IP address?
Use the kms:ViaService condition key in the key policy.
Exam codes and dates change often. Our AWS certification changes page keeps track of what is current.
The final policy fits on one screen: deny unless from the office's two addresses or the shop's VPC, skip calls services make on the team's behalf, and leave one break-glass role out of it entirely. CloudFormation deploys again, Athena queries run, and the backup job writes through its endpoint as before. Jake's summary for the team wiki: "The rule checks where the request comes from. Services and endpoints come from somewhere else, so tell the rule about them."
📌 If you keep one line from this page
aws:SourceIp only sees public addresses on requests that do not go through a VPC endpoint: add aws:ViaAWSService false so services keep working, allow endpoints with aws:SourceVpce or aws:SourceVpc, and check CloudTrail's sourceIPAddress before changing anything.
IpAddress, not StringEquals · private IPs never match · IPv6 counts · keep a break-glass role.
Revision note. Written October 19, 2026. An IP rule checks one fact about a request; most surprises come from requests where that fact is missing or belongs to someone else.