Using NGINX Reverse Proxy (Authentication and HTTPS)

I was going to write this up separately but it probably belongs as a subsection to this article.

Setting up Fail2Ban

Fail2ban is a nice little service that will update your firewall to ban connections from certain IP addresses after a certain number of failed login attempts in a certain amount of time. It provides an extra layer of protection to your server now that it is exposed to the Internet and every script kiddie in the world sees your open port and starts knocking on the door.

If you have ssh exposed to the Internet, openHAB 1.x, or a whole host of other services, this is a great tool to filter out sources of attack once bad actors are detected.

For the most part the bulk of the attacks will be bots trying combinations of common and/or default usernames and passwords (ALWAYS reset the default password people!) but there is no reason why your server needs to be spending CPU cycles and wasting bandwidth processing these sorts of attacks.

Basic setup is pretty easy but I found and have been using a little bit of extra special sauce to make the bans permanent because Iā€™ve seen some IPs which hit me once a day rather than a bunch of times in a row.

#Installation

These instructions are for Ubuntu. They should work for raspbian as well but I donā€™t know for sure.

Installation is just a quick:

apt-get update
apt-get install fail2ban

It will probably have a bunch of dependencies to install as well.

Configuration

In good Debian fashion, the config files are in /etc/fail2ban. The config file to be concerned with is jail.conf. This is the default config but unfortunately this file gets overwritten during updates so what you want to do is create a jail.local file and comment out everything. This gives you an example of all the configs without overriding everything in the main .conf file. You can do this with the following command:

sudo awk '{ printf "# "; print; }' /etc/fail2ban/jail.conf | sudo tee /etc/fail2ban/jail.local

Now you will want to change a few settings.

Find the section that starts with [DEFAULT]. Uncomment the [DEFAULT] and ignoreip line and add your LAN, openVPN subnet, Docker subnet, et al (i.e. any internal subnets that should be allowed to fail to login without being banned). If you have some known external IPs or IP ranges that you know you will be logging in from add those as well. You donā€™t want to accidentally ban yourself when you know the access is you.

Next find the bantime parameter. By default the ban time is 10 minutes. If there are maxretry failed attempts in failtime the IP will be banned for this long. I set mine to -1 which means any failed login attempt results in a ban. Uncomment and set to an appropriate value for you.

Next find the findtime parameter and set it to an appropriate value. I set mine to 48 hours (i.e. 2880 minutes) so I can catch those guys coming after me slowly.

The next field is the maxretry field. I use 1 so they get banned on the first attempt.

Scroll down a bit and configure destemail and mta to an appropriate value. I use ssmtp on my system so system emails get sent to my gmail address. This will give you an alert email every time fail2ban bans an IP. We will configure it to include a bunch of information from whois as well so you can see where the attempt is coming from.

Now find the [nginx-http-auth] section. Uncomment this section, add an enabled=true and if necessary change the path to the nginx error.log file. Iā€™m using a Dockerified nginx so my log is in /opt/nginx/logs. Finally add the action. This is what fail2ban will do when it finds a failed login. I use action_mwl which bans the IP and sends an email with whois report and relevant log lines to destmail we configured. Look at the commented out sections for alternatives actions.

I donā€™t know if this is required but the SSHD tutorials I used have you do this so Iā€™m doing it too. There are a number of files in the filters.conf directory which contain regular expressions that are used to determine if a line in the log file represents a failed attempt. Below I specify to use the nginx-http-auth.conf file but I donā€™t know if that is required.

[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port    = http,https
action = %(action_mwl)s
#logpath = %(nginx_error_log)s
logpath = /opt/nginx/logs/error.log

If you have a standard install the default %(nginx_error_log)s can be left unchanged.

Now stop fail2ban so we can set a default firewall configuration.

Firewall

If you havenā€™t already, install iptables-persistent which will let you save your iptables configuration when the machine reboots.

If you already have a good iptables configuration skip this step. If not, the following will create a basic base firewall table that allows incoming connections for ssh (port 22) and HTTP (port 80) and HTTPS (port 443).

sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
sudo iptables -A INPUT -j DROP

If you want to access to other ports from other machines beyond localhost add additional -A INPUT lines like above to accept those connections as well.

You can see the current firewall rules by typing:

sudo iptables -S

You can save firewalls so they survive a reboot using:

sudo dpkg-reconfigure iptables-persistent

Create a blacklist

Now we need to make a minor change to /etc/fail2ban/iptables-multiport.conf. In the section actionstart add a line to cat the offending IP to ip.blacklist. It should look like this:

actionstart = <iptables> -N f2b-<name>
              <iptables> -A f2b-<name> -j <returntype>
              <iptables> -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
              cat /etc/fail2ban/ip.blacklist \
              | while read IP; do iptables -I f2b-<name> 1 -s $IP -j REJECT --reject-with icmp-port-unreachable; done

The above will take all of the IP addresses in ip.blacklist and add a rule to ban them in iptables when fail2ban startsup. Thus once fail2ban flags an IP as bad it will permanently be banned until you remove the IP from the blacklist.

To populate the blacklist add an echo to add the banned IP to the blacklist to actionban.

actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
            # Persistent banning of IPs
            echo '<ip>' >> /etc/fail2ban/ip.blacklist

Start fail2ban

Now start up the service.

Fail2ban works by scrolling through your log files so every time you start it will see all the failed attempts currently in your relevant log files. If you are under particular amount of attack that will mean you will be deluged with dozens if not a hundred emails every time you restart fail2ban.

Likewise, because it follows the log file it isnā€™t always really fast about recognizing all the IPs right away. Some attacks have dozens of attempts before fail2ban sees them.

Finally, Iā€™m not 100% certain that it catches every attempt every time. But it is better than nothing. In the month or so that Iā€™ve been running it on ssh Iā€™ve banned close to 10,000 IPs.

#Removing an IP from the Ban
First find and remove the IP from /etc/fail2ban/ip.blacklist.

Next find the rule blocking the IP you want to now allow using the following command:

sudo iptables -S | grep <ip address>

where <ip address> is the IP you want to unban.

Copy the rule you found with the above command and delete it with:

sudo iptables -D <rule specification>

where <rule specification> is the rule copied.

#References


I donā€™t remember where I got the trick to create and use the ip.blacklist

9 Likes

Nice one! Iā€™ve been using fail2ban for a while but hadnā€™t had the chance to write it up (I did this a while ago and forgot a few important steps).

Could you add a quick one liner for removing an entry off of the ban list? I feel if anyoneā€™s like me and types with their fists when on the mobile then thereā€™s a good chance of a failure.

excellent stuff
I also recommend fail2ban. I use it in all my Linux installations.

Same here.

Good catch. Iā€™ve added an unban section.

Hello all
I just followed the instructions of Benjy but I can not get ot work.

When I enter
http://openhabianpy
into my browser it always redirects to
https://start/index

This is the config at /etc/nginx/sites-enabled/openhab

server {
        listen                          80;
        listen                          [::]:80;
        server_name                     openhabianpi.fritz.box;
        return 301                      https://$server_name$request_uri;
}
server {
        listen                          443 ssl;
        listen                          [::]:443 ssl;
        server_name                     openhabianpi.fritz.box;
        ssl_certificate                 /etc/ssl/3bert.crt;
        ssl_certificate_key             /etc/ssl/3bert.key;
        add_header                      Strict-Transport-Security "max-age=31536000"; # Remove if using self-signed and are having trouble.
        location / {
                proxy_pass                              http://localhost:8080/;
                proxy_buffering                         off;
                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;
                auth_basic                              "Username and Password Required";
                auth_basic_user_file                    /etc/nginx/.htpasswd;
        }
}

What is wrong?

Thank you very much in advance!

Best regards,
Matze

There is a button for that :slight_smile:

Firstly Iā€™d remove the Strict-Transport-Security line in your config, since youā€™re using self-signed certificates

Secondly, where did the listen [::]:80; come from? If you had the Strict-Transport-Security line on a previous setup, then itā€™s likely still cached even when you remove it or use it for another item. See http://classically.me/blogs/how-clear-hsts-settings-major-browsers

Finally, what happens if you change:

                proxy_set_header Host                   $http_host;

to

                proxy_set_header Host                   $proxy_host;

Remember to test sudo nginx -t and sudo service nginx restart too! Hope this helps.

That was a fast solution! :slight_smile:
Thank you very much!

Changing to $proxy_host did it!

Not a problem, out of interest, which of the three solutions solved your problem or did all three?

Hi, Iā€™ve followed this thread carefully but canā€™t find an answer to my problem.

All I want to do is use NGINX as a reverse proxy without SSL or other authentication, with local LAN clients only. I want NGINX to filter requests by IP address and deny access to certain OH sitemaps, depending on the IP address. (This is to work around the problem where a tablet user can access any sitemap simply by selecting it in the pop-over try that appears when clicking the icon.)

Details, including relevant nginx conf file code snippets are here. (If you all prefer I consolidate & move my entire query over here I will condense it and do so.)

I actually just updated the docs version of this (which has also been moved to a general security thread)

Note the part about ā€œsatisfy anyā€, which I beleive is the line that youā€™re missing. You also seem to be missing the proxy pass line in your more specific location.

I reviewed your updated docs & noted the new section on controlling access by IP address. I tried adding the ā€œsatisfy any;ā€ line above my IP allow/deny blocks (see other post here,) but with no effect.

I emphasize that when I access sitemaps NOT selected by the special location ~ { } regexp, I have no problems with access at all. Nginx & OH, & my OH tablet clients work fine together.

There is only a problem when trying to access a specific sitemap that I wish to block access to for certain IP addresses on the LAN, while allowing other IPs access. As mentioned in my other post, I know my regexp & allow/deny rules are working fine, because I do get proper ā€œaccess forbidden by ruleā€¦ā€ messages in the error log, for the denied IP addresses.

The trouble is when a permitted IP address tries to access the location ~ regexp { } item, it spits back back the ā€œno such file or directoryā€ message.

With appreciation for your time.

A wanted to post a quick update about running nginx in a Docker container and reverse proxying multiple servers, not just openHAB.

Docker

I ended up abandoning running nginx in Docker primarily because I canā€™t use soft links in the volumes I mount into the container. This becomes a problem with the LetsEncrypt certs because the ā€œcurrentā€ certs are presented in a directory with softlinks. To make it work I would have to point at the actual cert files and then update it every time the cert expires and gets updated. The advantages of Docker at this point became overcome but this future maintainence.

Reverse Proxying Multiple Services

What I wanted to do was use addresses like

Upon further research, the solutions discussed above with rewrites provide only half of the solution.

Nginx does not actually go into the HTML files being transmitted and update the URLs used therein to point to /gogs (for example). So nginx provides the main page as expected but all the other elements of the page and links on the page omit the leading ā€œ/gogs/ā€ in subsequent requests and Nginx fails to find the resource to return.

Some servers (gogs and calibre in particular) have an option you can use when starting the server to rewrite the URLs to include the ā€œ/gogs/ā€ in the pages it serves. Thus you get the original page through nginx and each subsequent request has the correct relative URL.

However, as a forewarning, Iā€™ve not yet made this work correctly. Itā€™s low on my list of priorities right now.

And this also means that using ā€œ/openhab/ā€ to get at your openHAB will not work unless/until changes are made to OH to support adjusting the URLs in the pages it serves.

Dear friends,

first of all I would like to express my gratitude for this brillant piece of software! I just love it!

Now to my question or problem: In the habpanel I created basically two dashboards and I want to redirect based on $remote_user. The reason behind is that I canā€™t figure out how to set up users that have only access to distinctive dashboards (as far as I skimmed the tutorials and documentation there is no such concept as of now, right?).

I tried it with a very easy approach through:

proxy_pass http://127.0.0.1/habpanel/index.html#/view/$remote_user;

but I get connection lost trying to reestablish connection.

Has anybody an idea how to sperate dashboards and restrict to specific users? I canā€™t get my head around this topic somehow.

Any hint is appreciated.

Thanks,
David

Iā€™ve discovered that NGIFX wonā€™t install if Apache2 service is running, even if Apache2 is on a different set of ports.

sudo service apache2 stop 

allows ngifx to install

Forget NGINX! Use Apache2 instead, if you want to change the path to e.g. https://myserver/openhab/basicui/app ā€“ after quite a few hours, I got it working with the following config file:

<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=CENSORED,ou=CENSORED,ou=person,dc=codewizards,dc=co"
        AuthLDAPBindPassword "CENSORED"
        #AuthzLDAPAuthoritative on
        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>

This file is included in my default-ssl.conf:

<VirtualHost ... CENSORED ...>

... lots of other stuff ...

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

Both basicui and paperui work fine this way. But no guarantee: Maybe there are still more Substitute rules needed. I didnā€™t test everything, yet.

I hope this helps everyone who wants to use openHAB behind a reverse-proxy (and with LDAP-authentication)!

Best regards, Marco :slight_smile:

P.S.: It seriously sucks that openHAB doesnā€™t allow to configure a path-prefix! I have written quite a few programs in my life, already, and always when there were URLs involved, things like a prefix (or even more) were configurable. How can you even get the idea that this is an unnecessary feature?! And most importantly: If you really donā€™t want to configure it, why donā€™t you hard-code such a prefix (e.g. ā€œopenhab/ā€) ā€“ it would be far easier to remap an existing prefix than to deal with all these individual paths (like ā€œbasicuiā€, ā€œpaperuiā€, ā€œrestā€ etc.).

1 Like

ARG!!! The Android app did not work, yet :frowning:

The following two lines need to be added:

AddOutputFilterByType SUBSTITUTE application/json

Substitute "s|http://|https://|n"

Iā€™ll try to edit my previous post and add this.

Again an update:

RequestHeader unset Accept-Encoding

is needed! I thus removed the comment-# in my post above. It seems the smarthome.js was still in my browser-cache, after I disabled and tested it.

Well, itā€™s a pity that we canā€™t use gzip, but it doesnā€™t really matter. Weā€™re usually in the LAN, anyway :wink:

Hey @nlmarco,
your contribution here is very much appreciated! Why donā€™t you move these steps to their own tutorial? The topic sure deserves one and a thread is easier found than reply number 38 here :wink: