Switch from debian based image to alpine and add Haproxy for reverse proxy to Home Assistant

This commit is contained in:
Benjamin Renard 2023-01-08 01:59:02 +01:00
parent cffaa5acc0
commit 20f3b9e860
15 changed files with 434 additions and 22 deletions

View file

@ -1,11 +1,9 @@
FROM debian:11 FROM alpine
# Update/upgrade RUN apk add rsyslog openvpn openssh haproxy curl
RUN apt-get update RUN mkdir /root/.ssh && chmod 700 /root/.ssh && touch /root/.ssh/authorized_keys
RUN apt-get upgrade -y RUN mkdir /etc/ssh/sshd_config.d/ /etc/rsyslog.d/ /var/lib/haproxy/dev
RUN apt-get install --no-install-recommends --yes rsyslog openvpn ssh less vim iputils-ping net-tools RUN echo 'Include /etc/ssh/sshd_config.d/*.conf' >> /etc/ssh/sshd_config
RUN mkdir /root/.ssh RUN echo '$IncludeConfig /etc/rsyslog.d/*.conf' >> /etc/rsyslog.conf
RUN chmod 700 /root/.ssh
RUN touch /root/.ssh/authorized_keys
COPY entrypoint.sh /usr/local/sbin/entrypoint.sh COPY entrypoint.sh /usr/local/sbin/entrypoint.sh
RUN chmod 755 /usr/local/sbin/entrypoint.sh RUN chmod 755 /usr/local/sbin/entrypoint.sh
ENTRYPOINT /usr/local/sbin/entrypoint.sh ENTRYPOINT /usr/local/sbin/entrypoint.sh

55
README.md Normal file
View file

@ -0,0 +1,55 @@
# Docker image to get access to your Home Assistant via OpenVPN and a reverse proxy
This Alpine Linux based image allow you to mount a tunnel to your external host that have to run OpenVPN server and a reverse proxy to access to your Home Assistant. In this container, you have:
- a Rsyslog (for logging)
- a SSH service with root access (if you declare your SSH pub key)
- a OpenVPN client
- a Haproxy configured to get access to your Home Assistant
## Installation
```bash
git clone https://gitea.zionetrix.net/bn8/ha-remote-vpn /srv/ha-remote-vpn
docker pull brenard/ha-remote-vpn
```
## Configuration
### On the container
You have to:
- put your external host IP address or domain name in `srv/openvpn/client.conf` (on the `remote` line at the begining of the file)
- put your Home Assistant IP address in `srv/haproxy/haproxy.cfg` (on the `server` line at the end of the file)
- pur your SSH public key in `srv/ssh/authorized_keys`
## On your external host
You have to:
- install and configure OpenVPN using the provide `srv/openvpn/server.conf` and the `secret.key` file that will be generated by the client container on its first start
- install and configure the reverse proxy of your choice, for instance, Apache2: on a Debian host :
- Install it : `apt install apache2`
- Copy `apache2.conf` in `/etc/apache2/sites-available/home.conf` and ajust it for your needs
- Enable required modules and the site : `a2enmod proxy_http proxy_wstunnel rewrite ssl && a2ensite home && service apache2 restart`
### On your Home Assistant
You have to authorized access via your reverse proxy by adding the following lines in your `configuration.yaml` file:
```yaml
http:
use_x_forwarded_for: true
trusted_proxies:
- 192.168.1.160
```
**Note:** Adjust your docker container IP address in the list `trusted_proxies`.
## Start the container
```bash
cd /srv/ha-remote-vpn
docker run -it --rm -v "$( realpath srv ):/srv" --cap-add=NET_ADMIN brenard/ha-remote-vpn
```

42
apache2.conf Normal file
View file

@ -0,0 +1,42 @@
<VirtualHost *:80>
ServerName ha.example.com
DocumentRoot /var/www/empty
RewriteEngine on
RewriteCond %{REQUEST_URI} !(^/\.well-known/.*$)
RewriteRule ^(.*)$ https://%{SERVER_NAME}$1 [R=307]
ErrorLog /var/log/apache2/ha.example.com.error.log
CustomLog /var/log/apache2/ha.example.com.access.log combined
</VirtualHost>
<VirtualHost *:443>
ServerName ha.example.com
SSLEngine On
#SSLCertificateFile /etc/letsencrypt/live/ha.example.com/cert.pem
#SSLCertificateKeyFile /etc/letsencrypt/live/ha.example.com/privkey.pem
#SSLCACertificateFile /etc/letsencrypt/live/ha.example.com/chain.pem
SSLCertificateFile /etc/ssl/private/ssl-cert-snakeoil.key
SSLCertificateKeyFile /etc/ssl/certs/ssl-cert-snakeoil.pem
DocumentRoot /var/www/html
# Home-Assistant
ProxyPreserveHost On
ProxyRequests off
ProxyPass /api/websocket ws://172.16.88.2:80/api/websocket
ProxyPassReverse /api/websocket ws://172.16.88.2:80/api/websocket
ProxyPass / http://172.16.88.2:80/
ProxyPassReverse / http://172.16.88.2:80/
RewriteEngine on
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://172.16.88.2:80/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /(.*) http://172.16.88.2:80/$1 [P,L]
ErrorLog /var/log/apache2/ha.example.com.error.log
CustomLog /var/log/apache2/ha.example.com.access.log combined
</VirtualHost>

View file

@ -1,40 +1,107 @@
#!/bin/bash -e #!/bin/sh -e
[ "$1" == "--stop" ] && { killall haproxy openvpn sshd rsyslogd; exit; }
if [ -d /srv/rsyslog ]
then
if [ -n "$( ls /srv/rsyslog/*.conf )" ]
then
echo -n "Install Rsyslog configuration... "
cp -p /srv/rsyslog/*.conf /etc/rsyslog.d/
echo done.
fi
echo "Start rsyslog service..." echo "Start rsyslog service..."
service rsyslog start /usr/sbin/rsyslogd
echo done. echo done.
else
echo "Rsyslog configuration directory not found (/srv/rsyslog)"
fi
if [ -d /srv/ssh ] if [ -d /srv/ssh ]
then then
# Generate key if missing
if [ -z "$( ls /srv/ssh/*_key 2> /dev/null )" ]
then
echo "Generate SSH host keys..."
ssh-keygen -A
cp -p /etc/ssh/*_key* /srv/ssh/
echo done.
else
echo "Existing SSH host keys present, reuse it"
# Install host keys
echo -n "Install SSH host keys... "
cp -p /srv/ssh/*_key /srv/ssh/*_key.pub /etc/ssh/
chown root: /etc/ssh/*_key*
chmod 600 /etc/ssh/*_key
chmod 644 /etc/ssh/*_key.pub
echo done.
fi
# Install configuration
if [ -n "$( ls /srv/ssh/*.conf 2> /dev/null )" ] if [ -n "$( ls /srv/ssh/*.conf 2> /dev/null )" ]
then then
echo "Install custom SSH configuration files..." echo -n "Install custom SSH configuration files... "
cp /srv/ssh/*.conf /etc/ssh/sshd_config.d/ cp -p /srv/ssh/*.conf /etc/ssh/sshd_config.d/
echo done. echo done.
else else
echo "No custom SSH configuration files found. Put it in /srv/ssh if need (with .conf extension)." echo "No custom SSH configuration files found. Put it in /srv/ssh if need (with .conf extension)."
fi fi
# Install authorized_keys file
if [ -e /srv/ssh/authorized_keys ] if [ -e /srv/ssh/authorized_keys ]
then then
echo "Install SSH authorized keys (from /srv/ssh/authorized_keys file)" echo -n "Install SSH authorized keys (from /srv/ssh/authorized_keys file)... "
cat /srv/ssh/authorized_keys > /root/.ssh/authorized_keys cat /srv/ssh/authorized_keys > /root/.ssh/authorized_keys
chmod 644 /root/.ssh/authorized_keys
echo done.
else else
echo "No SSH authorized keys to install. Put it in /srv/ssh/authorized_keys file." echo "No SSH authorized keys to install. Put it in /srv/ssh/authorized_keys file."
fi fi
echo "Start SSH service..." # Start SSH
service ssh start echo -n "Start SSH service... "
/usr/sbin/sshd -f /etc/ssh/sshd_config
echo done. echo done.
else
echo "SSH configuration directory not found (/srv/ssh)"
fi fi
if [ -d /srv/openvpn ] if [ -d /srv/openvpn ]
then then
cp /srv/openvpn/* /etc/openvpn # Generate secret on first start
service openvpn start if [ ! -e /srv/openvpn/secret.key ]
else then
echo "OpenVPN configuration directory not mount (/srv/openvpn)" echo -n "Generate missing share secret key file... "
openvpn --genkey secret /srv/openvpn/secret.key
chmod 400 /srv/openvpn/secret.key
echo done.
fi fi
echo "Run BASH shell" # Ensure /dev/net/tun is present
bash -l mkdir -p /dev/net
if [ ! -c /dev/net/tun ]; then
mknod /dev/net/tun c 10 200
fi
# Start OpenVPN
echo -n "Start OpenVPN ... "
/usr/sbin/openvpn --daemon --config /srv/openvpn/client.conf
echo done.
else
echo "OpenVPN configuration directory not found (/srv/openvpn)"
fi
if [ -d /srv/haproxy ]
then
# Start Haproxy
echo -n "Start OpenVPN ... "
/usr/sbin/haproxy -f /srv/haproxy/haproxy.cfg
echo done.
else
echo "Haproxy configuration directory not mount (/srv/haproxy)"
fi
echo "Run interactive shell"
sh

2
srv/haproxy/blacklist Normal file
View file

@ -0,0 +1,2 @@
# Access blacklist
# 123.123.123.123

44
srv/haproxy/error.http Normal file
View file

@ -0,0 +1,44 @@
HTTP/1.0 500 Server Error
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html>
<head>
<title>Home Assistant</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<style>
* {
background: white;
color: #333;
font-family: calibri;
font-weight: bold;
font-size: 1.1em;
}
img {
max-height: 50vw;
max-width: 50vh;
}
h1 {
font-size: 1.1em;
}
p {
font-size: 0.8em;
font-style: italic;
</style>
</head>
<body>
<center>
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHRpdGxlPmZpbGVfdHlwZV9ob21lYXNzaXN0YW50PC90aXRsZT48cGF0aCBkPSJNNS4xMTQsMjcuOTQ0VjE4LjQyN0gyLjY2YS42NjguNjY4LDAsMCwxLS40Ny0xLjEzNEwxNS40MSwzLjc0OGgwYS44MTkuODE5LDAsMCwxLDEuMTY2LS4wMDhsLjAwNy4wMDcsNi45LDcuMDM3di0xLjNoMGEuNTUzLjU1MywwLDAsMSwuNTUtLjU1NmgyLjI5MmEuNTUzLjU1MywwLDAsMSwuNTQ5LjU1NnY0Ljc4TDI5LjgxMiwxNy4zaDBhLjY3MS42NzEsMCwwLDEtLjAxLjk0Mi42NTcuNjU3LDAsMCwxLS40NjIuMTlIMjYuODc2djkuNTE3YS41NTMuNTUzLDAsMCwxLS41NDkuNTU2SDUuNjY0QS41NTMuNTUzLDAsMCwxLDUuMTE0LDI3Ljk0NFoiIHN0eWxlPSJmaWxsOiMzZGJjZjM7ZmlsbC1ydWxlOmV2ZW5vZGQiLz48cGF0aCBkPSJNMjEuNzE4LDE1LjA3YTIuODQxLDIuODQxLDAsMCwwLTIuODI2LDIuODU3LDIuODc2LDIuODc2LDAsMCwwLC4yMjQsMS4xMTZsLTIuMDYxLDIuMDgzVjE0Ljc5MWEyLjgyNiwyLjgyNiwwLDEsMC0yLjEyLDB2Ni4zMzVsLTIuMDYxLTIuMDgzYTIuODc2LDIuODc2LDAsMCwwLC4yMjQtMS4xMTYsMi44MjYsMi44MjYsMCwxLDAtMi44MjYsMi44NTgsMi43OTEsMi43OTEsMCwwLDAsMS4xLS4yMjdsMy41NTksMy42VjI4LjVoMi4xMlYyNC4xNTdsMy41Ni0zLjZhMi43ODIsMi43ODIsMCwwLDAsMS4xLjIyNywyLjg1OCwyLjg1OCwwLDAsMCwwLTUuNzE1Wm0tMTEuNDQ2LDRhMS4xNDMsMS4xNDMsMCwxLDEsMS4xMy0xLjE0M0ExLjEzNiwxLjEzNiwwLDAsMSwxMC4yNzIsMTkuMDdaTTE2LDEzLjI4NGExLjE0MywxLjE0MywwLDEsMSwxLjEzMS0xLjE0M0ExLjEzNiwxLjEzNiwwLDAsMSwxNiwxMy4yODRabTUuNzIzLDUuNzg2YTEuMTQzLDEuMTQzLDAsMSwxLDEuMTMxLTEuMTQzQTEuMTM2LDEuMTM2LDAsMCwxLDIxLjcxOCwxOS4wN1oiIHN0eWxlPSJmaWxsOiNmZmYiLz48L3N2Zz4=" border="0"/>
<h1>Your Home Assistant seem not reacheable for the moment.</h1>
<p>Please check your installation or retry later.</p>
</center>
</body>
</html>

75
srv/haproxy/haproxy.cfg Normal file
View file

@ -0,0 +1,75 @@
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats timeout 30s
user haproxy
group haproxy
daemon
# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
log global
mode http
option httplog
option dontlognull
option log-health-checks
option log-separate-errors
option logasap
option contstats
option abortonclose
#option forwardfor except 172.16.81.0/24
timeout connect 3s
timeout client 60s
timeout server 60s
timeout http-request 5s
timeout check 2s
retries 3
option splice-auto
option tcp-smart-connect
errorfile 400 /srv/haproxy/error.http
errorfile 403 /srv/haproxy/error.http
errorfile 408 /srv/haproxy/error.http
errorfile 500 /srv/haproxy/error.http
errorfile 502 /srv/haproxy/error.http
errorfile 503 /srv/haproxy/error.http
errorfile 504 /srv/haproxy/error.http
# Force source IP address to connect to HA
#source 192.168.8.161
frontend ha_front
bind 0.0.0.0:80
#bind 0.0.0.0:443 ssl crt /srv/haproxy/bundle.pem
mode http
maxconn 10000
# Get user ip behind uppon reverse proxy
capture request header X-Forwarded-For len 15
# Blacklist
acl blacklist hdr(x-forwarded-for) -f /srv/haproxy/blacklist
http-request deny if blacklist
default_backend ha_back
backend ha_back
mode http
balance roundrobin
option httpchk GET / HTTP/1.0
timeout server 60s
server ha-host 192.168.8.2:8123 check observe layer4

1
srv/openvpn/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.key

53
srv/openvpn/client.conf Normal file
View file

@ -0,0 +1,53 @@
# Remote host
remote remote.fqdn.tdl 1188
# Protocol & port
proto udp
port 1188
# Interface
dev vpn-ha
dev-type tap
# MTU
tun-mtu 1500
# Secret shared key (generated on first client start)
# Note: to manually generate it, run:
# openvpn --genkey secret /srv/openvpn/secret.key
# cp /srv/openvpn/secret.key /etc/openvpn/secret.key
# chmod 400 /srv/openvpn/secret.key /etc/openvpn/secret.key
secret /srv/openvpn/secret.key
cipher AES-256-CBC
# Keepalive
ping 30
ping-restart 60
# Allow remote address changed
float
# IP address inside VPN
ifconfig 172.16.88.2 255.255.255.0
route-gateway 172.16.88.1
# Optional routes recheable througt the remote host
# route 192.168.8.0 255.255.255.0
# Run openvpn using this specified user & group
user nobody
group nogroup
persist-key
persist-tun
## Logging
# Log level (0-9)
verb 3
# Max repeat count for logged messages
mute 10
# Managing interface
# management 127.0.0.1 7588

57
srv/openvpn/server.conf Normal file
View file

@ -0,0 +1,57 @@
# Listen on specific IP address (optional, default: all)
# local 192.168.1.8
# Protocol & port
proto udp
port 1188
# Interface
dev vpn-ha
dev-type tap
# MTU
tun-mtu 1500
# Secret shared key (generated on first client start)
# Note: to manually generate it, run:
# openvpn --genkey secret /srv/openvpn/secret.key
# cp /srv/openvpn/secret.key /etc/openvpn/secret.key
# chmod 400 /srv/openvpn/secret.key /etc/openvpn/secret.key
secret secret.key
# Keepalive
ping 30
# Allow remote address changed
float
# IP address inside VPN
ifconfig 172.16.88.1 255.255.255.0
route-gateway 172.16.88.2
# Optional routes recheable througt the remote host
# route 192.168.9.0 255.255.255.0
# Run openvpn using this specified user & group
user nobody
group nogroup
persist-key
persist-tun
## Logging
# Log level (0-9)
verb 3
# Max repeat count for logged messages
mute 10
# Daemon log
log /var/log/openvpn/homeassistant.log
# Daemon status file
status /var/log/openvpn/homeassistant.status
# Managing interface
# management 127.0.0.1 7588

9
srv/rsyslog/haproxy.conf Normal file
View file

@ -0,0 +1,9 @@
# Create an additional socket in haproxy's chroot in order to allow logging via
# /dev/log to chroot'ed HAProxy processes
$AddUnixListenSocket /var/lib/haproxy/dev/log
# Send HAProxy messages to a dedicated logfile
:programname, startswith, "haproxy" {
/var/log/haproxy.log
stop
}

5
srv/rsyslog/openvpn.conf Normal file
View file

@ -0,0 +1,5 @@
# Send OpenVPN messages to a dedicated logfile
:programname, startswith, "openvpn" {
/var/log/openvpn.log
stop
}

2
srv/ssh/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*_key
*_key.pub

1
srv/ssh/authorized_keys Normal file
View file

@ -0,0 +1 @@
# Put your SSH key here to get access to your container (as root)

View file

@ -0,0 +1 @@
PermitRootLogin prohibit-password