Howto: Hide hue bridge behind reverse proxy

Giving family members and guests access to the hue bridge so that they can use the Philips app or other apps to control lights is a security nightmare. Just by accident they can nuke your complete setup. Philips is not very motivated to change that. Since I am running nginx on my firewall/routing server anyhow I came up with the following solution.

The intention of this Howto is to provide guidance on what you need. I kept it pretty general. You should have some experience with network setup, nginx and the hue bridge api.

What will work:

  • Users can control lights and groups.
  • No need to press the bridge button. Apps get a preset whitelist entry.

What will not work:

  • Entertainment
  • Any setup activity, adding lights, scenes, rules (all intended)

You will need to adapt the following to your environment. The hue bridge is connected to my core lan at 192.168.0.23. There is a guest vlan 192.168.10.255 (device vlan10) on which the proxied bridge will appear on 192.168.10.23. This is necessary since ports can not be configured, they are fixed (80 and 443).

First, you need to create the secondary ip address:

sudo ip addr add 192.168.10.23/24 broadcast 192.168.10.255 dev vlan10

To persist this, please consult your OS documentation. In addition, you need the mac-id of the interface. For now, let’s assume the mac-id is 0a:0b:0c:01:02:03

The next step is to create a openssl certificate. This work is based on hue-pass - thanks a lot to KodeCR:

Create a file hue_certificate.sh:

#!/bin/bash
if [ ! -f 'hue_certificate.pem' ]; then
	bridgeid=$1
	dec_bridgeid=`python3 -c "print(int(\"$bridgeid\", 16))"`
	openssl req -new -config hue_openssl.conf -nodes -x509 -days 4017 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve -subj "/C=NL/O=Philips Hue/CN=$bridgeid" -keyout hue_private.key -out hue_public.crt -sha256 -set_serial $dec_bridgeid
	if [ $? -ne 0 ] ; then
		echo -e "\033[31m ERROR!! Local certificate generation failed!\033[0m"
	else
		cat hue_private.key hue_public.crt > hue_certificate.pem
		#rm hue_private.key hue_public.crt
	fi
else
	echo "Certificate already exists"
fi

In the same folder create hue_openssl.conf:

[ req ]
default_bits            = 1024
default_md              = sha256
default_keyfile         = privkey.pem
distinguished_name      = req_distinguished_name
attributes              = req_attributes
req_extensions  = v3_req
x509_extensions = usr_cert


[ usr_cert ]
basicConstraints=critical,CA:FALSE
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ v3_req ]
extendedKeyUsage = serverAuth, clientAuth, codeSigning, emailProtection
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

[ req_distinguished_name ]

[ req_attributes ]

Your proxied bridge will have bridgeid 0A0B0CFFFE010203 (first 6 mac digits, FFFE, last 6 mac digits). To create the certificate, run

sh hue_certificate.sh 0a0b0cfffe010203

Here the bridgeid has to be given in lowercase.

You should now have two files: hue_certificate.pem and hue_private.key. Move them into a suitable location.

NGINX setup

Create /etc/nginx/sites-enabled/hue_192_168_10_23

server {
    listen                          192.168.10.23:80;
    server_name                     192.168.10.23;
    resolver 192.168.0.5;

    set $hue_bridge     'hue01';
    set $hb_name        '"name":"hue01"';
    set $hb_mac         '"00:17:88:xx:yy:zz"';
    set $hb_id          '"001788FFFEXXYYZZ';
    set $hb_ip          '"192.168.0.23"';

    set $hb_name_proxy  '"name":"hueproxy"';
    set $hb_mac_proxy   '"0a:0b:0c:01:02:03"';
    set $hb_id_proxy    '"0A0B0CFFFE010203"';
    set $hb_ip_proxy    '"192.168.10.23"';

    include snippets/hue_locations.conf;

}

server {
    listen                          192.168.10.23:443 ssl;
    server_name                     192.168.10.23;

    ssl on;
    access_log /var/log/nginx/access.log;

    ssl_certificate                 /home/tv/github/hue-pass/hue_certificate.pem;  
    ssl_certificate_key             /home/tv/github/hue-pass/hue_private.key; 
    resolver 192.168.0.5;

   set $hue_bridge     'hue01';
    set $hb_name        '"name":"hue01"';
    set $hb_mac         '"00:17:88:xx:yy:zz"';
    set $hb_id          '"001788FFFEXXYYZZ';
    set $hb_ip          '"192.168.0.23"';

    set $hb_name_proxy  '"name":"hueproxy"';
    set $hb_mac_proxy   '"0a:0b:0c:01:02:03"';
    set $hb_id_proxy    '"0A0B0CFFFE010203"';
    set $hb_ip_proxy    '"192.168.10.23"';

    include snippets/hue_locations.conf;

}

Edit the file and change it to reflect your environment.

Create /etc/nginx/snippets/hue_locations.conf

    location /
    {
        return 301                  https://$server_name$request_uri;
    }

    location ~ ^/api[/]*$
    {
        limit_except POST {
            deny  all;
        }
        #proxy_pass                              https://$hue_bridge/api/;
	default_type application/json;
	return 200 '[{"success":{"username":"VALIDBRIGDEUSERIDHERE"}}]';
    }

    # special for iconnecthue

    location = /api/(null)/
    {
        default_type application/json;
        return 200 '[{"error":{"type":1,"address":"/","description":"unauthorized user"}}]';
    }

    # allow controlling lights
    location ~ ^/api/(.*)/lights/(.*)/state[/]*$
    {
        proxy_pass                              https://$hue_bridge/api/$1/lights/$2/state;

        limit_except GET PUT {
            deny  all;
        }
    }

    # allow controlling groups
    location ~ ^/api/(.*)/groups/(.*)/action[/]*$
    {
        proxy_pass                              https://$hue_bridge/api/$1/groups/$2/action;

        limit_except GET PUT {
            deny  all;
        }
    }

    # everything else is read-only
    location ~ ^/api/(.*)$
    {
        proxy_pass                              https://$hue_bridge/api/$1;

        limit_except GET {
            deny  all;
        }

	sub_filter_once off;
        sub_filter_types application/json;

        sub_filter $hb_name $hb_name_proxy;
        sub_filter $hb_mac  $hb_mac_proxy;
        sub_filter $hb_id   $hb_id_proxy;
        sub_filter $hb_ip   $hb_ip_proxy;
    }

You need to replace VALIDBRIGDEUSERIDHERE with a valid username from the bridge whitelist.

Done. Restart nginx.

If you need to recreate the certificate at some point: At least on IOS you have to delete and install the Philips IOS app again.

The approach presented here can be used to control write access to individual lights, groups and setup entries. However your guest will always see the whole configuration. If you don’t want this to happen, you need a full reverse proxy which will modify the json responses.

2 Likes