├── example ├── scripts │ ├── wait-for │ └── test.sh └── docker-compose.yml ├── .gitmodules ├── README.md ├── LICENSE ├── Dockerfile └── setup_firewall.sh /example/scripts/wait-for: -------------------------------------------------------------------------------- 1 | wait-for-repo/wait-for -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "example/scripts/wait-for-repo"] 2 | path = example/scripts/wait-for-repo 3 | url = git@github.com:eficode/wait-for.git 4 | -------------------------------------------------------------------------------- /example/scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ping -c 1 -w 1 8.8.8.8 4 | then 5 | echo "FAILED! Was able to ping google from behind the firewall." 6 | exit 1 7 | fi 8 | 9 | if ! nc -z -w 1 52.218.200.155 80 10 | then 11 | echo "FAILED! Was able not able to reach the allowed server." 12 | exit 1 13 | fi 14 | 15 | echo "SUCCESS! The firewall was configured sucessfully." 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | docker-simple-firewall 2 | ====================== 3 | 4 | A container which sets up a simple iptables firewall only allowing outbound 5 | traffic to the docker local network and a single ip, port and protocol 6 | combination. Once the firewall is ready, a TCP socket is opened to signal the 7 | firewall is ready. 8 | 9 | Usage 10 | ----- 11 | 12 | First, start the firwall container. 13 | 14 | docker run \ 15 | --name=firewall \ 16 | --cap-add=NET_ADMIN \ 17 | -e ALLOW_IP_ADDRESS=52.218.200.155 \ 18 | -e ALLOW_PORT=80 \ 19 | -e ALLOW_PROTO=tcp \ 20 | 0xcaff/simple-outbound-firewall 21 | 22 | Then, connect another container to the firewall by sharing the firewall 23 | container's network stack. 24 | 25 | docker run \ 26 | --network="container:firewall" \ 27 | -it \ 28 | alpine:3.7 29 | 30 | Checkout the [`example/`][example] folder to see an example of this with 31 | docker-compose and waiting for the firewall to be ready before using the 32 | network. 33 | 34 | [example]: ./example 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Martin Charles 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.7 2 | 3 | # Install runtime dependencies. The versions are pinned for stable, 4 | # reproducible, deterministic, pure builds. 5 | # 6 | # netcat-openbsd is needed because busybox's nc doesn't support the `-k` flag 7 | # which allows for accepting clients after the first one disconnects. 8 | RUN apk --update add \ 9 | iptables=1.6.1-r1 \ 10 | ip6tables=1.6.1-r1 \ 11 | netcat-openbsd=1.130-r1 12 | 13 | # This is only address, port and protocol traffic will be allowed to be sent to 14 | # (besides the docker internal network). 15 | ENV ALLOW_IP_ADDRESS 178.60.78.125 16 | ENV ALLOW_PORT 1194 17 | 18 | # One of udp or tcp. 19 | ENV ALLOW_PROTO udp 20 | 21 | # This port will be open and accepting TCP connections after the firewall has 22 | # been initialized. Only one client can connect to this port at a time so 23 | # clients SHOULD not hold open connections and only keep the connection open 24 | # long enough to check whether it is active. A script like 25 | # [wait-for](https://github.com/Eficode/wait-for) can be used to consume this 26 | # event. 27 | ENV FIREWALL_READY_SIGNAL_PORT 60000 28 | 29 | # Setting up the firewall, can only be done at container start time. 30 | COPY ./setup_firewall.sh /firewall/ 31 | ENTRYPOINT [ "/firewall/setup_firewall.sh" ] 32 | -------------------------------------------------------------------------------- /example/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | # An example of setting up a firewall service and postponing execution in other 4 | # containers until the firewall is ready. 5 | 6 | services: 7 | # This service initializes the firewall and notifies consumers once it is up 8 | # by listening on a TCP port. 9 | firewall: 10 | image: 0xcaff/simple-firewall:latest 11 | 12 | # This capibility is needed to add firewall rules. 13 | cap_add: 14 | - net_admin 15 | 16 | # Some environment variables to configure the firewall. See the Dockerfile 17 | # for more about these options. 18 | environment: 19 | ALLOW_IP_ADDRESS: 52.218.200.155 20 | ALLOW_PORT: 80 21 | ALLOW_PROTO: tcp 22 | 23 | # This is the port which a tcp socket will be opened on once the firewall 24 | # is ready. Containers which require the firewall to be available before 25 | # doing something should wait until this port is open. See below for an 26 | # example of how to wait. 27 | FIREWALL_READY_SIGNAL_PORT: 60000 28 | 29 | # A service subject to the firewall to test whether the firewall is working. 30 | test: 31 | image: alpine:3.7 32 | volumes: 33 | - ./scripts/:/scripts/ 34 | 35 | # This service shares the network stack of the firewall container to be 36 | # protected by the firewall. This also means the ready signal port is 37 | # available on localhost. 38 | network_mode: 'service:firewall' 39 | 40 | # The [wait-for script][wait-for] waits until a TCP socket is open then runs 41 | # a specified command. 42 | # 43 | # [wait-for]: https://github.com/Eficode/wait-for 44 | command: "/scripts/wait-for localhost:60000 -- ./scripts/test.sh" 45 | -------------------------------------------------------------------------------- /setup_firewall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Sets up a restrictive outbound ipv4 firewall only allowing traffic to one 4 | # destination (address, protocol, port, ip address). It is only a ipv4 firewall 5 | # because docker (17.10.0-ce) only supports ipv6 behind a flag. 6 | 7 | # Exit on first non-zero exit code like a sane language. 8 | set -e 9 | 10 | echo "Initializing Firewall" 11 | 12 | # Clear output table. 13 | iptables --flush OUTPUT 14 | 15 | # Drop unmatched traffic. 16 | iptables --policy OUTPUT DROP 17 | 18 | # Allows traffic corresponding to inbound traffic. 19 | iptables \ 20 | --append OUTPUT \ 21 | --match conntrack \ 22 | --ctstate ESTABLISHED,RELATED \ 23 | --jump ACCEPT 24 | 25 | # Accept traffic to the loopback interface. 26 | iptables \ 27 | --append OUTPUT \ 28 | --out-interface lo \ 29 | --jump ACCEPT 30 | 31 | # Accept traffic to tunnel interfaces. 32 | iptables \ 33 | --append OUTPUT \ 34 | --out-interface tap0 \ 35 | --jump ACCEPT 36 | 37 | iptables \ 38 | --append OUTPUT \ 39 | --out-interface tun0 \ 40 | --jump ACCEPT 41 | 42 | # Accept traffic to the one allowed destination. 43 | iptables \ 44 | --append OUTPUT \ 45 | --destination "${ALLOW_IP_ADDRESS}" \ 46 | --protocol "${ALLOW_PROTO}" \ 47 | --dport "${ALLOW_PORT}" \ 48 | --jump ACCEPT 49 | 50 | # Accept local traffic to docker network. It doesn't seem possible to use the 51 | # --realm flag in this iptables. 52 | 53 | # The local address range routed by the eth0 interface. 54 | docker_network=$(ip -o addr show dev eth0 | awk '$3 == "inet" {print $4}') 55 | 56 | iptables \ 57 | --append OUTPUT \ 58 | --destination ${docker_network} \ 59 | --jump ACCEPT 60 | 61 | echo "Firewall Initialized" 62 | 63 | # Accept connections on port to signal that the firewall is up. `-l` is for 64 | # listen. `-k` is to keep the connection open after the first client disconnects 65 | # and `-p` is to specify the port. Netcat can only handle one connection at a 66 | # time. 67 | nc -l -k -p $FIREWALL_READY_SIGNAL_PORT localhost 68 | --------------------------------------------------------------------------------