Restrict Access to ERPNext website based on device

Hi Everyone,
The company I work with wishes to deploy ERPNext but as part of the requirements we would require that the site is only accessible from selected devices, that is we would prefer the site to only be accessible to devices resident in any of our stores/offices and some selected mobile devices
I have considered the following ideas

  1. The Use of IP Addresses: This involves setting the Nginx to restrict access to certain IPs , but Given that their internet service provider does not operate using a static IP address, the IP address can be changed at any point which will entail frequent modification of Nginx settings

  2. Using Mac Address: This way only Mac addresses from trusted devices are allowed to access the site, but since ERPNext is mainly browser-based we would have to force each device to transmit Mac address and block all unrecognized Mac Addresses, I am not entirely sure if Nginx supports Mac address filtering, but Nonetheless this is a less reliable technique given that Mac addresses can be spoofed.

Has anyone implemented this sort of structure previously?
Any ideas or suggestions would be greatly appreciated.

1 Like

I’m not sure a Mac address is accessible from the browser though.

You can “allow” IP addresses in nginx and perhaps restrict or redirect others…

At the server level, you can use IPtables, Firewall to filter MAC address:

https://www.cyberciti.biz/tips/iptables-mac-address-filtering.html

For future reference (drop all except allowed MAC):
sudo iptables -A INPUT -p tcp --dport 443 -m mac ! --mac-source YOUR-MAC-ADDRESS-HERE -j DROP

This is to control access within a LAN/WAN but MAC addresses cannot be transmitted by browsers.

Yes - browser does not transmit MAC but communication at the interface level sees it. So if restricting to LAN - IP range can be specified for “allow” within nginx. And/or MAC filtering can be deployed on ERPNext server IPtables.

Hmm… I am not so sure this would be easy to implement across several brick and mortar locations. If there were say 5 stores and 2 regional offices, this kind of filtering could be quite difficult to setup and maintain.

Just my thoughts…

BKM

My thoughts as well. VPN use would allow intranet IP address for all locations? Offcourse MAC filtering at the server level would complicate things if running a multi-tenant setup. As the permitted MAC address would have have access to all sites on server… ?

For the stores/offices, I feel the easiest solution is modifying your ERPNext web server’s firewall. Only allow inbound HTTP traffic from certain IP subnets. As mentioned above, modifying iptables directly will give you fantastic control. But there’s a learning curve, if you’ve never worked with it.

Certain Linux distributions (Ubuntu?) ship with ufw (Uncomplicated Firewall), which is a wrapper for iptables. Some people find ufw easier to use and modify.

I’ve never tried mac address filtering with iptables and ERPNext. Adding that to my “To Try” list.

VPN would work too. Although I wonder if it would cause headaches with non-ERPNext websites and IP addresses, because of routing/DNS/etc.

1 Like

Thanks Brian,

But what about scenarios where several machines - only one of which you would want to allow - possess the same IP?

Re MAC address filtering: if browser/http(s) don’t transmit MAC addresses, how can we filter based on them?

As an example. Let’s assume our ERPNext server is in the cloud. It’s in a “production configuration”, so Nginx is configured and enabled.

First, you configure the server’s firewall so the Default action is to drop all inbound HTTP connections. This ensures that no device, anywhere, can communicate with the server using TCP 80 or 443.

Each of those physical locations (office, brick & mortar stores), should have only a few public, static IP addresses. The devices behind them on the LAN will have unknown IPs, on Private Network subnets. However, the gateway router will use NAT and modify the packets. The outside world will just see the public, static IP.

You modify your ERPNext server’s firewall, and allow Inbound TCP 80/443 from only those few IPs. This solves Part 1 of the OP’s question, where he wants to restrict connections to certain stores/locations. Any device on the office LAN could access the ERPNext web server. Regardless of machine IP, browser make or model.

In theory, you could use the same solution for mobile devices. Instead of safelisting IPs, you safelist MAC Addresses. It’s not perfect. MAC addresses can be spoofed. But it should work. I say “should” only because I haven’t personally tested this with ERPNext.

Nothing above requires modifying ERPNext, Nginx, browsers, etc. It’s all done in Linux firewalls.

That solves the OP’s question:

But if you don’t care about a device’s location? An interesting alternative is modifying ERPNext’s authentication routines. So that instead of Username + Password, it authenticates differently. Perhaps with public+private keys. In that scenario, the device can communicate over any network, provided it possesses the correct key files.

1 Like

I do not believe this is the normal case. Most businesses I setup with ERPNext are using it from a cloud VPS server and their public IP address is not static. It changes on some schedule just like your home internet service and almost always changes if the power to the modem is lost for even a brief period. Static IP addresses for these sites usually adds another $70 to $100 to the monthly bill. Out of the last 5 systems I started up, only one opted for a static IP address and then dropped it several months later when it was deemed not a necessity.

In really big organizations, static IP’s are common, but that is not usually the target audience for ERPNext, do I don’t think it is all that common.

Anyone else agree or disagree? It might be interesting to see how this works in other regions since I am limited to my small part of the country.

BKM

1 Like

Great point. I’ve had the opposite experience, and mostly dealt with customers/sites with static IPs. But that’s certainly not universal. If the customer’s physical site IP is dynamic, you’ll need a solution like VPN, shared keys, etc.

Great discussion.

I have been implementing ERPNext for a private entity operating in healthcare domain. They are conscious of the data security - perhaps too much.

I designed the solution around user IP restriction discussed here. I proposed to purchase a static public IP from the ISP so the desk users of the cloud hosted ERPNext site can be restricted to that particular IP only.

But the owner (the decision maker from the customer side) is not happy with the proposal. He wants each user be bind to a single device, effectively making a given user not be able to use any device (not only a laptop, tab or a phone but also a co-worker’s computer already connected to the network and has no problem accessing the system by that co-worker) other than the pre-assigned desktop computer.

I jokingly said to my marketing team, “okay ask this guys to buy set of static public IP addresses, one for each internal user” but realistically thinking how this can be provided without going to that extreme. Do you guys have any suggestions?

I was able to achieve such a restriction by using the UUID of the device to map access to a user but the validation only happens post-login and the user is thrown out after validation if unsuccessful. However, it is more effort and trouble than it’s worth as it could be pain to set up as each user and not practical for a multi-location organization or an organization with hundreds of users.

great!

thanks for the feedback. I’m really interest on this method. mine is less than two dozen users. I can figure the UUID of each device. It is very simple because right now all the systems are Windows desktops. In fact, there’s an opportunity for us to negotiate it as a VAS with the customer.

How did you do the configuration in the ERP Next, given you have a sheet mapping the username of the user and uuid of the windows desktop.

Add a custom field to the user profile to store allowed uuid.

create a function to fetch UUID from the device:

def get_system_uuid():
    try:
        current_file_dir = os.path.dirname(os.path.abspath(__file__))
        script_path = os.path.join(current_file_dir, 'info.sh')
        output = subprocess.check_output(['sudo', script_path], universal_newlines=True, stderr=subprocess.STDOUT)
        uuid = output.strip()

        if not uuid:
            frappe.throw(_('Unable to fetch system UUID.'), frappe.ValidationError)
        return uuid
    except subprocess.CalledProcessError as e:
        error_message = f"Command: {e.cmd}, Exit status: {e.returncode}, Output: {e.output}"
        frappe.log_error(frappe.get_traceback() + "\n" + error_message, 'Hardware Fingerprint Generation Failed')
        frappe.throw(_('Failed to generate hardware fingerprint. Error: {0}').format(error_message), frappe.ValidationError)

This part is actually tricky because getting uuid requires elevated permissions so you have to find a way to allow this script to execute and you have to adapt it to the device environment. this is why i said this is not sustainable for large deployments. In my case, this additional parameter is stored in a seperate bash script here:

        script_path = os.path.join(current_file_dir, 'info.sh')
        output = subprocess.check_output(['sudo', script_path], universal_newlines=True, stderr=subprocess.STDOUT)

But i cannot post the script content for security reasons.

Next step is to create another function to compare the returned UUID with the UUID stored in the user profile. if it does not match, execute logout and return a message to the user that they’re not allowed to login on this device.

Wow !

I’m grateful for your insights, and the help extended to me understanding the concept and workaround.

I might use this in the future, but not for this customer. Simply because the time and effort you put into that is not worth it. Code level changes are not permissible for this solution.

If I use UUID based restriction for a future customer I’ll definitely come back and thank you again. Until then I encourage other people to explore this and comment. flexy2ky, your answer here and the explanation will be useful for many people.

Thanks again.

As i said in my earlier post, it’s more effort than it’s worth and i am exploring easier and more practical ways to achieve this. once i come up with something i will share.