IP Whitelist/Blacklist or Allowlist/Denylist on the ForgeRock Identity Cloud

Originally posted on keithdaly-identity.medium.com

Method for Implementing IP Blacklisting/Whitelisting

I recently did some work for a PoC that required a scripted version of a client IP whitelist. There has been a compiled node available in our marketplace for a while, but it is not possible to add custom compiled components to the ForgeRock ID Cloud.

Since this was a PoC, I did not need to worry about where the whitelist/blacklist values were stored. In the code below, I have the list defined as a JSON array of CIDR values. This works fine for relatively static address ranges. If desired, this can be reimplemented as a call to retrieve an ESV if stored in one, or as a call-out to an external system (beware of performance issues if you take this path, though).

The code below shows a whitelist implementation, which would be appropriate as a way to allow traffic from only certain sub-domains. In the code, there are two lines that would need to be flipped (true/false) to modify this to enforce a blacklist.

The Authentication Journey:

Before getting to the code, the following image shows the out-of-the-box Login flow augmented to perform whitelist/blacklist checking.

The “Validate IP” node is a Scripted Node that is configured to use the script. Outcomes are defined as “Pass” and “Block” which corresponds to the (case-sensitive) outcomes defined in the script.

The scripted node code (“Validate IP”):

And here is the script… Be sure to at least modify the cidrAddresses value to suite your environment. These do need to be CIDR addresses (lookup “convert ipv4 to cidr” on your favorite search engine to find online tools, if needed).

//------------------------------------------------------//
//-- IP white/black approve/deny list check           --//
//------------------------------------------------------//
//-- The following implementation compares against    --//
//-- a whitelist (approvelist)                        --//
//-- This can easily be changesd to a blacklist       --//
//-- (denylist) by inverting the true/false           --//
//-- statements in the code. These lines are          --//
//-- indicated with comments                          --//
//------------------------------------------------------//
//-- The list here is a simple JSON array of CIDR     --//
//-- addressees. This is the fastest comaprison       --//
//-- possible, but can be modified to retrieve        --//
//-- values from an external source or ESV.           --//
//---------------------------------------------------kd-//

function compareIPtoCIDR(ipAddress, cidrAddress) {

// Convert the IP address to a number
const ipParts = ipAddress.split('.');
const ipNumber =
(parseInt(ipParts[0], 10) << 24) +
(parseInt(ipParts[1], 10) << 16) +
(parseInt(ipParts[2], 10) << 8) +
parseInt(ipParts[3], 10);

// Extract the CIDR address and subnet mask
const cidrParts = cidrAddress.split('/');
const cidrIp = cidrParts[0];
const subnetMask = parseInt(cidrParts[1], 10);

// Convert the CIDR IP address to a number
const cidrIpParts = cidrIp.split('.');
const cidrIpNumber =
(parseInt(cidrIpParts[0], 10) << 24) +
(parseInt(cidrIpParts[1], 10) << 16) +
(parseInt(cidrIpParts[2], 10) << 8) +
parseInt(cidrIpParts[3], 10);

// Calculate the subnet mask as a number
const subnetMaskNumber = Math.pow(2, 32 - subnetMask) - 1;

// Calculate the network address as a number
const networkAddress = cidrIpNumber & (~subnetMaskNumber);

// Check if the IP address is in the network range
return ipNumber >= networkAddress && ipNumber <= networkAddress + subnetMaskNumber;
//return true;
}

//-- Change to a lookup from a config object or external system if required
var cidrAddresses = ['34.64.0.0/10', '93.184.216.34/32'];

//-- The client IP address should be the first IP Address listed in the array of addresses returned
var ipAddress = requestHeaders.get("x-forwarded-for").toString().split(",").shift().substring(1);

//-- Change this default value to "true" for blacklist (denylist)
var found = false;
cidrAddresses.forEach((cidrAddress) => {
//-- Change to "found = false" for blacklist (denylist)
if (compareIPtoCIDR(ipAddress, cidrAddress)) {found = true}
});

if (found === true) {
outcome = "Pass";
} else {
outcome = "Block";
}
2 Likes

Hi @keith.daly

I’ve implemented something very similar to this in IG using the org.forgerock.openig.util.InetAddressMask and java.net.InetAddress classes which is far easier to implement (defining allow/deny ranges with InetAddressMask using a simple String of a CIDR and then doing the evaluation with a simply call to the InetAddressMask’s test method passing an InetAddress argument). Do you know if there may be any plans to make that class available in Identity Cloud?

Thanks,
Mike

1 Like

Thanks for the suggestion. In this post, I was trying to create a script that would work on anyone’s tenant without any system modification, but that class would simplify things.

I am not sure about the possibility for that class to be generically white listed. I will bring it up with the product teams and get back to you.

Absolutely understood. I just figured that since ForgeRock already had a compiled class that does a lot of the heavy lifting it would make sense to leverage that if we could. Appreciate the follow up, and I hope you are doing well!

Opening those classes may have unintended consequences, security or otherwise. I would go through a service ticket if you want to use the compiled classes in your tenant.

For this specific case, the function in the script is solid and efficient, so probably not worth the effort and risk of whitelisting classes (my personal opinion).

2 Likes

Team, I also see a need / want to limit what IP’s an administrator can log in from. Is there a way to manage this action?

John, The answer depends on which admins are you referring to. For the top-level super admins for the whole tenant, you would need to file a service ticket to request the functionality, since that authN flow is managed by ForgeRock (if it is even possible). For your own admins inside the alpha or bravo realm, you would just need to add a scripted IP white-listing node in the flow used to authenticate the admin users.

1 Like

What about country blocking. We have a hard compliance rule where certain countries should never even hit the tenant. I see we can try and use a scripted node and gather IP’s but some of that also requires the person on the browser to accept the location tracking. We need something more along the lines of what Microsoft / AWS / IBM do where you can limit access based on country. I know this is not perfect from a security perspective but it is a hard requirement for many industries and honestly why many of the cloud vendors even have it as an option. I would think this would be easy to implement as google does have this service already. Thoughts?