Apache2 reverse-proxy with LDAP-authentication, HTTPS and URL-path-prefix

openHAB2 + Apache2 reverse-proxy + LDAP authentication + HTTPS + URL-path-prefix

This tutorial describes how to get openHAB2 running with

using an Apache2 reverse-proxy (to be precise, my system is: Apache 2.4.7 on Ubuntu-server 14.04.5).

According to this forum thread it is not possible to configure a path-prefix with NGINX. With Apache2, however, I succeeded in doing so.

Apache2 modules

You can see in /etc/apache2/mods-enabled/ which modules are already enabled. To see what’s installed, look into /etc/apache2/mods-available/.

To install a module, you should use your OS package manager – in case of Debian/Ubuntu and the like, that’s apt-get. I’m sorry, but I can’t tell you for sure what packages you need to install, but based on dpkg -S xxx, I think all you need is:

apt-get install apache2 libldap-2.4-2

If you follow this tutorial with a vanilla installation from scratch, please provide feedback, if this is fine or if you need more.

To enable a module, which is already installed, use:

a2enmod xxx

…for example:

a2enmod proxy_wstunnel

Here’s the content of my /etc/apache2/mods-enabled/ (created with ls|sort):

  • access_compat.load
  • alias.conf
  • alias.load
  • auth_basic.load
  • authn_core.load
  • authn_file.load
  • authnz_ldap.load
  • authz_core.load
  • authz_groupfile.load
  • authz_host.load
  • authz_svn.load (not needed)
  • authz_user.load
  • autoindex.conf
  • autoindex.load
  • dav.load (not needed)
  • dav_svn.conf (not needed)
  • dav_svn.load (not needed)
  • deflate.conf
  • deflate.load
  • dir.conf
  • dir.load
  • env.load
  • filter.load
  • headers.load
  • ldap.conf
  • ldap.load
  • mime.conf
  • mime.load
  • mpm_prefork.conf
  • mpm_prefork.load
  • negotiation.conf
  • negotiation.load
  • php5.conf (not needed)
  • php5.load (not needed)
  • proxy.conf
  • proxy_html.load
  • proxy_http.load
  • proxy.load
  • proxy_wstunnel.load
  • rewrite.load
  • setenvif.conf
  • setenvif.load
  • socache_shmcb.load
  • ssl.conf
  • ssl.load
  • status.conf
  • status.load
  • substitute.load
  • xml2enc.load

My Apache2 is used for other stuff, too, hence there are modules that are probably not needed for you to run openHAB. Because I have no time to disable them and test, if openHAB still runs, I have simply listed all my modules above and marked those that I believe to be unnecessary.

Apache2 config file default-ssl.conf

I assume you already configured /etc/apache2/sites-enabled/default-ssl.conf properly and are able to access “https://yourserver/” with a browser. If no, please consult other docs on how to set up HTTPS.

IMHO the easiest editor is mcedit – if you don’t have it, install it:

apt-get install mc

Then edit the configuration file:

mcedit /etc/apache2/sites-enabled/default-ssl.conf

At the end, before </VirtualHost>, you add one Include line:

# ... lots of other stuff ... leave this unchanged!

        Include /etc/apache2/openhab/openhab-ssl.conf
    </VirtualHost>
</IfModule>

Apache2 config file openhab-ssl.conf

The include-file needs to be created by you. First create the directory:

mkdir /etc/apache2/openhab/

Then create the file:

mcedit /etc/apache2/openhab/openhab-ssl.conf

…and put the following content:

        <Location "/openhab">
                Options SymLinksIfOwnerMatch

                AuthType Basic
                AuthName "openHAB"

                ## BEGIN LDAP
                AuthLDAPURL "ldap://localhost:389/ou=person,dc=codewizards,dc=co?cn?sub?(objectClass=person)"
                AuthLDAPBindDN "cn=____________,ou=daemon,ou=person,dc=codewizards,dc=co"
                AuthLDAPBindPassword "___________________________________"
                AuthBasicProvider ldap
                Require valid-user
                ## END LDAP

                RewriteEngine On
                RewriteRule "/openhab/openhab/(.*)" "/openhab/$1" [R,L]
                RewriteRule "/openhab/?(.*)" "http://localhost:10080/$1" [P,L]
#               LogLevel alert rewrite:trace8

## We cannot use ProxyPass, because this implicitly adds its own rewrite-rules *before* ours!
## Hence, we cannot redirect the browser from .../openhab/openhab/... to .../openhab/...!
#               ProxyPass http://localhost:10080
                ProxyPassReverse http://localhost:10080

                ProxyHTMLEnable On

## The ProxyHTMLExtended can be used to work on *embedded* JavaScript. It does not work
## on separate .js-files. Thus, I now use SUBSTITUTE instead -- which works on both separate
## and embedded. Thus, this ProxyHTML* is not needed, anymore.
#               ProxyHTMLExtended On
#               ProxyHTMLURLMap / /openhab/ [e]
#               ProxyHTMLURLMap /basicui /openhab/basicui
#               ProxyHTMLURLMap /openhab/basicui /openhab/basicui

## Seems the suppression of gzip is not needed. Found this hint in the web,
## before, but the problem was actually another one. Thus, commented the following line
## again.
## UPDATE: IT IS NEEDED! My smarthome.js was obviously cached, before.
                RequestHeader unset Accept-Encoding

                AddOutputFilterByType SUBSTITUTE text/html
                AddOutputFilterByType SUBSTITUTE text/css
                AddOutputFilterByType SUBSTITUTE application/javascript
                AddOutputFilterByType SUBSTITUTE application/json
                Substitute "s|/basicui/|/openhab/basicui/|n"
                Substitute "s|/rest/|/openhab/rest/|n"
                Substitute "s|'/rest'|'/openhab/rest'|n"
                Substitute "s|/paperui/|/openhab/paperui/|n"
                Substitute "s|/inbox/|/openhab/inbox/|n"
                Substitute "s|/icon/|/openhab/icon/|n"
                Substitute "s|http://|https://|n"
        </Location>

You must adapt all the stuff between ## BEGIN LDAP and ## END LDAP to your local environment. Please consult mod_authnz_ldap for details. Btw. I’m using the Apache Directory Server (not allowed to link it) listening on localhost:389 (using setcap 'cap_net_bind_service=+ep' /usr/lib/jvm/java-8-oracle/jre/bin/java).

Additionally, you must either change your openHAB2 server to listen on localhost:10080 (see below) or change the port 10080 to the default 8080.

You probably do not need to change anything else. However, I left a few comments from my experiments to give a bit more background info.

For explanation:

  1. We need the rewrite-rule RewriteRule "/openhab/openhab/(.*)" "/openhab/$1" [R,L], because some URLs are sent relative and then resolve to a duplicate “openhab/openhab/”. This rule sends the browser a redirect to the proper (non-duplicate) path.

  2. The rule RewriteRule "/openhab/?(.*)" "http://localhost:10080/$1" [P,L] replaces the simple ProxyPass directive. As you can see, this is the 2nd rule. If we use the easier ProxyPass instead (which I first did), then the rewrite-engine gets only proxied URLs to see, because the proxying happens first. However, our deduplication-rewrite-rule (see above) must be first, before any proxying is done. That’s why, we do not use ProxyPass and instead “manually” configure the proxying using this “RewriteRule” directive.

  3. We still keep the ProxyPassReverse though. It rewrites URLs into the opposite direction – e.g. if the openHAB server responds with a HTTP redirect, this makes sure our client does not get the internal URL, but the correct, external one.

  4. I’m not sure whether the ProxyHTMLEnable On is still needed or whether the SUBSTITUTE would be sufficient. This directive causes links in HTML documents to be rewritten. It currently works this way and I have no time to experiment with it being omitted. Please feel free to test and give feedback!

  5. The RequestHeader unset Accept-Encoding suppresses compression. Without it, openHAB would return gzipped data and our SUBSTITUTE would not work (because its rules don’t match the compressed content).

  6. All the SUBSTITUTE stuff below causes the entire content (HTML, CSS, JavaScript, JSON) to be string-replaced so that the URLs returned to the browser contain the “openhab/” URL-path-prefix. Maybe this list is still not complete – however it seems to work fine for me for a few days, already.

It seriously sucks that openHAB does not support to configure an URL-path-prefix or at least uses a hard-coded one. To write string-replacement-rules for replacing one single path-prefix by another one (e.g. to map “openhab/” to “oh/”) would be far easier and more reliable than to deal with all the paths individually ("/basicui/", “/rest”, “/icon/” etc.). But this is the way, it currently is, and the list of replacement-rules seems pretty complete. This might easily break with the next openHAB-release, though :frowning:

Make openHAB2 listen on localhost:10080 only

By default, openHAB2 listens on all network interfaces on the ports 8080 (HTTP) and 8443 (HTTPS). In order to make it listen only on localhost (more secure!) and on another port (to prevent collisions with other services) you do the following:

Create the file openhab/conf/services/org.ops4j.pax.web.cfg and put the following content:

## See: https://ops4j1.jira.com/wiki/display/paxweb/Basic+Configuration

## We listen on localhost only, because we use the Apache2 as a facade proxy.
## This way, we can use Apache's LDAP authentication and Apache's HTTPS.
org.ops4j.pax.web.listening.addresses=127.0.0.1

## We disable SSL, because our facade-proxy (Apache) is doing SSL with the proper cert.
org.osgi.service.http.secure.enabled=false

## We listen on port 10080 internally (on localhost), while the facade proxy does
## HTTPS on 443 and HTTP for the intellihouse-OpenPGP-encrypted stuff on 80.
org.osgi.service.http.port=10080

After restarting openHAB2, it should listen on port 10080 on IP 127.0.0.1 (localhost), only. You can check this with netstat -lntp.

It is important that the port configured here matches the reverse-proxy-configuration of your openhab-ssl.conf above!

Restart Apache2

When your configuration is complete, you must restart the server:

service apache2 restart

That’s it. If there are no errors and your openHAB2 server is running, you should now be able to access it with your browser.

I hope this tutorial is helpful!

Cheers, Marco :slight_smile:

5 Likes

This seems like a huge oversimplification. Is there no simpler way? Im worried about this messing up the authentication my other apache apps are using, or would this LDAP config apply only to openhab?

I wish the Linux community would stop producing such good apps which all insist on running their own webserver. Openhab should expect to be run behind Apache2, I believe, and not even provide its own webserver.

I want one box running webmin, zoneminder and openhab. But OH2 and WM want to run their own webserver, making the load on the machine much greater, and it makes securing the box much more difficult.

Thanks for your very good writeup.

No, this is no oversimplification at all! This is (except for the password and the cn) exactly the LDAP configuration that I currently use. Of course, you can configure LDAP differently and this might become far more complex, but the stuff inbetween the ## BEGIN LDAP and ## END LDAP definitely works.

Btw. this LDAP configuration means that everyone being represented by an LDAP object of type person in the directory ou=person,dc=codewizards,dc=co (including sub-directories recursively) is allowed to access openHAB. In other words: Every employee of my company – which is exactly what I want :wink:

IMHO, the 3 purely LDAP-related lines (all starting with “AuthLDAP”) are not complicated, but if you want it even simpler, you can use file-based authentication. This would reduce these 3 lines to 1 (the other 2 lines are still needed – with AuthBasicProvider file instead of AuthBasicProvider ldap).

See: https://httpd.apache.org/docs/2.4/howto/auth.html

And if you want to use LDAP, you should read this:

https://httpd.apache.org/docs/2.4/mod/mod_authnz_ldap.html

Everything inside a “<Location> … </Location>”-section applies to this section, only (maybe with a few exceptions – but I can’t think of any, right now). There’s thus (nearly) no risk that you screw up things outside of this location.

I’m not aware of openHAB being related to the Linux (= kernel) community at all.

It is very useful to be able to also run it standalone, without a facade-HTTP! This has lots of advantages in many situations – most importantly during development.

Btw, the openHAB team actually thought this was the standard use-case and thus didn’t implement authentication in openHAB 2 – leaving this to the facade server.

When you want to run a Java application behind an HTTP facade server, both processes must somehow communicate. Tomcat provides a special protocol for this purpose, but this works AFAIK with Apache2 httpd only (and not with other web servers – maybe you want to use nginx or lighthttpd). Additionally, where’s the difference, if your backend-server listens on port localhost:9001 talking protocol X or if it listens on localhost:9002 talking protocol HTTP?

The approach to connect the facade server with the backend server via HTTP works with every web-server that supports reverse-proxying and is thus a very good decision.

However, I totally agree that they made a major mistake when they designed openHAB to run in the root (/) of the HTTP – not having a path-prefix!

If there was a path-prefix, the Apache2 configuration needed would be less than half the size!

So what’s the problem? Even with its default configuration, openHAB doesn’t listen on port 80 or 443, but 8080 and 8443, thus you don’t run into a port collision. And as already said: You somehow have to connect the two – what’s wrong with an HTTP-reverse-proxy-connection?

Btw. I used the port 10080 here in this post to reduce the risk of port collisions even more, because the ports 8080 and 8443 are used by default by most Java HTTP servers.

You’re very welcome!

Implemented, but its not working…Im getting in the browser a 502 proxy error:

The proxy server received an invalid response from an upstream server.
The proxy server could not handle the request GET /openhab/.

Reason: Error reading from remote server

The apache log shows:

[proxy_http:error] [pid 29735] (20014)Internal error (specific information not available): [client A.B.C.D:51105] AH01102: error reading status line from remote server localhost:8443
[proxy:error] [pid 29735] [client A.B.C.D:51105] AH00898: Error reading from remote server returned

The openhab logs seem empty…systemctl status openhab2.service shows the openhab service to be running. Any thoughts on what I might have done wrong?

The /etc/apache2/openhab/openhab-ssl.conf file is:

<Location "/openhab">
        Options SymLinksIfOwnerMatch

        AuthType Basic
        AuthName "openHAB"

        AuthBasicProvider file
       	AuthUserFile /etc/apache2/.htpasswd
            Require valid-user

        RewriteEngine On
        RewriteRule "/openhab/openhab/(.*)" "/openhab/$1" [R,L]
        RewriteRule "/openhab/?(.*)" "http://localhost:8443/$1" [P,L]

        ProxyPassReverse http://localhost:8443

        ProxyHTMLEnable On

        RequestHeader unset Accept-Encoding

        AddOutputFilterByType SUBSTITUTE text/html
        AddOutputFilterByType SUBSTITUTE text/css
        AddOutputFilterByType SUBSTITUTE application/javascript
        AddOutputFilterByType SUBSTITUTE application/json
        Substitute "s|/basicui/|/openhab/basicui/|n"
        Substitute "s|/rest/|/openhab/rest/|n"
        Substitute "s|'/rest'|'/openhab/rest'|n"
        Substitute "s|/paperui/|/openhab/paperui/|n"
        Substitute "s|/inbox/|/openhab/inbox/|n"
        Substitute "s|/icon/|/openhab/icon/|n"
        Substitute "s|http://|https://|n"
</Location>

In your openhab-ssl.conf, you use the URL http://localhost:8443/. Usually, port 8443 is the SSL port! Hence this cannot work, because you specified the protocol-prefix http:// -- not https://.

Additionally, I doubt that talking HTTPS with the openHAB2 backend server works at all. Because it makes no sense, I didn't test it. And you should definitely use HTTP between the facade and the backend server!

Thus, please check all your ports and make sure, you use HTTP -- not HTTPS!

Just to clarify: Your client (eg. browser or android app) talks HTTPS with the facade server remotely, but the facade server talks locally (host is localhost!) plain HTTP with the backend server.

Finally, I recommend to read my tutorial thoroughly again, because I clearly state that I even completely disable the unused HTTPS in the backend openHAB2 server and make it listen on localhost:10080 (protocol HTTP) only.

Thank you very much. I am now able to access Openhab through my Apache2! Where is your tip jar?

Just wanted to notify you about issue openhab-core/150, which I just created to hopefully get a URL-path-prefix, soon.

1 Like

Sorry to come back on this topic so late.
However, just tried to implement this apache configuration for the reverse proxy - which is working fine in principle, but I could not bring charts and also linked external websites to work with this config.
Adding a

Substitute "s|'/chart'|'/openhab/chart'|n"

does not help.

For external website embedding I’m using Webview in the sitemap e.g. something like that in the sitemap.

Webview item=myitem label="myLabel" url="http://xxx.yyy.zzz/bin/xx.exe/dn?L=vs_liveticker&evaId=1231801" height=8

For reference, by apache config after the LDAP section (which is working fine by the way)

                RewriteEngine On
		RewriteCond %{HTTPS} !=on 
		RewriteRule "/openhab/openhab/(.*)" "/openhab/$1" [R,L]
		#RewriteRule "/openhab/?(.*)" "http://localhost:10080/$1" [P,L]
		RewriteRule "/openhab/?(.*)" "http://xxx.yyy.zzz:8080/$1" [P,L]

		#RedirectMatch permanent ^/$ /openhab.app?sitemap=default
		#RedirectMatch permanent "^/openhab$" "/start/index"

		## We cannot use ProxyPass, because this implicitly adds its own rewrite-rules *before* ours!
		## Hence, we cannot redirect the browser from .../openhab/openhab/... to .../openhab/...!
		#               ProxyPass http://localhost:10080
		#               ProxyPassReverse http://localhost:10080
		ProxyPassReverse http://xxx.yyy.zzz:8080/
		
		ProxyHTMLEnable On

		## The ProxyHTMLExtended can be used to work on *embedded* JavaScript. It does not work
		## on separate .js-files. Thus, I now use SUBSTITUTE instead -- which works on both separate
		## and embedded. Thus, this ProxyHTML* is not needed, anymore.
		#               ProxyHTMLExtended On
		#               ProxyHTMLURLMap / /openhab/ [e]
		#               ProxyHTMLURLMap /basicui /openhab/basicui
		#               ProxyHTMLURLMap /openhab/basicui /openhab/basicui

		## Seems the suppression of gzip is not needed. Found this hint in the web,
		## before, but the problem was actually another one. Thus, commented the following line
		## again.
		## UPDATE: IT IS NEEDED! My smarthome.js was obviously cached, before.
		RequestHeader unset Accept-Encoding

		AddOutputFilterByType SUBSTITUTE text/html
		AddOutputFilterByType SUBSTITUTE text/css
		AddOutputFilterByType SUBSTITUTE application/javascript
		AddOutputFilterByType SUBSTITUTE application/json
		Substitute "s|/basicui/|/openhab/basicui/|n"
		Substitute "s|/rest/|/openhab/rest/|n"
		Substitute "s|'/rest'|'/openhab/rest'|n"
		Substitute "s|/paperui/|/openhab/paperui/|n"
		Substitute "s|/inbox/|/openhab/inbox/|n"
		Substitute "s|/icon/|/openhab/icon/|n"
		Substitute "s|http://|https://|n"
		
		Substitute "s|/habpanel/|/openhab/habpanel/|n"
		Substitute "s|/habmin/|/openhab/habmin/|n"
		
		Substitute "s|'/chart'|'/openhab/chart'|n"
		Substitute "s|'/start'|'/openhab/start'|n"

Does charts or external webview elements in sitemap display correctly on your side with the provided config?
If not what to add for charts and external websites to work correctrly behind the apache reverse proxy?

Thanks for any hint.

Thanks @nlmarco! You’re that man. I’ve been struggling to get remote access working with my Apache2 Reverse Proxy and your Tutorial got it working for me.

It’s ridiculous that the OpenHAB software isn’t written in a way (with relative URLs) that makes all of these custom rewrite rules unnecessary, but thanks for working around those shortcomings and sharing your results.

We would happily accept and merge your contribution to implement this.

Thank you for your snarky reply.

I’m new to OpenHAB, this issue appears to have been around for years, and honestly this is the type of issue that keeps people spending their evenings hacking the configuration to make the system run at all instead of using it for its intended purpose.

I’d love to fix this but it’s going to be a while until I’m at a point where I can. In the meantime I stand by my remark that this is a dumb issue tat is indicitive of a poor coding decision resulting in serious inflexibility.

@nlmarco did you find you had to add anything else? My BasicUI works fine but PaperUI gives am “ERROR 404 - not found” in a small popup in the bottom right and the “Addons” selection in the sidebar menu is missing.

No, it is a feature that no one who is willing to donate their own precious development time to implement. This is an open source project. People work on what they want to work on. If something doesn’t get done, it’s because it’s not a problem for the people who are actively contributing to the code and the people who are complaining about it are not contributing to the code.

You are right, it is a problem that has been around for years. But no one has yet stepped up to code it. Until someone does step up it won’t get fixed.

So yes, my comment was a bit snarky. But waltzing in here and calling us “dumb” and “ridiculous” because OH doesn’t offer a feature that you desire, a feature I might add that isn’t really needed by the vast majority of OH users, is insulting, offensive, and demoralizing. A little bit of snark is the least of what I wanted to respond with.

Most non-technial OH users use myopenhab.org.
A small minority of OH users will host their own instance of myopenhab.org on site or in the cloud.
An even smaller minority access through SSH tunnels or VPN.
The tiniest minority of users expose their OH to the Internet through a reverse proxy.

1 Like

I had a whole reply I just deleted.

This is getting massivly off-topic and hijacking this excellent Tutorial.