So.. you’ve finished installing and setting up all your docker containers, plugins ect. Radarr is doing its thing, Plex is chugging away… everything is just peachy! And now you want to be able to access the unraid GUI outside your network.

The safest method you can do this is by setting up a VPN. You can do that by installing the OpenVPN container but that won’t give you access if your server or the docker service crashes. So setting up the VPN on the router ect is much more recommended.

BUT you’re not always in a situation where you can connect to a VPN. For example your work computer. Be it you don’t have the administrator rights to change your network settings or your company policy forbids it..

This is where Apache Guacamole is useful. By installing Apache Guacamole,  centosxfcevnc Firefox vnc and letsencrypt we can gain access to the unraid GUI externally. And by setting up fail2ban and geo-block we can protect our self from bruteforce attempts at gaining access!

Installation

Apache Guacamole

I use the jasonbean/guacamole container. Nothing special you need to think about. Add your custom port and select your appdata location. The default username and password is guacadmin
Note: 
It isn’t mentioned in the documentation but if you are installing the container using docker run or compose you need to add -e 'OPT_MYSQL'='Y'


Firefox
VNC Web Browser

Search for VNC Web Browser in community applications and you will find a template by cheesemarathon for the consol/centos-xfce-vnc container.

I have found that using the VNC Web Browser Container gives me constant connection errors and have switched to the Firefox container instead.
Install the container and add your VNC Password if you want. Take note of the VNC port as you will need that later.
Use the VNC_PASSWORD variable described here 

Do not add SSL in the container settings as it looks like Guacamole doesn’t support VNC with encryption. 


Let’s Encrypt

To access all this we’ ll use the linuxserver/letsencrypt container from linuxserver.

This container sets up an Nginx web server and reverse proxy with php support and a built-in letsencrypt client that automates free SSL server certificate generation and renewal processes. It also contains fail2ban for intrusion prevention. 

Before we start you need to acquire a domain. You can do that on duckdns or any other domain service. I’m using https://domains.google/ and I’m very happy with that.

If you have a dynamic ip-address you can setup the captinsano DDclient container and have that update your synthetic record. If you don’t want to pay for a custom domain, using the duckdns container will work just fine.

Forward your domain to your public IP address. After you’ve done that add your different ANAME/CNAME records e.g guacamole.yourdomain.com or unraid.yourdomain.com

@ = root domain (technicalramblings.com) and points to my external ip
www = sub domain 
grafana = sub domain
TTL: (Time to Live) How often a copy of the record stored in cache must be updated or discarded.
Installation
  1. Container Port: 80 – Choose your desired host port. e.g 81 (You can’t set this to 80 as the unRAID web GUI uses that. )
  2. Container Port: 443 – Set this to 444 or something else (On update 6.4 unraid will use port 443 if you setup https and it’s better to be ahead of time so it won’t cause any issues)
  3. Enter you email
  4. Add your domain e.g yourdomain.com
  5. Add your different sub domains e.g guacamole,guac ect
  6. Validation: Select your validation type. http will work in most cases (Unless your ISP blocks port 80)
  7. Container Path: /config Install the container config to your desired location. I recommend using an SSD.

Next is port forwarding.

This is done on your router and you need to forward port 80 and 443 to the ports you chose in step 1 and 2. So if your servers IP is 192.168.1.2 and you have chosen that the container is on port 81, you need to forward all traffic on port 80 to port 81 on IP 192.168.1.2 And do the same for port 443 to 444.

If you’re unsure how to do this on your router check out: Portforward.com

Next go to https://yourserverip:444 or http://yourserverip:81 If you now see the Nginx welcome page, it works. Also test if yourdomain.com redirects you to the nginx welcome page.

Note: TTL differs from each provider, some has a minimum 60 minutes before DNS propagates and others have 1 minute. So it might take a while before https://yourdomain.com works.

 


Configuring Apache Guacamole

Browse to the Apache guacamole container and login. You can add a new admin user if you want.

The default username and password is guacadmin

Go to Settings and click on Connections
Click on New Connection
Give the connection a name. I just called it Firefox. And Location is ROOT and Protocol is VNC
Set your Maximum number of connections, I use 3

Scroll down to Parameters – Network.
Add your Hostname: Your Unraid IP
Add your Port: This is the VNC port to the Firefox container (default is 7914)
For Authentication input the password you set for the Firefox container.
Click Save

 

You can now test the connection you have created.

Click on your user name and select the connection.  You should now be presented with the desktop of the VNC Web Browser container.

 

Tip: If you want to go back to settings just press ctrl + shift + alt to open the side menu. Here you can also copy text that you have copied within the VNC connection!

Here you can open Firefox and go to your unraid IP and log in.

Windows 10 VM with RDP

You can also easily add your Windows VM using RDP, this will also let you mount your shares so you can manage your files like you would at home. 

 
This is how I added my Windows 10 Pro VM:

The 3389 port is the RDP port.


Configuring Nginx

Go to your letsencrypt appdata location. Find the nginx folder and then edit the file called default or add a new .conf file in the site-conf folder. I recommend using notepad++

If you want to use this on a subdomain I recommend creating a guacamole.conf file instead and adding the nginx config to that.

Below is an nginx config that will give you A+ ratings on securityheaders.io and ssllabs.com

server_name guacamole.domain.com; This is where you will add your domain name e.g guacamole.duckdns.org

proxy_pass http://192.168.1.34:8089/; This is your IP and port to the Apache Guacamole container

READ THE COMMENT ON add_header X-Frame-Options AND add_header Content-Security-Policy IF YOU USE THIS ON A SUBDOMAIN YOU WANT TO IFRAME!

# GUACAMOLE CONTAINER
server {
listen 80;
server_name guacamole.domain.com;
return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl http2;
server_name guacamole.domain.com;

##SSL SETTINGS
## READ THE COMMENT ON add_header X-Frame-Options AND add_header Content-Security-Policy IF YOU USE THIS ON A SUBDOMAIN YOU WANT TO IFRAME!

## Certificates from LE container placement
ssl_certificate /config/keys/letsencrypt/fullchain.pem;
ssl_certificate_key /config/keys/letsencrypt/privkey.pem;

## Strong Security recommended settings per cipherli.st
ssl_dhparam /config/nginx/dhparams.pem; # Bit value: 4096
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_timeout  10m;

## NOTE: The add_header Content-Security-Policy won't work with duckdns since you don't own the root domain. Just buy a domain. It's cheap
## Settings to add strong security profile (A+ on securityheaders.io/ssllabs.com)

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none; #SET THIS TO index IF YOU WANT GOOGLE TO INDEX YOU SITE!
add_header Content-Security-Policy "frame-ancestors https://*.$server_name https://$server_name"; ## Use *.domain.com, not *.sub.domain.com (*.$server_name) when using this on a sub-domain that you want to iframe!
add_header X-Frame-Options "ALLOW-FROM https://*.$server_name" always; ## Use *.domain.com, not *.sub.domain.com (*.$server_name) when using this on a sub-domain that you want to iframe!
add_header Referrer-Policy "strict-origin-when-cross-origin";
proxy_cookie_path / "/; HTTPOnly; Secure"; ##NOTE: This may cause issues with unifi. Remove HTTPOnly; or create another ssl config for unifi.
more_set_headers "Server: Classified";
more_clear_headers 'X-Powered-By';
##END SSL SETTINGS
    
location / {
    proxy_pass http://192.168.1.34:8089/;
    proxy_buffering off;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    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_no_cache $cookie_session;
                
    }
} 

Configuring fail2ban

From github: Fail2Ban scans log files like /var/log/auth.log and bans IP addresses conducting too many failed login attempts. It does this by updating system firewall rules to reject new connections from those IP addresses, for a configurable amount of time. Fail2Ban comes out-of-the-box ready to read many standard log files, such as those for sshd and Apache, and is easily configured to read any log file of your choosing, for any error you wish.

Luckily Fail2ban comes preinstalled with your letsencrypt container, so you only need to add the filter and edit the jail.local file!

For this to work we need the letsencrypt container to be able to see the catalina.out  file in the Apache Guacamole container.

  • Open the letsencrypt container settings.
  • Add a path from the letsencrypt container to the Apache Guacamole container.
      • Name: guacamole fail2ban
      • Container path: /guacamole or whatever you prefer
      • Host path: Your path to the Apache Guacamole /log folder e.g /AppData/ApacheGuacamole/guacamole/log
      • Access mode: Read only
    • Description: fail2ban path into guacamole /log folder

jail.local

Go to the fail2ban folder inside the letsencrypt appdata folder and edit the jail.local file.
For my config I have set the bantime to 86400 seconds (24h)
The findtime is 600 seconds and maxretry is 3
At the end of the jail.local file add the following:

[guacamole-auth]

enabled = true
port = http,https
filter = guacamole-auth
logpath = /guacamole/catalina.out
ignoreip = 192.168.1.0/24
  • The ignore IP is so that fail2ban won’t ban your local IP. Check out http://jodies.de/ipcalc if you are wondering what your netmask is.
  • The logpath is the container path you created in step 2. And catalina.out the Guacamole log.

Now, there is already a filter (guacamole.conf) for Guacamole in the filter.d  folder inside the fail2ban folder. But that filter won’t work unless we make a change to it.
Copy the guacamole.conf file and rename it guacamole-auth.conf
In the guacamole-auth.conf file change:

failregex = ^.*\nWARNING: Authentication attempt from <HOST> for user "[^"]*" failed\.$

to

failregex = \bAuthentication attempt from \[<HOST>(?:,.*)?\] for user ".*" failed\.

Restart the letsencrypt container.

If you get the error below in the fail2ban.log file you can comment the 3 date pattern lines in the guacamole-auth.conf file.

2018-06-12 21:36:49,121 fail2ban.filter [351]: ERROR Error during seek to start time in "/guacamole/catalina.out"
2018-06-12 21:36:49,121 fail2ban.filterpoll [351]: ERROR Caught unhandled exception in main cycle: TypeError('an integer is required',)

Comment these lines by adding # in front.

#datepattern = ^%%b %%d, %%ExY %%I:%%M:%%S %%p
#^WARNING:()**
#{^LN-BEG}

Remember to restart the container anytime you make a change in the conf file.

Banned

The fail2ban.log file should output something like this:

2018-06-12 21:39:07,529 fail2ban.jail [350]: INFO Jail 'guacamole-auth' started
2018-06-12 21:39:30,779 fail2ban.filter [350]: INFO [guacamole-auth] Ignore 192.168.1.1 by ip
2018-06-12 21:39:44,801 fail2ban.filter [350]: INFO [guacamole-auth] Found 77.16.72.179 - 2018-06-12 21:39:44
2018-06-12 21:39:57,420 fail2ban.filter [350]: INFO [guacamole-auth] Found 77.16.72.179 - 2018-06-12 21:39:57
2018-06-12 21:40:00,025 fail2ban.filter [350]: INFO [guacamole-auth] Found 77.16.72.179 - 2018-06-12 21:39:59
2018-06-12 21:40:00,196 fail2ban.actions [350]: NOTICE [guacamole-auth] Ban 77.16.72.179

Unbanning

If you managed to ban yourself or a friend banned themself you can do this to unban.

Bash into the container with:
docker exec -it letsencrypt bash

Enter fail2ban interactive mode:
fail2ban-client -i

Check the status of the jail:
status guacamole-auth

Output

fail2ban> status guacamole-auth
Status for the jail: guacamole-auth
|- Filter
| |- Currently failed: 0
| |- Total failed: 3
| `- File list: /guacamole/catalina.out
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: 77.16.72.179

unban with:
set guacamole-auth unbanip 77.16.72.179

If you already know the IP you want to unban you can just type this:
docker exec -it letsencrypt fail2ban-client set guacamole-auth unbanip 77.16.72.179

Adding geo-blocking

Purpose

Restrict access to Internet content based upon the user’s geographical location

Installation

If you are using the letsencrypt container the nginx module is already installed. If not you can take a look at the howtoforge guide.

That said the container doesn’t come with the GeoIP database.

1. Download the database and place it in your letsencrypt appdata location e.g /appdata/letsencrypt/geoip2/GeoIP.dat I added a folder called geoip2 and placed the file there.

Note: The IPv6 database will also block IPv4!

NGINX

2. Go to your letsencrypt appdata folder. In the nginx folder you will find a file called nginx.conf Add the following after http {:

http {

geoip_country /config/geoip2/GeoIP.dat;

# LOCAL IP ALLOW GEO BLOCK
    geo $lan-ip {
    default no;
    192.168.1.0/24 yes;
    }
# GEO IP BLOCK SITE 1
    map $geoip_country_code $allowed_country {
    default no;
    YOUR-COUNTRY-CODE yes;
    }

Instead of "YOUR-COUNTRY-CODE" add your own country code from this list. This will block all other countries than the one you choose.

The geo $lan-ipis for allowing you to access the domain on your LAN. Check out http://jodies.de/ipcalc if you are wondering what your netmask is.

Note: The geo $lan-ip part is only needed if you set default to no

For it to actually block you need to add this in your server block:

 # LOCAL IP ALLOW GEO BLOCK
    if ($lan-ip = yes) {
    set $allowed_country yes;
    }
    
# COUNTRY GEO BLOCK 
    if ($allowed_country = no) {
    return 444;
    }

So if you created a guacamole.conf file in the nginx/site-confs folder you add it there.

It will then look like this:

# GUACAMOLE CONTAINER
server {
listen 80;
server_name guacamole.domain.com;
return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl http2;
server_name guacamole.domain.com;

##GEOBLOCK
 # LOCAL IP ALLOW GEO BLOCK
    if ($lan-ip = yes) {
    set $allowed_country yes;
    }
    
# COUNTRY GEO BLOCK 
    if ($allowed_country = no) {
    return 444;
    }

##SSL SETTINGS
## READ THE COMMENT ON add_header X-Frame-Options AND add_header Content-Security-Policy IF YOU USE THIS ON A SUBDOMAIN YOU WANT TO IFRAME!

## Certificates from LE container placement
ssl_certificate /config/keys/letsencrypt/fullchain.pem;
ssl_certificate_key /config/keys/letsencrypt/privkey.pem;

## Strong Security recommended settings per cipherli.st
ssl_dhparam /config/nginx/dhparams.pem; # Bit value: 4096
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_timeout  10m;

## NOTE: The add_header Content-Security-Policy won't work with duckdns since you don't own the root domain. Just buy a domain. It's cheap
## Settings to add strong security profile (A+ on securityheaders.io/ssllabs.com)

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none; #SET THIS TO index IF YOU WANT GOOGLE TO INDEX YOU SITE!
add_header Content-Security-Policy "frame-ancestors https://*.$server_name https://$server_name"; ## Use *.domain.com, not *.sub.domain.com (*.$server_name) when using this on a sub-domain that you want to iframe!
add_header X-Frame-Options "ALLOW-FROM https://*.$server_name" always; ## Use *.domain.com, not *.sub.domain.com (*.$server_name) when using this on a sub-domain that you want to iframe!
add_header Referrer-Policy "strict-origin-when-cross-origin";
proxy_cookie_path / "/; HTTPOnly; Secure"; ##NOTE: This may cause issues with unifi. Remove HTTPOnly; or create another ssl config for unifi.
more_set_headers "Server: Classified";
more_clear_headers 'X-Powered-By';
##END SSL SETTINGS
    
location / {
    proxy_pass http://192.168.1.34:8089/;
    proxy_buffering off;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    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_no_cache $cookie_session;
                
    }
} 

Blocked

You can test if it worked with a VPN or do a performance test from a location that is blocked here https://www.webpagetest.org/

Optional:  Organizr

Another security layer is using Organizr to block access to your guacamole.domain.com by having you to log into Organizr first! And you can even add a fail2ban filter on the Organizr login form! By using server authentication you will be shown a 401 Unauthorized page unless you log in first.

How to setup Organizr with Let’s Encrypt on unRAID

Optional:  Basic http auth

Another security layer is using basic http auth.

Linuxservers letsencrypt container is  already be pre configured to ban failed http auths with fail2ban!

Sources: https://github.com/fail2ban/fail2ban/issues/1574