#!/bin/bash


## Depends lxc-scripts installed


##
## Install
##

HOST_EXTERNAL_DEVICE=${HOST_EXTERNAL_DEVICE:-eth0}

version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; }

shorewall_candidate_version=$(echo $(apt-cache policy shorewall | grep "Candidate:" | cut -f 2 -d :))

## Support for docker introduced in 5.0.6
if version_gt "$shorewall_candidate_version" 5.0.5; then
    apt-get install -y shorewall
else
    (
        VERSION="5.0.7.2-1"
        cd /tmp &&
        wget http://ftp.fr.debian.org/debian/pool/main/s/shorewall-core/shorewall-core_${VERSION}_all.deb &&
        wget http://ftp.fr.debian.org/debian/pool/main/s/shorewall/shorewall_${VERSION}_all.deb &&
        dpkg -i shorewall-core_${VERSION}_all.deb shorewall_${VERSION}_all.deb &&
        rm shorewall-core_${VERSION}_all.deb shorewall_${VERSION}_all.deb
    ) || {
        echo "Failed to install shorewall."
        exit 1
    }
fi

case $(lsb_release -is) in
    Debian)
        case $(lsb_release -rs) in
            10)
                ## we had trouble with ``nft`` shorewall
                update-alternatives --set iptables /usr/sbin/iptables-legacy
                ;;
        esac
        ;;
esac



apt-get install -y dnsutils </dev/null


##
## Configuration
##

cat <<EOF > /etc/shorewall/README
Important notes gathered through time:


# Shorewall duties on our host

- block any access from outside to local ports if not mentionned
  explicitely in shorewall.

- connect external ports to LXC (dockers has its own means)
  - This uses ``/var/lib/lxc/*/shorewall`` files

- let mosh connect correctly

- ensure a correct access from Host/LXC/Docker to server's services.
  For instance, an Host/LXC/Docker should be able to as if it was
  external: ``curl https://myhostwebsite``. This is called routeback
  and requires some special rules.


# Shorewall restarting and cache

Some process in shorewall seems to be using cache in some ways in
recent version that implies that it won't take actions if files are
not changed. A simple 'touch FILE' seems to be enough. Notice the
'Compiling' lines appearing in ``shorewall restart``.

It's always good to double-check in ``iptables -nL`` that some rules
actually seem to match your intention.

Don't forget that ``iptables-save`` is probably the best way to get
the full rules printed on stdout.


# Debian, ovh kernels and iptables-nft

Starting from Debian10, iptables by default uses iptables-nft... which
works well with default debian kernel. OVH kernels DO NOT provide
necessary kernel and we must:

    update-alternatives --set iptables /usr/sbin/iptables-legacy

Note that transition is a little tricky because BOTH ways can have
their tables simultaneously. Use ``iptables-nft -nL`` and
``iptables-legacy -nL`` to check.

For now, we had little success to properly have the ``nft`` version
working properly on debian kernel. So even on debian kernel, we switch
here to iptables-legacy if on debian system.


# Interaction with docker's iptables rules

This is configured in ``shorewall.conf``, thanks to a simple::

    DOCKER=Yes


# Route back

Be sure to check in /var/lib/lxc/*/shorewall definitions, they
must include special stances (see in next section).

On the side of shorewall, all network interface should be declared in
``/etc/shorewall/interfaces``.


# lxc ``shorewall`` files

Prefer the usage of ``ports`` files. If you insist on having a better
control of rules per LXC, you can use ``shorewall`` files.

They should be located in /var/lib/lxc/*/shorewall. This is a standard
redirection from external host port 10022 to lxc's port 22, on port
tcp::

    DNAT     net     lan:%%IP%%:22   tcp 10022
    #DNAT     net     lan:%%IP%%:22   udp 10022

Routeback (access of the same service from Host/LXC/Docker on the external
address) is given by these additional rules::

    DNAT     lan     lan:www:80     tcp    80    -  %%HOST_INTERNET_IP%%
    DNAT     lan     lan:www:443    tcp    443   -  %%HOST_INTERNET_IP%%

    DNAT     fw     lan:www:80     tcp    80    -  %%HOST_INTERNET_IP%%
    DNAT     fw     lan:www:443    tcp    443   -  %%HOST_INTERNET_IP%%


# lxc ``ports`` files

They should be located in /var/lib/lxc/*/ports. This is a standard
redirection from external host port 10022 to lxc's port 22, on both
tcp and udp::

   10022:22    ## Normal port
   # 10023:23  ## This is commented !

Note that comments are supported also.


EOF



cat <<EOF > /etc/shorewall/zones
fw                firewall
net               ipv4
lan               ipv4
EOF

cat <<EOF > /etc/shorewall/macro.Mosh
#######################################################################################################
#                                         DO NOT REMOVE THE FOLLOWING LINE
##############################################################################################################################################################
#ACTION         SOURCE          DEST            PROTO   DPORT   SPORT   ORIGDEST        RATE    USER    MARK    CONNLIMITTIME     HEADERS SWITCH  HELPER
#

PARAM   -       -       udp     60000:61000
EOF



cat <<EOF > /etc/shorewall/interfaces
#ZONE   INTERFACE       BROADCAST       OPTIONS
net     $HOST_EXTERNAL_DEVICE
## Uncomment to enable vpn setup
#vpn    tun0    detect


## All interfaces that require route back should be listed
## here:
lan     lxcbr0  -                       routeback

BEGIN SHELL

ifconfig=\$(ifconfig)

echo "BEGIN DOCKER adding networks rules:" >&2
for docker_net in \$(docker network list -f driver=bridge -q); do
    gws=\$(docker network inspect "\$docker_net" --format "{{range .IPAM.Config}}{{.Gateway}}{{\"\n\"}}{{end}}") || continue
    for gw in \$gws; do
        if=\$(printf "%s" "\$ifconfig" | egrep "\$gw" -B 1 | head -n 1 | cut -f 1 -d " ")
        echo "  lan    \$if -  routeback" >&2
        echo "lan    \$if -  routeback"
    done
done
echo "END DOCKER" >&2

true

END SHELL

EOF

cat <<EOF > /etc/shorewall/policy
#SOURCE DEST    RULE     LOG

fw      all     ACCEPT
lan     all     ACCEPT
net     all     DROP info
all     all     DROP info
EOF

cat <<EOF > /etc/shorewall/rules
SSH/ACCEPT      net             fw
Ping/ACCEPT     net             fw

Mosh(ACCEPT)    net     fw

BEGIN SHELL

host_ip="\$(/sbin/ifconfig $HOST_EXTERNAL_DEVICE 2> /dev/null | sed "s/^.*inet //g" | grep ^[0-9] | sed "s/ .*$//g")"

for name in \$(lxc-ls-running); do
    ip=\$(dig +short A "\$name")
    [ -e "/var/lib/lxc/\$name/shorewall" ] &&
    cat /var/lib/lxc/\$name/shorewall |
        sed -r "s/%%HOST_INTERNET_IP%%/\$host_ip/g" |
        sed -r "s/%%IP%%/\$ip/g"

    if [ -e "/var/lib/lxc/\$name/ports" ]; then
        for ports in \$(cat /var/lib/lxc/\$name/ports | sed -r 's/#.*\$//g'); do
            lxc_port=\${ports#*:}
            ext_port=\${ports%:*}
            echo "LXC \$name: redirection from \$host_ip:\$ext_port -> \$ip:\$lxc_port" >&2
            for proto in tcp udp; do
                for zone in net lan fw; do
                    echo "DNAT     \$zone     lan:\$ip:\$lxc_port     \$proto    \$ext_port    -  \$host_ip"
                done
            done
        done
    fi

done

true

END SHELL

EOF

cat <<EOF > /etc/shorewall/masq
$HOST_EXTERNAL_DEVICE    lxcbr0
EOF

cat <<EOF > /etc/shorewall/start
## correct a bug that prevent DHCP packet to be correctly sent between
## LXC, preventing them to receive an IP.

. /etc/default/lxc

if [ -d "/sys/class/net/\$LXC_BRIDGE" ] && [ "\$(cat /sys/class/net/\$LXC_BRIDGE/operstate)" = "up" ]; then
    source_file=
    if [ -e /etc/init/lxc-net.conf ]; then
        source_file=/etc/init/lxc-net.conf
    elif [ -e /usr/lib/x86_64-linux-gnu/lxc/lxc-net ]; then
        source_file=/usr/lib/x86_64-linux-gnu/lxc/lxc-net
    fi
    if [ "\$source_file" ]; then
        code=\$(egrep '^\s+iptables.*\s+-j\s+' \$source_file | grep -v '\-D' | sed -r 's/^\s+[^-]+/run_iptables /g')
        echo "Adding LXC rules:"
        echo "\$code"
        eval "\$code"
    fi
fi

EOF

##
## lxc-scripts
##

[ -d "/opt/apps/lxc-scripts" ] || {
    echo "Error: required 'lxc-scripts' not installed." >&2
    exit 1
}

apt-get install -y moreutils  ## needed because ``ts`` is used in this script
ln -sf /opt/apps/lxc-scripts/etc/cron.d/lxc-shorewall-repair /etc/cron.d/lxc-shorewall-repair

cat <<EOF > /etc/logrotate.d/lxc-shorewall-repair
/var/log/lxc-shorewall-repair.log {
    weekly
    missingok
    dateext
    dateyesterday
    dateformat _%Y-%m-%d
    extension .log
    rotate 52
    compress
    delaycompress
    notifempty
    create 640 root root
    sharedscripts
}
EOF


##
## LOGS
##

mkdir -p /var/log/shorewall
chgrp syslog /var/log/shorewall
chmod g+w /var/log/shorewall

cat <<EOF > /etc/rsyslog.d/shorewall.conf
:msg, contains, "Shorewall:" /var/log/shorewall/main.log
& ~

if \$msg contains 'net-fw DROP IN=' then {
  action(type="omfile" file="/var/log/shorewall/net-fw.log")
  stop
}


EOF

cat <<EOF > /etc/logrotate.d/shorewall
/var/log/shorewall/init.log
/var/log/shorewall/net-fw.log
/var/log/shorewall/main.log
{
    weekly
    missingok
    dateext
    dateyesterday
    dateformat _%Y-%m-%d
    extension .log
    rotate 52
    compress
    delaycompress
    notifempty
    create 640 root root
    sharedscripts
    postrotate
        reload rsyslog >/dev/null 2>&1 || true
    endscript
}

EOF

## Init logs
sed -ri 's%^(STARTUP_LOG=).*$%\1/var/log/shorewall/init.log%g' /etc/shorewall/shorewall.conf

service rsyslog restart


##
## Final settings
##


## Activate support for docker
sed -ri 's/^DOCKER=No$/DOCKER=Yes/g' /etc/shorewall/shorewall.conf


sed -ri 's/^IP_FORWARDING=Keep$/IP_FORWARDING=On/g' /etc/shorewall/shorewall.conf