Detailed access control and user management by reverse proxy - it works

I solved the 403 issue for the master user by adding these lines into nginx.conf:

		} 
		if ($remote_user = "master") {
			set $test  "${test}C"; 
		} 

The issue with the iOS app is still there, will investigate further.

It works from local and remote via the browser (also on iOS) but not on the native app.
No idea how I can solve that …

Yes, you are right for the master user. I fixed that one as well but forgot to post it. My mistake, sorry for that. You can by the way simplify this rule by just putting set $test “”;

Original post edited.

For the iOS app, the trick maybe on the REST call. Did you update the filter script and the Nginx configuration with the X-Forwarded-Host variable as indicated in my previous message?

Can you post the result of calling the sitemap from the rest interface remotely from the Safari browser, ie typing something like:

https://your.site/rest/sitemaps

This should give a pretty good idea of the issue, if any.

Hi Laurent
Thx for your patience …
I have the “X-Forwarded-Host” additions in my nginx.conf and filter file.

First, the access to the sitemaps via iOS Safari browser works from remote.
Here is my try with “https://mydomain:8443/rest/sitemaps” in iOS browser (Safari) from remote.

-> HTTP 502 Bad Gateway

error.log

2020/05/04 14:01:56 [error] 9172#0: send() failed (111: Connection refused) while resolving, resolver: 127.0.0.1:53
2020/05/04 14:01:56 [error] 9172#0: send() failed (111: Connection refused) while resolving, resolver: [::1]:53
2020/05/04 14:02:01 [error] 9172#0: send() failed (111: Connection refused) while resolving, resolver: 127.0.0.1:53
2020/05/04 14:02:06 [error] 9172#0: send() failed (111: Connection refused) while resolving, resolver: [::1]:53
2020/05/04 14:02:11 [error] 9172#0: send() failed (111: Connection refused) while resolving, resolver: 127.0.0.1:53
2020/05/04 14:02:16 [error] 9172#0: send() failed (111: Connection refused) while resolving, resolver: [::1]:53
2020/05/04 14:02:21 [error] 9172#0: send() failed (111: Connection refused) while resolving, resolver: 127.0.0.1:53

oh_ssl.error.log

2020/05/04 14:02:26 [error] 9172#0: *135 localhost could not be resolved (110: Operation timed out), client: xxx.xxx.xxx.xxx, server: mydomain, request: "GET /rest/sitemaps HTTP/1.1", host: "mydomain:8443"

oh_ssl.access.log

xxx.xxx.xxx.xxx - - [04/May/2020:14:02:25 +0200] "GET /rest/sitemaps HTTP/1.1" 401 179 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko)"
xxx.xxx.xxx.xxx - - [04/May/2020:14:02:25 +0200] "GET /rest/sitemaps HTTP/1.1" 401 179 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko)"
xxx.xxx.xxx.xxx - master [04/May/2020:14:02:26 +0200] "GET /rest/sitemaps HTTP/1.1" 502 157 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Mobile/15E148 Safari/604.1"

for completeness, here is my nginx.conf file (domain masked):

removed to prevent duplicate code in this thread

Ok, I think I understand where this comes from. The difficult section is that one:

location = /rest/sitemaps {
	resolver 127.0.0.1 [::1];
	proxy_set_header X-Forwarded-Host $http_host;
	proxy_pass http://localhost:8080/cgi-bin/filter?user=$remote_user;
}

I indeed declare a local resolver (127.0.0.1 is your localhost, but you need to have a DNS resolver running on your server for that to work, something like dnsmasq for instance) to make sure that you don’t have any issue when starting nginx if your openhab server is not running (otherwise if I remember well nginx may just refuse to start).

I suspect you don’t have a DNS resolver running on your server, and this is what is causing all the errors in your error.log and oh_ssl.error.log.

To check this, you can change that section to:

location = /rest/sitemaps {
	proxy_set_header X-Forwarded-Host $http_host;
	proxy_pass http://127.0.0.1:8080/cgi-bin/filter?user=$remote_user;
}

The errors should be gone and you should now get the right output when on Safari (and the iOS app should now work as well). If this is the case, just try to stop openhab and restart nginx to see if this change is causing an issue or not. If not, then just leave everything like this and you are done :slight_smile:

1 Like

Excellent, works, also restart nginx service if openhab is down.
I already commented out this line once, but had not changed the localhost with the localhost IP address.
Thanks a lot for your support.

Cool, good news ! :+1:

I have updated my original post to fix that. Enjoy the set-up, really convenient for a multi-user access !

Hey all,

Just wanted to add my experience to get this working, exactly what i was looking for :slight_smile: so thanks!

But i had some issues with the filter.cgi script, it seemed the $HTTP_X_FORWARDED_HOST did not get filled with a value (and i am only using https). After lots of digging and learning… i changed the following:

curl --header "X-Forwarded-Proto: https" --header "X-Forwarded-Host: openhab.somedomein.xx" -s http://localhost:8080/rest/sitemaps | /usr/bin/jq -c "$exp"

So this changes it response to https with the correct domain. What makes it seem work for me!

Hope this helps someone, or I am doing things wrong :slight_smile:

I have looked for user level access in OH for a while and now came across this.
Most part i got working but still im not able to get iOS app working.
Basically im not sure where to put and how to handle the script. Probably i am ignorant but i did not notice if the CGI module is called somewhere or maybe it was configured from before?
I installed fcgiwrap as per this guide: Ubuntu 18.04 LTS : Nginx : Configure CGI executable Env : Server World
But im still getting 403.

In log i have:

2020/12/22 23:33:20 [error] 163120#163120: *334 FastCGI sent in stderr: "Cannot get script name, are DOCUMENT_ROOT and SCRIPT_NAME (or SCRIPT_FILENAME) set and is the script executable?" while reading response header from upstream, client: 172.69.138.14, server: openhab.somedomain.com, request: "GET /cgi-bin/filter?user=master HTTP/1.1", upstream: "fastcgi://unix:/var/run/fcgiwrap.socket:", host: "openhab.somedomain.com"

Filter script is in /var/www/cgi-bin/filter.cgi

/etc/nginx/fcgiwrap.conf:

location /cgi-bin/ {
gzip off;
root /var/www;
# Fastcgi socket
fastcgi_pass  unix:/var/run/fcgiwrap.socket;
# Fastcgi parameters, include the standard ones
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
  }

Probably something stupid i am missing but all ideas are welcome.

Seems it is actually working fine for browsers but not for the iOS app.
No matter what it either servers all sitemaps or none.

Thank you for your useful solution. I am new on OH I am really want to have this security and filtering of user…
Can you please tell me where I have to save the script and what name? (does need shortcut like.sh?)

Hello! I try to implement but I have no idea about the filter.cgi shortcut? Where should he be? In the event that I can log in with different users but the android application does not work?
Regards!

Hello, I am a little late to the party.

Anyway I would like to introduce the following project to you:

A few, very short words

Technology involved

It uses NGINX as reverse proxy and mutual TLS authentication (client certificates) to determine (with the help of a NodeJS app) which Sitemaps, MainUI pages and Items a client has access to.

Concept

Each client has a unique username and can be member of multiple organizations.
Each organization can have multiple Sitemaps.

The authorization of Item access is build on top of the Sitemap authorization, my project does not require any real configuration outside of openHAB & your CA for the client certificates.

Hi to All.
I want follow-up to the earlier message of Florian H. and let you know that I have created a fork of his great Multiuser proxy project.
His project is no longer maintained and has been archived. I have done major update to it and now it fully supports the MainUI of openHAB 3 and 4, granular filtering of user access at the level of individual Items, Pages and Sitemaps. I’m now using it in production for my quite sizable OH instance. It has been tested with both OpenHAB 3 and 4.
Feel free to look at it and test it. The documentation is fully updated as well and shall be quite self-explanatory.

GitHub - Davek145/openhab-multiuser-proxy: openHAB Multi-User support for the REST API v 2.0.2

3 Likes

Does this work via openhabcloud?

It cannot - openHAB cloud connects to openHAB via localhost (however there is a way to put a proxy in between, but mTLS doesn’t work via openHAB Cloud, so you don’t have the user information).

If you mean openHAB Cloud hosted at myopenHAB.org than not really. For filtering to work, you need to place this filtering proxy in front of openHAB instance and prevent direct access to the REST API.
For public cloud service you can limit items pushed from your local instance in the connector instead.

However, if you have cloud instance under your control, than yes, you can use it as well. You will likely need to adjust nginx proxy setup and verify how is the REST API exposed in your cloud instance. But in principle, it shall work.

David

That’s what I thought so since openhabcloud access openhab instance via openhab connector.

In my opinion you need to consider overall security architecture of your OH rollout.

If I have use case that requires securing OH Rest API and user authorization on item by item basis (that is what the multiuser-openhab-proxy with mTLS does), I shall not expose my items for remote access to shared public openhabcloud via the connector. And similar to any other third party not under my control. As this opens other attack vector to my OH instance.
Instead I would access the local OH instance via properly configured VPN. Or, if I need other features of the openhabcloud like notifications, I would roll out my own dedicated instance of it at proven SOC2 attested cloud provider, embed logic of the multiuser-proxy to it for segregating access of individual users to specific items including third party services (i.e. Alexa). Properly secure communication between it and my local OH instance, add WAF, IDS and monitoring to public facing interface etc.
It is doable to do it securely, but for sure it is not effort and cost free. And probably too complex for majority of OH users.

On the other hand, if my use case is OK with exposing my OH items to shared public openhabcloud, than I would not bother with additional filtering of REST API access. For such use case using visibleTo (+custom roles added by Karaf console) to filter items for users at presentation layer only shall be sufficient.

If you only need notifications, you can use the openHAB Foundation-operated openHAB Cloud, just disable remote access in the cloud connector‘s settings.