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

Hi,

Based on the above solution, I have been working at creating a complete solution for both App and UI access through a reverse nginx proxy and trying to “limit” access by associating a password per sitemap, plus a master user having access to all sitemaps.

Here is the result:

nginx configuration file in reverse proxy setup giving access to basicui and App only, all other interfaces disabled:

server {
	listen 80;
	server_name myserver.dynu.net;
	return 301 https://$server_name$request_uri;
}

server	{
	listen 443 ssl;
	server_name myserver.dynu.net;

	ssl_certificate /etc/letsencrypt/live/myserver.dynu.net/fullchain.pem; # managed by Certbot
	ssl_certificate_key /etc/letsencrypt/live/myserver.dynu.net/privkey.pem; # managed by Certbot
	include /etc/letsencrypt/options-ssl-nginx.conf;
	ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

	access_log /var/log/nginx/oh_ssl.access.log;
	error_log /var/log/nginx/oh_ssl.error.log;

	auth_basic "Sitemap";
	auth_basic_user_file /etc/nginx/ohpass;

	# Disable unwanted UIs
	location ~ /(habpanel|classicui|paperui|doc|habmin) {
		return 403;
	}

	# Redirect to expected UIs (need to do it in two steps to process auth_basic before the return 302
	# authentified user is then used as the sitemap name
	location = / {
		try_files DUMMY @return302;
	}
	location @return302 {
		return 302 /basicui/app?sitemap=$remote_user;
	}

	# User management at sitemap level -> strip openhab json file through filter script (see below)
	location = /rest/sitemaps {
		proxy_set_header        X-Forwarded-Host        $http_host;
		proxy_pass http://127.0.0.1/cgi-bin/filter?user=$remote_user;
	}

	# Proxying the request to OpenHab
	location / {

		# Double test to limit access to the right UI and the right sitemap
		if ($uri = "/basicui/app") { 
			set $test  "${test}A"; 
		} 
		if ($arg_sitemap != $remote_user) {
			set $test  "${test}B"; 
		} 
		if ($remote_user = "master") {
			set $test  "";
		}
		if ($test = AB) { 
			return 403;
		}

		# Proxying the request
		proxy_pass			http://192.168.4.4:8083/;

		proxy_set_header	Host				$http_host;
		proxy_set_header	X-Real-IP			$remote_addr;
		proxy_set_header	X-Forwarded-For		$proxy_add_x_forwarded_for;
		proxy_set_header	X-Forwarded-Proto	$scheme;

		proxy_http_version 1.1;
		#proxy_set_header Connection "";

		client_max_body_size       10m;
		client_body_buffer_size    128k;

		proxy_connect_timeout      90;
		proxy_send_timeout         90;
		proxy_read_timeout         90;

		proxy_buffer_size          4k;
		proxy_buffers              4 32k;
		proxy_busy_buffers_size    64k;
		proxy_temp_file_write_size 64k;

		# OpenHab authentication (base64 of user:password)
		#proxy_set_header Authorization "Basic ZWxzYTpteWVsc2FwcHAK";

		proxy_intercept_errors on;
    	}
}

The file /etc/nginx/ohpass contains the couple sitemaps/password, created through:

htpasswd -c /etc/nginx/ohpass master
htpasswd /etc/nginx/ohpass sitemap1
htpasswd /etc/nginx/ohpass sitemap2
...

sitemap1, sitemap2 are the filename of the different sitemaps (without the extension).

As the equivalent of mod_ext_filter is not available on nginx, the simplest solution I found was to call a script through the cgi-bin interface. Here is the corresponding filter script:

#!/bin/sh

eval $(echo "$QUERY_STRING"|awk -F'&' '{for(i=1;i<=NF;i++){print $i}}') # import the GET variables
read QUERY_STRING
eval $(echo "$QUERY_STRING"|awk -F'&' '{for(i=1;i<=NF;i++){print $i}}') # import the POST variables

if [ "$user" = "master" ]; then
	exp="."
else
	exp="[.[]|select(.name==\"$user\")]"
fi

echo Content-type: application/json
echo

curl --header "X-Forwarded-Host: $HTTP_X_FORWARDED_HOST" -s http://localhost:8083/rest/sitemaps | /usr/bin/jq -c "$exp"

The script is conceptually very similar to the one from Daniel, adding the exception of the master user having access to all sitemaps.

Once this is set-up, basicui is directly accessible through http://myserver.dynu.net (redirected automatically to SSL access and the full UI URL). User is prompted for a sitemap name and a password, and then get access to the UI. The same URL/sitemap name/password can be used in the App with the same limitations (ie either user is master and sees all, or user is sitemap name and limited to that given sitemap).

I have been testing that setup for some time and found it so far quite safe, with a clear separation by user and no unauthorized access to restricted sitemaps (I have for instance created a specific sitemap for my kids so that they can open the portal and switch on/off the alarm, but don’t want them to have access to the watering system or other critical automation at home).

I hope this will help anyone willing to set up access control to his/her OpenHab installation.