20210420_docker_offline.rst (6533B)
1 The routing to freedom 2 ###################### 3 4 :date: 2021-04-26 07:54 5 :category: Offlining 6 :author: Louis Holbrook 7 :tags: docker,networking,iptables,iproute 8 :slug: docker-offline-1-routing 9 :summary: How to not be forced being online when forced to use docker 10 :series: Offline Docker 11 :seriesprefix: docker-offline 12 :seriespart: 1 13 :lang: en 14 :status: published 15 16 17 .. 18 .. CAUTION:: 19 20 This is a purely technical article. You should probably be `this geeky <https://g33k.holbrook.no/8319a926>`_ to continue reading. 21 22 Five years ago I decided that I wanted to be able to work from anywhere, anytime. Four years ago, that promise was kept. In part. 23 24 I do not need to go to an office somewhere. I can work outside in a park if I want to. I can ride on to a new town every day. I only ever need to bring my trusty old `Tuxedo Laptop`_ whereever I go. 25 26 All of this as true, as long as there is internet available. And, as it turns out, *good* internet available. 27 28 This has become especially obvious to me once I started to work with a project that involves a collection of microservices contained in a Docker environment, which also makes extensive use of custom packages that change frequently alongside the development process. Turns out, every time I want to rebuild my cluster of containers when sitting in the sun in a park, I need my LTE modem to play along. If it doesn't, then a single package that can't be reached will thwart the build. 29 30 This does not feel much like freedom after all. So let's see how we can serve all of these locally from our host instead. 31 32 First of all, we have to be able to reach our local host from the Docker containers. This is less straightforward than it may seem at first. The most obvious solution is to use the ``host`` network driver, but this exposes your *whole* localhost interface and routes to internet, too. Aside from the security issues that raises, it can also also trick you into assuming that some resources are available when they in fact will not be when you move on to a different environment. What we want is to *block* access to internet, while *choosing* which services to let the Docker container use. 33 34 Once we have this in place, we want to create local repositories for all the stuff we otherwise need to download. In this particular case, that means a **Docker** repository, a **Python** repository, a **nodejs** repository and a **linux** repository. We'll use Archlinux_ for this exercise, because that's been my home environment for the last four years. 35 36 In fact, having your own mirror of all these and anything else you base most of your work on is not only a good idea for the purpose of *offlining* in itself, but wasting on bandwidth for items you've already downloaded hundreds of times is not exactly a nod to climate awareness either. And even more importantly, ensuring *availability* of software is something we should all participate in, and not merely defer to a couple of git repository giants. 37 38 .. _Tuxedo Laptop: https://www.tuxedocomputers.com/en 39 40 .. 41 42 .. _Archlinux: https://archlinux.org 43 44 Reaching the local host 45 ======================= 46 47 *Local host* not *localhost*, mind you. Which means we need a different interface to connect to. And since we are not wiring up in any physical sense, a virtual interface seems to be the reasonable way to go. 48 49 First, let's prepare a base Docker layer with some tools that you should never leave home without. 50 51 52 Prepare the docker image 53 ------------------------ 54 55 .. include:: code/docker-offline/Dockerfile.archbase 56 :code: docker 57 58 Let's build this as an image called ``archbase``. Provided the content above is a file called ``Dockerfile.archbase`` in your current directory: 59 60 .. code-block:: bash 61 62 $ docker build -t archbase -f Dockerfile.archbase . 63 64 65 Set up network interfaces 66 ------------------------- 67 68 Bring up a virtual interface 69 70 .. code-block:: bash 71 72 $ ip link add foo type dummy 73 74 Find the subnet of the ``no-internet`` Docker network. This network driver is a builtin that provides exactly what it advertises. 75 76 .. code-block:: bash 77 78 $ docker network inspect no-internet 79 [...] 80 "Config": [ 81 { 82 "Subnet": "10.1.1.0/24", 83 "Gateway": "10.1.1.1" 84 } 85 ] 86 87 Assign an IP address to the dummy interface in a *different* subnet than the one the Docker network uses. 88 89 .. code-block:: bash 90 91 $ ip addr add 10.1.2.1/24 dev foo 92 93 94 Traverse the firewall 95 --------------------- 96 97 Find the bridge used by the Docker container. Look for an ip address that matches the gateway of the docker network config. 98 99 .. code-block:: bash 100 101 $ ip addr ls 102 17: br-d4ddb68f9938: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 103 link/ether 02:42:9c:1a:58:d2 brd ff:ff:ff:ff:ff:ff 104 inet 10.1.1.1/24 brd 10.1.1.255 scope global br-d4ddb68f9938 105 valid_lft forever preferred_lft forever 106 inet6 fe80::42:9cff:fe1a:58d2/64 scope link 107 valid_lft forever preferred_lft forever 108 [...] 109 7614: foo: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-496e528b928c state UNKNOWN group default qlen 1000 110 link/ether 1a:50:53:2b:96:98 brd ff:ff:ff:ff:ff:ff 111 inet 10.1.3.1/24 scope global foo 112 valid_lft forever preferred_lft forever 113 inet6 fe80::1850:53ff:fe2b:9698/64 scope link 114 valid_lft forever preferred_lft forever 115 116 Add the virtual interface to the bridge. 117 118 .. code-block:: bash 119 120 $ ip link set foo master br-d4ddb68f9938 121 122 Long story short, the previous step will make the traffic from the container reach the ``INPUT`` chain in ``iptables``. Now we can make an exception for incoming traffic from the ``no-internet`` Docker bridge. 123 124 .. code-block:: bash 125 126 $ iptables -I INPUT 1 --source br-d4ddb68f9938 --destination 10.1.2.1/24 -j ACCEPT 127 128 129 Verify 130 ------ 131 132 Provided you don't have any other hurdles in your local ``ìptables`` setup, a port on device ``foo`` should be reachable from the docker container. We can use socat to check. 133 134 On local host: 135 136 .. code-block:: bash 137 138 $ socat TCP4-LISTEN:8000,bind=10.1.2.1,reuseaddr - 139 140 Start the docker container with shell prompt 141 142 .. code-block:: bash 143 144 $ docker run --network no-internet -it archbase /bin/bash 145 146 The moment of truth 147 148 .. code-block:: bash 149 150 $ echo bar | socat - TCP4:10.1.2.1:8000 151 152 Spoiler: ``bar`` should pop up on the local host side.