IAM Conditions with IPs OR VPC Endpoints

Posted by Elliot Segler on Fri 12 March 2021 Updated on Sat 13 March 2021

Security is usually the most important consideration for us, and our customers workloads in my work at Modis. In many of our accounts, we've adopted a security model for Staff Identity Federation that allows us to build "islands of identity". This model allows us to integrate with identity providers like ADFS or Azure AD, manage access using groups and role base access controls (RBAC).

In addition to this, we also commonly apply layers of security that contstrain our users to specific known IP address ranges, usually including staff offices and the existing cloud environment.

We've previously written a case study on just that here, which my colleague James wrote here: Securing The Land Registry in the Cloud

Recently, the team have been uplifting the network environment to make heavier use of VPC Endpoints for all the common services we access. This increases our security posture by ensuring traffic destined to AWS Service Endpoints stays in the AWS network, and helps to reduce cost because we no longer pay the data transfer costs for our NAT Gateways for this traffic.

After deploying the VPC Endpoint for STS, we noticed that some of our AssumeRole calls were failing from within our VPCs, even after we had added our private address space in our policies that apply our IP Restrictions. After some digging and some assistance from the team at AWS Support, we landed on a single policy that does both, but I wanted to talk about the policy syntax here, hopefully to save someone some struggle later.

The issue we ran into stems from the fact that IAM Policies aren't (at the time of writing this) able to support the conditional "OR" logic when using multiple conditions for a given statement. If you write a policy with multiple conditions they are evaluated with AND logic. The AWS documentation explains this pretty well here.

A truncated example of the trust policy for the target role policy we landed on is below:

{
  "Version": "2012-10-17",
  "Statement": [
    ... REMOVED FOR BREVITY ...
    {
      "Effect": "Deny",
      "Principal": {
        "AWS": "arn:aws:iam::XXXXXXXXXXXX:role/AssumeRoleSource"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "NotIpAddressIfExists": {
          "aws:SourceIp": [
            "1.2.3.0/24",
            "4.3.2.1/32",
            "10.0.0.0/8",       
          ]
        },
        "StringNotLikeIfExists": {
          "aws:SourceVPCe": [
            "vpce-aaaaaaaaaaaaaa"
          ]
        }
      }
    }
  ]
}

The deny statement is interesting for two reasons. Normally we'd be using a single NotIpAddress here with a list of the known IP addresses. Note that this is unfortuately written as a double negative, that is Deny requests from places we don't recognise. If you were to look at the requests that were failing in CloudTrail, you'd see that all the requests coming in from our VPCs have both the VPCEndpoint and a SourceIP field. What's important to note is that even though they are logged, you need to pay attention to when IAM actually sees those fields. What we learnt is that if your requests are coming from a VPC Endpoint the SourceIP is not set. Similarly, if the traffic from from the internet, you won't find a VPC Endpoint field.

The StringNotLikeIfExists resolves to True if the condition key doesn't exist in the request, and allows us to do a conditional OR in a really obtuse manner. The ...IfExists keys are worth a read into and understanding, because they are a powerful IAM feature.

There's one other way we could have written this policy, if we had multiple endpoints but still wanted to use private IP space, and that's the aws:VpcSourceIp key. You could put the private IP space for all your VPC ranges across multiple VPCs (even accounts). Our VPC endpoints live near our Transit Gateway infrastructure because there's a cost optimisation strategy there, so we wouldn't benefit much from this kind of policy over specifying specific endpoints (and I belive the latter is probably more secure anyway).

tags: aws, iam, vpc, vpcendpoints