Restricting access to AWS based on source IP and its considerations

Kieran Yio
4 min readFeb 6, 2023

Some organisations may require you to restrict AWS access for all or a subset of users to only the specified IP ranges as part of the security policy or for other reasons. Typically, these IP ranges are the organisation’s IP addresses. This AWS documentation shows how we can craft a simple IAM policy to deny all AWS actions in the account when the request is not from the specified IP ranges.

https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_aws_deny-ip.html

Example:

{
"Version": "2012-10-17",
"Statement": {
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"NotIpAddress": {
"aws:SourceIp": [
"<CIDR range 1>",
"<CIDR range 2>"
]
},
"Bool": {"aws:ViaAWSService": "false"}
}
}
}

This IAM policy is attached to a user or role that will be assumed via single sign on.

Let’s look at some considerations when implementing this policy.

aws:SourceIp does not support private network

At the point of writing this blog, aws:SourceIp does not support private IP addresses. You should only specify public IP addresses in the IAM policy. If you specify private IP addresses, it will not work.

You may ask, since AWS is accessed via the internet, why do we need to specify private IP addresses? When do we use it? What is the use case for it? This happens when you have a VPC endpoint created and you are using a VPN.

For example, you created a Secrets Manager VPC endpoint and you are connected to the AWS Client VPN. Executing a nslookup gives you a result like this:

~ % nslookup secretsmanager.ap-southeast-1.amazonaws.com
Server: x.x.x.x
Address: x.x.x.x#53

Non-authoritative answer:
Name: secretsmanager.ap-southeast-1.amazonaws.com
Address: 10.x.x.x
Name: secretsmanager.ap-southeast-1.amazonaws.com
Address: 10.x.x.x
Name: secretsmanager.ap-southeast-1.amazonaws.com
Address: 10.x.x.x

Whenever you try to access Secrets Manager, traffic will be routed to the endpoint instead of the internet and access will be denied due to the IP restriction. Looking at the CloudTrail event, you will realise that the source IP is a private IP. So how do we allow access in this case? This brings us to the next consideration.

Combine with other global condition context keys

There are quite a few global condition context keys that can be used in conjunction with aws:SourceIp.

https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html

Using the scenario above, one way we can allow access is by using aws:SourceVpce.

Example:

{
"Version": "2012-10-17",
"Statement": {
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"NotIpAddress": {
"aws:SourceIp": [
"<CIDR range 1>",
"<CIDR range 2>"
]
},
"StringNotEquals": {
"aws:SourceVpce": "vpce-1a2b3c4d"
},
"Bool": {"aws:ViaAWSService": "false"}
}
}
}

This will allow access to AWS when the request is either from the specified IP addresses or the VPC endpoint. However, this approach is harder to maintain. Whenever there is a new VPC endpoint or a change to an existing VPC endpoint, you will have to update the IAM policy.

A better approach is to use aws:SourceVpc to allow AWS access for requests from the VPC where the VPC endpoints are created. This covers all the VPC endpoints without having to explicitly specify all of them in the policy unless some of them are created in a different VPC. In which case, you will have to specify all the VPCs.

Example:

{
"Version": "2012-10-17",
"Statement": {
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"NotIpAddress": {
"aws:SourceIp": [
"<CIDR range 1>",
"<CIDR range 2>"
]
},
"StringNotEquals": {
"aws:SourceVpc": "vpc-111bbb22"
},
"Bool": {"aws:ViaAWSService": "false"}
}
}
}

Not all IAM actions support global condition context keys

When implementing the IP restriction policy, you need to be mindful of whether a service or an action supports global condition context keys. Otherwise, it will cause an issue to your users, preventing them from proceeding with their work, especially if there is an existing time-sensitive issue in the production environment that needs to be resolved.

One example is the Amazon RDS (Relational Database Service). If you are using IAM database authentication to access the database, you will encounter such an error when trying to log in.

psql: FATAL: PAM authentication failed for user “xxx”

This is due to the limitation of IAM database authentication where it does not support all global condition context keys.

https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html#UsingWithRDS.IAMDBAuth.Limitations

Since this is an AWS limitation, we will have to exclude this action from the IAM policy. Instead of listing all the actions to deny and exclude rds-db:connect, we can do the inverse way by using the NotAction element and add that action in the list. This is to avoid having a long policy and potentially hitting the policy size limit.

https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-quotas.html#reference_access-analyzer-quotas

Example:

{
"Version": "2012-10-17",
"Statement": {
"Effect": "Deny",
"NotAction": [
"rds-db:connect"
],
"Resource": "*",
"Condition": {
"NotIpAddress": {
"aws:SourceIp": [
"<CIDR range 1>",
"<CIDR range 2>"
]
},
"StringNotEquals": {
"aws:SourceVpc": "vpc-111bbb22"
},
"Bool": {"aws:ViaAWSService": "false"}
}
}
}

Unfortunately, there isn’t a complete list of services or actions that do not support all global condition context keys. We will have to find out ourselves, perhaps through manual effort.

--

--