How would you secure a web server running in an LXC instance?

I have an Apache Guacamole instance running on an Ubuntu LXC server on the Turris. Everything works well but I am now interested in security hardening of the container.

As a summary:

  • Guacamole runs on Tomcat, on port 8080
  • Nginx is used to reverse proxy into Guacamole.
    • Access to Guacamole is through Nginx port 80 and port 443 (for SSL using Lets Encrypt)
    • Nginx will proxy the request to port 8080

On the router, the port forwards look like this:

So, in terms of firewalls, how can I secure this? I only want WAN access to be via HTTP port 80 and 443. No other ports should be accessible from the WAN.

Would this be achieved with a firewall rule at the router level? Or a firewall rule at the container level? I’d prefer a router level configuration because ufw on LXC is a bit broken by default and requires a few tweaks to get working…

WAN access to Clients inside your network is blocked by default, so from WAN only port 80 and 443 on your LXC-Container can be accessed.

What you could do:

  • Create a separate Subnet which then can be separated from your LAN using firewall rules on TO. This would prevent an attacker that hijacked your LXC-Container to access Clients on your LAN. LAN Clients would still be able to access your Container
  • set iptables rules on your LXC-Container to only allow access on port 80 and 443. This would allow an attacker inside your LAN to only access those ports.
  • An atacker that has root access to your TO will always have access to your LXC-Containers, there‘s nothing you can do about it. On the other hand your TO is separated from it‘s containers and all containers are separated from each other by default, except network access which you could restrict on your TO or inside those containers.

So your configuration is safe enough if your LAN is safe, you could protect your LAN by using a different subnet and you should use a new container for each new service you want to run on your TO to seperate them from each other.

1 Like

Thanks for your reply, I really appreciate it!

  • set iptables rules on your LXC-Container to only allow access on port 80 and 443. This would allow an attacker inside your LAN to only access those ports.

If I were to do this, wouldn’t that impact port 8080?

Because port 80 & 443 effectively map to port 8080 (via Nginx), if I block all other ports, wouldn’t that mean my Guacamole instance is unreachable?

As far as I understand your setup nginx and guacamole are both running inside the same Container, so nginx accesses guacamole via localhost:8080. This port can be open while access to this port from any other machine can be restricted.

Beginners Guide to iptables:

But honestly as long as you are not administrating the IT-services of fort knox your setup is safe enough right now :wink:

Feel free to ask if you need further assistance

1 Like

Thanks for your guidance!

For the benefit of others who might be in a similar position to me, these are the rules I ended up implementing inside the container (courtesy of thegeekstuff.com):

# Reset
iptables --flush

# Default drop
iptables -P INPUT DROP
iptables -P FORWARD DROP

# Allow incoming HTTP
iptables -A INPUT -i eth0 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT

# Allow incoming HTTPS
iptables -A INPUT -i eth0 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEPT

# Allow outgoing HTTPS
iptables -A OUTPUT -o eth0 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEPT

# Allow outgoing HTTP
iptables -A OUTPUT -o eth0 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT

# Allow Ping from Inside to Outside
iptables -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT

# Allow Loopback Access
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Allow outbound DNS
iptables -A OUTPUT -p udp -o eth0 --dport 53 -j ACCEPT
iptables -A INPUT -p udp -i eth0 --sport 53 -j ACCEPT

# Log Dropped Packets
iptables -N LOGGING
iptables -A INPUT -j LOGGING
iptables -A LOGGING -m limit --limit 2/min -j LOG --log-prefix "IPTables Packet Dropped: " --log-level 7
iptables -A LOGGING -j DROP

# Save changes
sudo /sbin/iptables-save

My next steps will be experimenting with fail2ban and implementing 2FA with Duo inside Guacamole which should be an interesting learning curve!

1 Like

Hi,

I have a fair amount of experience with this, using a TO, with nginx in one (Ubuntu) LXC and guacamole in another (Ubuntu) LXC.

I have previously implemented Duo for 2FA with guacamole (and also CAS, https://apereo.github.io/cas/5.2.x/index.html), but you should know that Google Authenticaor/TOTP is now available & I feel that is the best option, by far, for 2FA.

Regarding IP filters, I think you may be going overboard and introducing unnecessary complexity. The original firewall (port forward) rules you had, I feel, are sufficient… Honest!

FWIW, I usually implement such rules via CLI (172.27.0.100 is the address of my nginx proxy):

uci add    firewall  redirect
uci set    firewall.@redirect[-1].name=Allow-HTTP-Inbound
uci set    firewall.@redirect[-1].target=DNAT
uci set    firewall.@redirect[-1].src=wan
uci set    firewall.@redirect[-1].dest=lan
uci set    firewall.@redirect[-1].proto=tcp
uci set    firewall.@redirect[-1].src_dport=80
uci set    firewall.@redirect[-1].dest_ip=172.27.0.100
uci set    firewall.@redirect[-1].dest_port=80

uci add    firewall  redirect
uci set    firewall.@redirect[-1].name=Allow-HTTPS-Inbound
uci set    firewall.@redirect[-1].target=DNAT
uci set    firewall.@redirect[-1].src=wan
uci set    firewall.@redirect[-1].dest=lan
uci set    firewall.@redirect[-1].proto=tcp
uci set    firewall.@redirect[-1].src_dport=443
uci set    firewall.@redirect[-1].dest_ip=172.27.0.100
uci set    firewall.@redirect[-1].dest_port=443

uci commit firewall; /etc/init.d/firewall reload

I note UFW doesn’t work in LXC containers, and I feel Fail2Ban i(or even knockd) is not useful here - YMMV.

In any case, I just leave IP-layer security alone on the LXCs & leave that up to the router IP tables (port forward rules, as above).

The other issue is SSL/HTTPS (I assume you’ve got nginx redirecting HTTP/:80 to HTTPS/:443), and there is a lot of material about regarding the creation of ‘secure’ certificates.

FWIW, I use a script similar to the following (although the below example are self-signed certs):

PKI_DIR="/etc/nginx/ssl"
mkdir -p ${PKI_DIR}
chown -R root:root ${PKI_DIR}
chmod -R 600 ${PKI_DIR}
pushd ${PKI_DIR} ## was: cd ${PKI_DIR}

openssl req -nodes -new -newkey rsa:2048 -keyout server.key -out server.csr \
  -subj "/C=GB/ST=England/L=London/O=Essential Widgets/OU=IT/CN=www.server.com"
openssl x509 -signkey server.key -req -days 365 -in server.csr -out server.crt \
  -extfile <(printf "subjectAltName=DNS:server.me,DNS:vm-server.home,DNS:*.dtdns.net")
cat server.crt server.ca-bundle > server.ca-bundle.crt

openssl dhparam 4096 -out dh4096.pem
openssl dhparam 2048 -out dh2048.pem
ln -s dh2048.pem dhparam.pem

chmod 640 *.* 
chmod 644 *.crt *.pem 
popd

The above is actually the easy bit…

1 Like

What follows is about hardening at the HTTPS/SSL layer. When you’ve finished this hardening, test your site via: SSL Server Test (Powered by Qualys SSL Labs) - I score A+ :slight_smile:

This is a fragment from my nginx configuration file, including some useful references:

###############################################################################
### Configuration for HTTPS/SSL
    ssl_certificate              /etc/nginx/ssl/server.ca-bundle.crt;
    ssl_certificate_key          /etc/nginx/ssl/server.key;
    ssl_protocols                TLSv1.2;  ## TLS only, no SSL

## see: https://www.bjornjohansen.no/optimizing-https-nginx
#   ssl_ciphers                  HIGH:!aNULL:!MD5;
    ssl_ciphers                  ECDH+AESGCM:ECDH+AES256:ECDH+AES128:!ADH:!AECDH:!MD5;
    ssl_prefer_server_ciphers    on;

## see: https://wiki.mozilla.org/Security/Server_Side_TLS#DHE_handshake_and_dhparam
    ssl_dhparam                  /etc/nginx/ssl/dhparam.pem;

    add_header Strict-Transport-Security "max-age=31536000" always;

## OCSP Stapling, test via (works?): openssl s_client -connect 127.0.0.1:443 -status
## see: https://support.comodo.com/index.php?/Knowledgebase/Article/View/1091/0/certificate-installation--nginx
## see: https://www.digicert.com/ssl-support/nginx-enable-ocsp-stapling-on-server.htm
## see: https://community.letsencrypt.org/t/howto-ocsp-stapling-for-nginx/13611/6
    resolver                8.8.4.4  8.8.8.8;
    ssl_stapling            on;
    ssl_stapling_verify     on;
    ssl_trusted_certificate /etc/nginx/ssl/server.ca-bundle;
1 Like

And here is (a fragment of) the nginx server section, which doesn’t include any hardening, but which some people may find useful):

### SERVER block - HTTPS listener parameters
server {
   access_log off;

    listen                        443 ssl http2 default_server;
    server_name                   www.server.me server.me;

    location /guac/ {
        proxy_pass        http://lxc-tomcat.home:8080/guacamole/;
        proxy_cookie_path /guacamole/ /guac/;
    }

    location / {                                                    
        proxy_pass        http://lxc-tomcat.home:8080/guacamole/;
        proxy_cookie_path /guacamole/ /;
    }
}
2 Likes