Contents

IPv6 Only Docker Host

Building a Docker host in an IPv6 only environment

Updated on 2021-03-12

Setting up Docker on an IPv6 only VPS - e.g. at Mythic Beasts.

In this instance I have a /64 block allocated out of an (apparant) /48 subnet. The host needs to get its (link-local) gateway from RAs, but global IPs are hard-coded. EUI-64 is not used here.

Alpine as a host

Pros: Lightweight, so smaller attack surface

Cons: Busybox limitations, glibc alternative limitations. documentation ain’t great.

https://wiki.alpinelinux.org/wiki/Configure_Networking

/etc/network/interfaces

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet manual
	up ip link set $IFACE up
	down ip link set $IFACE down

iface eth0 inet6 static
	address 2a00:1098:88:xx:0:0:0:1
	netmask 48
	pre-up echo 1 > /proc/sys/net/ipv6/conf/eth0/accept_ra

Additional addresses

1
2
3
4
5
6
iface eth0 inet6 static
        address 2a00:1098:88:xx:0:0:0:22
        netmask 48
iface eth0 inet6 static
        address 2a00:1098:88:xx:0:0:0:70
        netmask 48

/etc/resolv.conf

Mythic Beasts NAT64 servers

1
2
3
search zabaglione.net
nameserver 2a00:1098:0:82:1000:3b:0:1
nameserver 2a00:1098:0:80:1000:3b:0:1

/etc/ssh/sshd_config

  • Only v6, and only on primary IP
  • No password login
  • root ssh allowed, cert only
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
--- sshd_config.orig
+++ sshd_config
@@ -15,6 +15,9 @@
 #ListenAddress 0.0.0.0
 #ListenAddress ::

+AddressFamily inet6
+ListenAddress 2a00:1098:88:xx::1
+
 #HostKey /etc/ssh/ssh_host_rsa_key
 #HostKey /etc/ssh/ssh_host_ecdsa_key
 #HostKey /etc/ssh/ssh_host_ed25519_key
@@ -29,7 +32,7 @@
 # Authentication:

 #LoginGraceTime 2m
-#PermitRootLogin prohibit-password
+PermitRootLogin prohibit-password
 #StrictModes yes
 #MaxAuthTries 6
 #MaxSessions 10
@@ -54,11 +57,11 @@
 #IgnoreRhosts yes

 # To disable tunneled clear text passwords, change to no here!
-#PasswordAuthentication yes
+PasswordAuthentication no
 #PermitEmptyPasswords no

 # Change to no to disable s/key passwords
-#ChallengeResponseAuthentication yes
+ChallengeResponseAuthentication no

 # Kerberos options
 #KerberosAuthentication no

https://wiki.alpinelinux.org/wiki/Docker#Installation

As root

Add Community Repo

1
2
3
4
5
6
7
zabaglione:~# cat /etc/apk/repositories
#/media/cdrom/apks
http://mirror.leaseweb.com/alpine/v3.11/main
http://mirror.leaseweb.com/alpine/v3.11/community
#http://mirror.leaseweb.com/alpine/edge/main
#http://mirror.leaseweb.com/alpine/edge/community
#http://mirror.leaseweb.com/alpine/edge/testing

Install Docker

1
zabaglione:~# apk add docker

Enable and start Docker

1
2
3
4
5
6
7
8
zabaglione:~# rc-update add docker boot
 * service docker added to runlevel boot
zabaglione:~# service docker start
 * Caching service dependencies ...                                                                                                                        [ ok ]
 * Mounting cgroup filesystem ...                                                                                                                          [ ok ]
 * /var/log/docker.log: creating file
 * /var/log/docker.log: correcting owner
 * Starting docker ...                                                                                                                                     [ ok ]

And install docker-compose.

1
zabaglione:~# apk add docker-compose

See also https://docs.docker.com/engine/security/userns-remap/

However, this does have implications including stopping host networks from working.

1
2
3
4
zabaglione:~# adduser -SDHs /sbin/nologin dockremap
zabaglione:~# addgroup -S dockremap
zabaglione:~# echo dockremap:$(cat /etc/passwd|grep dockremap|cut -d: -f3):65536 >> /etc/subuid
zabaglione:~# echo dockremap:$(cat /etc/passwd|grep dockremap|cut -d: -f4):65536 >> /etc/subgid

/etc/docker/daemon.json

1
2
3
{
        "userns-remap": "dockremap"
}

/etc/update-extlinux.conf

1
default_kernel_opts="... cgroup_enable=memory swapaccount=1"

than update the config and reboot

1
update-extlinux

To allow IPv6 connectivity outbound from docker containers, can enable IPv6 global addresses for them -

https://collabnix.com/enabling-ipv6-functionality-for-docker-and-docker-compose/

These can apparently be > /64, e.g. /80 (though preferably no smaller)

Having done that, they need to be announced to the upstream router over NDP. This can be facilitated with ndppd.

This is not available in the Alpine packages, but is available as a docker image -

https://github.com/janeczku/ndppd-alpine

but

1
2
3
zabaglione:~# docker run -d --restart=unless-stopped -e IPV6_SUBNET="2a00:1098:88:xx:0:0:0:1" --cap-drop=ALL --cap-add=NET_ADMIN --cap-add=NET_RAW --net=host janeczku/ndppd-alpine
docker: Error response from daemon: cannot share the host's network namespace when user namespaces are enabled.
See 'docker run --help'.

Removing the namespace stuff allows this to run. Need to work out dependencies - do the containers have to be up when ndppd starts?

It may be better to have ndppd running native on the host, but it doesn’t currently build against the glibc replacement in Alpine.

npd6

There’s also npd6, available for Alpine in the edge/testing repo.

Add to /etc/npd6.conf

1
2
prefix=2a00:1098:0088:00xx:0001::/80
interface = eth0

And enable & start the service.

This seems to work nicely.

Docker Compose

Mapping services to IPv6 addresses can involve a lot of colons! To bind a port (here 22) to a v6 address (here 2a00:1098:88:xx::22), on the same port (22) we end up with configuration that looks like -

1
2
    ports:
      - 2a00:1098:88:xx::22:22:22

Interactive Environment

A test environment that I can ssh into in order to run network utilities etc.

1
2
3
4
5
6
7
8
  build: ./toolkit
  command: ["/usr/sbin/sshd", "-D"]
  network_mode: bridge
  ports:
    - 2a00:1098:88:xx::22:22:22
  volumes:
    - /home:/home
  restart: always
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
FROM alpine

MAINTAINER Matt Balyuzi (https://github.com/mbalyuzi)

RUN apk update && \
    apk add --no-cache zsh mutt nmap iputils openssh rsync tmux apg keychain wget && \
    rm -f /tmp/* /etc/apk/cache/*

RUN sed -i s/#PermitRootLogin.*/PermitRootLogin\ without-password/ /etc/ssh/sshd_config

RUN passwd -d root

COPY identity.pub /root/.ssh/authorized_keys

RUN sed -i -e "s/bin\/ash/bin\/zsh/" /etc/passwd

RUN addgroup matt && \
    adduser -h /home/matt -s /bin/zsh -G matt -D matt && \
    addgroup matt wheel

RUN passwd -d matt

COPY ssh_hostkeys/* /etc/ssh/

ENV SHELL /bin/zsh

EXPOSE 22

CMD ["/usr/sbin/sshd", "-D"]

Odds and ends

https://github.com/moby/moby/issues/36954

Interesting - https://zerotier.atlassian.net/wiki/spaces/SD/pages/7274520/Using+NDP+Emulated+6PLANE+Addressing+With+Docker - use ZeroTier as a v6 docker backplane.