manbytesgnu_site

Source files for manbytesgnu.org
git clone git://holbrook.no/manbytesgnu_site.git
Log | Files | Refs

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.