From ca83deb761c845b26bdfee298ffe7c35ad78ed3a Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Wed, 9 Feb 2022 17:47:48 -0800 Subject: [PATCH] Docker IPv6 support Docker's initial IPv6 support is based on allocating public IPv6 to containers. This approach has many issues: * The server may not get a block of IPv6 assigned to it * It's complicated to allocate a block of IPv6 to cloudron server on home setups * It's unclear how dynamic IPv6 is. If it's dynamic, then should containers be recreated? * DNS setup is complicated * Not a issue for Cloudron itself, but with -P, it just exposed the full container into the world Given these issues, IPv6 NAT is being considered. Even though NAT is not a security mechanism as such, it does offer benefits that we care about: * We can allocate some private IPv6 to containers * Have docker NAT66 the exposed ports * Works similar to IPv4 Currently, the IPv6 ports are always mapped and exposed. The "Enable IPv6" config option is only whether to automate AAAA records or not. This way, user can enable it and 'sync' dns and we don't need to re-create containers etc. There is no inherent benefit is not exposing IPv6 at all everywhere unless we find it unstable. Fixes #264 --- CHANGES | 2 +- baseimage/initializeBaseUbuntuImage.sh | 2 +- setup/start.sh | 9 ++++++++- setup/start/cloudron-firewall.sh | 22 ++++++++++++++++++++++ src/docker.js | 2 +- src/infra_version.js | 2 +- src/platform.js | 5 +++-- 7 files changed, 37 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index da1dd3020..82e1090ae 100644 --- a/CHANGES +++ b/CHANGES @@ -2422,4 +2422,4 @@ * Update monaco-editor to 0.32.1 * Update xterm.js to 4.17.0 * Update docker to 20.10.12 - +* IPv6 support diff --git a/baseimage/initializeBaseUbuntuImage.sh b/baseimage/initializeBaseUbuntuImage.sh index 0cdde4957..85bd7d768 100755 --- a/baseimage/initializeBaseUbuntuImage.sh +++ b/baseimage/initializeBaseUbuntuImage.sh @@ -89,7 +89,7 @@ echo "==> Installing Docker" # create systemd drop-in file. if you channge options here, be sure to fixup installer.sh as well mkdir -p /etc/systemd/system/docker.service.d -echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/dockerd -H fd:// --log-driver=journald --exec-opt native.cgroupdriver=cgroupfs --storage-driver=overlay2" > /etc/systemd/system/docker.service.d/cloudron.conf +echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/dockerd -H fd:// --log-driver=journald --exec-opt native.cgroupdriver=cgroupfs --storage-driver=overlay2 --experimental --ip6tables" > /etc/systemd/system/docker.service.d/cloudron.conf # there are 3 packages for docker - containerd, CLI and the daemon readonly docker_version=20.10.12 diff --git a/setup/start.sh b/setup/start.sh index 1befc7947..6d9d7eb16 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -38,6 +38,13 @@ systemctl restart apparmor usermod ${USER} -a -G docker +if ! grep -q ip6tables /etc/systemd/system/docker.service.d/cloudron.conf; then + log "Adding ip6tables flag to docker" # https://github.com/moby/moby/pull/41622 + echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/dockerd -H fd:// --log-driver=journald --exec-opt native.cgroupdriver=cgroupfs --storage-driver=overlay2 --experimental --ip6tables" > /etc/systemd/system/docker.service.d/cloudron.conf + systemctl daemon-reload + systemctl restart docker +fi + mkdir -p "${BOX_DATA_DIR}" mkdir -p "${APPS_DATA_DIR}" mkdir -p "${MAIL_DATA_DIR}" @@ -107,7 +114,7 @@ systemctl enable box systemctl enable cloudron-firewall systemctl enable --now cloudron-disable-thp -# update firewall rules +# update firewall rules. this must be done after docker created it's rules systemctl restart cloudron-firewall # For logrotate diff --git a/setup/start/cloudron-firewall.sh b/setup/start/cloudron-firewall.sh index c84ab5d52..03e65dcf7 100755 --- a/setup/start/cloudron-firewall.sh +++ b/setup/start/cloudron-firewall.sh @@ -3,6 +3,9 @@ set -eu -o pipefail echo "==> Setting up firewall" + +####### IPv4 + iptables -t filter -N CLOUDRON || true iptables -t filter -F CLOUDRON # empty any existing rules @@ -122,4 +125,23 @@ fi iptables -D FORWARD -j CLOUDRON_RATELIMIT || true iptables -I FORWARD 1 -j CLOUDRON_RATELIMIT +####### IPv6 + +# if file exists and has some content (despite being 0 size) +if cat /proc/net/if_inet6 >/dev/null 2>&1; then + echo "==> Setting up IPv6 firewall" + ip6tables -t filter -N CLOUDRON || true + ip6tables -t filter -F CLOUDRON # empty any existing rules + + ip6tables -t filter -A CLOUDRON -m state --state RELATED,ESTABLISHED -j ACCEPT + ip6tables -t filter -A CLOUDRON -p tcp -m tcp -m multiport --dports 22,80,202,443 -j ACCEPT # 202 is the alternate ssh port + + ip6tables -t filter -A CLOUDRON -m limit --limit 2/min -j LOG --log-prefix "IP6Tables Packet Dropped: " --log-level 7 + # ip6tables -t filter -A CLOUDRON -j DROP + + if ! ip6tables -t filter -C INPUT -j CLOUDRON 2>/dev/null; then + ip6tables -t filter -I INPUT -j CLOUDRON + fi +fi + echo "==> Setting up firewall done" diff --git a/src/docker.js b/src/docker.js index 0a857f49d..14f68ebbc 100644 --- a/src/docker.js +++ b/src/docker.js @@ -293,7 +293,7 @@ async function createSubcontainer(app, name, cmd, options) { exposedPorts[`${containerPort}/${portType}`] = {}; portEnv.push(`${portName}=${hostPort}`); - const hostIps = hostPort === 53 ? getAddresses() : [ '0.0.0.0' ]; // port 53 is special because it is possibly taken by systemd-resolved + const hostIps = hostPort === 53 ? getAddresses() : [ '0.0.0.0', '::0' ]; // port 53 is special because it is possibly taken by systemd-resolved dockerPortBindings[`${containerPort}/${portType}`] = hostIps.map(hip => { return { HostIp: hip, HostPort: hostPort + '' }; }); } diff --git a/src/infra_version.js b/src/infra_version.js index 2765a4146..c88630521 100644 --- a/src/infra_version.js +++ b/src/infra_version.js @@ -6,7 +6,7 @@ exports = module.exports = { // a version change recreates all containers with latest docker config - 'version': '48.20.0', + 'version': '49.0.0', 'baseImages': [ { repo: 'cloudron/base', tag: 'cloudron/base:3.2.0@sha256:ba1d566164a67c266782545ea9809dc611c4152e27686fd14060332dd88263ea' } diff --git a/src/platform.js b/src/platform.js index d6840205b..6fb6d1615 100644 --- a/src/platform.js +++ b/src/platform.js @@ -115,8 +115,9 @@ async function pruneInfraImages() { async function createDockerNetwork() { debug('createDockerNetwork: recreating docker network'); - await shell.promises.exec('createDockerNetwork', 'docker network rm cloudron'); - await shell.promises.exec('createDockerNetwork', 'docker network create --subnet=172.18.0.0/16 --ip-range=172.18.0.0/20 --gateway 172.18.0.1 cloudron'); + await shell.promises.exec('createDockerNetwork', 'docker network rm cloudron || true'); + // the --ipv6 option will work even in ipv6 is disabled. fd00 is IPv6 ULA + await shell.promises.exec('createDockerNetwork', 'docker network create --subnet=172.18.0.0/16 --ip-range=172.18.0.0/20 --gateway 172.18.0.1 --ipv6 --subnet=fd00:c107:d509::/64 cloudron'); } async function removeAllContainers() {