# DFW - Docker Firewall Framework in Rust
1. [Overview](#overview)
1. [Example](#overview-example)
2. [Getting started](#gettingstarted)
3. [Configuration](#configuration)
4. [Troubleshooting](#troubleshooting)
5. [IPv6 support](#ipv6support)
1. [Example: webserver reachable via IPv6](#ipv6support-example)
6. [Breaking changes](#breakingchanges)
1. [Coming from v0.x to v1.x](#breakingchanges-v0tov1)
7. [Supported architectures](#supportedarchitectures)
8. [Supported Docker versions](#supporteddockerversions)
9. [Version bump policy](#versionbumppolicy)
10. [License](#license)
1. [Contribution](#license-contribution)
-----
## Overview
DFW is conceptually based on the [Docker Firewall Framework, DFWFW][dfwfw-github].
Its goal is to make firewall administration with Docker simpler, but also more extensive by trying to replace the Docker built-in firewall handling.
This is accomplished by a flexible configuration that defines how the firewall should be built up.
While DFW is running, Docker container events will be monitored and the rules rebuilt when necessary.
One of the key-features of DFW (and DFWFW before it) is to not require the running containers to publish their ports on the host (à la `docker container run --publish 80:8080`), but rather use the network-address translation (NAT) features of the host-firewall to forward packets directly to the port in the container.[1](#fn-1)
DFW supports the following firewall backends:
* iptables
* nftables
You can choose the one that works best for you.[2](#fn-2)
1
This only applies if you use IPv4 on your host.
If you want to have IPv6-support, you still need to publish the ports.
See [IPv6 support](#configuration-ipv6) for more information.
2
Please make sure to not mix firewall-backends: if you are already using one on your host, do not use the other one with DFW.
[dfwfw-github]: https://github.com/irsl/dfwfw
### Example
Assume that you want to run a reverse proxy in Docker that should proxy traffic to a web-application that is also running in Docker.
With the regular tools provided by Docker you would simply host-bind the port, put the two Docker containers on the same Docker network and would have a working solution.
While this works quite well, having Docker handling the firewall rules has a few potential drawbacks:
1. Traffic to host-mounted ports is not restricted by default.
While this usually is the desired behaviour, it might hurt you if you just want to launch a service and test it locally.[3](#fn-3)
With DFW you have to be explicit: if you want your service to be reachable, you have to configure exactly which container should be reachable from where.
While this does incur an upfront cost in terms of effort, it can reward you afterwards by ensuring you don't accidentally expose a service you didn't intend to expose.
2. Traffic between containers on the same Docker network is not restricted.
Again: most of the time this is the desired behaviour.
When it isn't though, Docker does not give you the tools to restrict this traffic.
DFW allows you to configure _exactly_ how you want containers to be able to communicate with each other, both in the same Docker network and across Docker networks.
In the example above we want the reverse proxy to communicate with the web-application, but the web-application should not be able to initiate a connection to the reverse proxy.
DFW allows you to implement this scenario.
If you have not encountered the drawbacks described above and are happy with the features provided by Docker, you might not need DFW.
But if you have, or are simply interested in trying DFW out, take a look at the [reverse proxy example][example-reverseproxy] which will work you through the proposed example.
3
You can of course bind the port to `127.0.0.1`, but you have to be explicit about that, which is easy to forget.
[example-reverseproxy]: https://github.com/pitkley/dfw/tree/main/examples/reverseproxy
## Getting started
If you are starting fresh, the first step is to decide on a firewall backend:
* [nftables][docs-nftables]
nftables can be seen as a newer generation of iptables, and it will replace iptables in most Linux distributions at some point.
(It already is the default in e.g. Debian 10 Buster.)
If you are starting fresh on a host where you have not used either backend yet, nftables is the suggested backend.
* [iptables][docs-iptables]
While iptables is the older netfilter implementation, it is still a valid firewall-backend and still finds extensive use across many distributions.
If you are already using iptables and have a configuration that you don't want to re-do, feel free to use the iptables backend with DFW.
Once you have decided which backend you want to use, please consult the backend-specific documentation on how to proceed further:
* [nftables][docs-nftables]
* [iptables][docs-iptables]
If you are already a user of DFW and are looking to upgrade to a newer version, consult the matching migration documentation:
* [Migrating from DFW 1.x to 1.2][migration-v1.x-to-v1.2]
* [Migrating from DFW 0.x to 1.2][migration-v0.x-to-v1.2]
[migration-v0.x-to-v1.2]: https://github.com/pitkley/dfw/blob/main/docs/migration/v0.x-to-v1.2.md
[migration-v1.x-to-v1.2]: https://github.com/pitkley/dfw/blob/main/docs/migration/v1.x-to-v1.2.md
[docs-nftables]: https://github.com/pitkley/dfw/blob/main/docs/GETTING-STARTED-nftables.md
[docs-iptables]: https://github.com/pitkley/dfw/blob/main/docs/GETTING-STARTED-iptables.md
## Configuration
The general configuration happens across six categories:
* `global_defaults`
This category defines global, default values to be used by DFW and the other categories.
[Field reference.](https://dfw.rs/1.3.0/dfw/types/struct.GlobalDefaults.html)
* `backend_defaults`
This category defines configuration values that are specific to the firewall-backend used.
[Field reference for `nftables`.](https://dfw.rs/1.3.0/dfw/nftables/types/struct.Defaults.html)
[Field reference for `iptables`.](https://dfw.rs/1.3.0/dfw/iptables/types/struct.Defaults.html)
* `container_to_container`
This controls the communication between containers and across [Docker networks][docker-networks].
[Field reference.](https://dfw.rs/1.3.0/dfw/types/struct.ContainerToContainer.html)
* `container_to_wider_world`
This controls if and how containers may access the wider world, i.e. what they can communicate across the `OUTPUT` chain on the host.
[Field reference.](https://dfw.rs/1.3.0/dfw/types/struct.ContainerToWiderWorld.html)
* `container_to_host`
To restrict or allow access to the host, this section is used.
[Field reference.](https://dfw.rs/1.3.0/dfw/types/struct.ContainerToHost.html)
* `wider_world_to_container`
This controls how the wider world, i.e. whatever comes in through the `INPUT` chain on the host, can communicate with a container or a Docker network.
[Field reference.](https://dfw.rs/1.3.0/dfw/types/struct.WiderWorldToContainer.html)
* `container_dnat`
This category allows you to define specific rules for destination network address translation, even or especially across Docker networks.
[Field reference.](https://dfw.rs/1.3.0/dfw/types/struct.ContainerDNAT.html)
**See the [examples][examples] and [configuration types][types.rs] for detailed descriptions and examples of every configuration section.**
Additionally, you can configure general behavior of DFW using command-line arguments, which are described by executing `dfw --help`:
```
dfw
Docker firewall framework, in Rust
USAGE:
dfw [OPTIONS]
OPTIONS:
--burst-timeout
Time to wait after a event was received before processing the rules, in milliseconds
[default: 500]
-c, --config-file
Set the configuration file
--check-config
Verify if the provided configuration is valid, exit afterwards.
--config-path
Set a path with multiple TOML configuration files
--container-filter
Filter the containers to be included during processing
[default: running]
-d, --docker-url
Set the URL to the Docker instance (e.g. unix:///tmp/docker.sock)
--disable-event-monitoring
Disable event monitoring
--dry-run
Don't touch firewall-rules, just show what would be done. Note that this requires Docker
and the containers/networks referenced in the configuration to be available. If you want
to check the config for validity, specify --check-config instead.
--firewall-backend
Select the firewall-backend to use
[default: nftables]
[possible values: nftables, iptables]
-h, --help
Print help information
-i, --load-interval
Interval between rule processing runs, in seconds (0 = disabled)
[default: 0]
--log-level
Define the log level
[default: info]
-m, --load-mode
Define if the config-fields get loaded once, or before every run
[default: once]
[possible values: once, always]
--run-once
Process rules once, then exit.
-V, --version
Print version information
```
[docker-networks]: https://docs.docker.com/engine/userguide/networking/
[examples]: https://github.com/pitkley/dfw/tree/main/examples
[types.rs]: https://dfw.rs/1.3.0/dfw/types/index.html
## Troubleshooting
If you are experiencing issues with DFW, you can consult the [troubleshooting documentation][docs-troubleshooting] for known potential obstacles.
If you don't find your issue covered, feel free to [open a GitHub issue describing your problem](https://github.com/pitkley/dfw/issues/new).
[docs-troubleshooting]: https://github.com/pitkley/dfw/blob/main/docs/TROUBLESHOOTING.md
## IPv6 support
If you make a container publicly available, DFW will use "destination NATting" and "masquerading" to redirect incoming packets to the correct internal IP of the container, and then correctly redirect the reponses back to the original requester.
Every default installation of Docker does _not_ assign private IPv6 addresses to networks and containers, it only assigns private IPv4s.
Generally there is also no need for private IPv6 addresses: Docker uses a proxy-binary when host-binding a container-port to perform the translation of traffic from the host to the container.
This host-binding is compatible with both IPv4 and IPv6, which means internally a single IPv4 is sufficient.
As mentioned, DFW does work differently: since it uses NAT to manage traffic, it effectively would have to translate incoming packets from IPv6 to IPv4 and the responses from IPv4 to IPv6, something that is not supported by nftables and iptables.
The consequence of this is that if you want your services to be reachable via IPv6, you have to ensure the following things:
1. You _have to_ publish the ports of the containers you want to be able to reach on your host through the Docker-integrated run-option `--publish`.
The host-port you select here is the one under which it will be reachable publicly later, i.e. if you want your webserver to be reachable from host-ports 80 and 443, you need to publish the container ports under 80 and 443.
2. In your wider-world-to-container rule, the host-port part of your exposed port _must match_ the port you published the container ports under (although it doesn't have to match the container-port itself).
As part of the wider-world-to-container rule DFW will create the firewall-rules necessary for the host-bound ports to be reachable via IPv6.
For this to work the ports need to match the ports you have selected when publishing the container-ports.
(If you are having trouble, make sure you don't have `expose_via_ipv6` set to `false` in your wider-world-to-container rule.)
### Example: webserver reachable via IPv6
Let's assume you want to run a webserver as a Docker container and want ports 80 for HTTP and 443 for HTTPS on your host to forward to this container.
The container you use internally uses ports 8080 and 8443 for HTTP and HTTPS respectively.
The following is how you have to configure the container:
```
$ docker run \
--name "your_container" \
--network "your_network" \
--publish 80:8080 \
--publish 443:8443 \
...
```
This is how you'd configure your rule:
```toml
[[wider_world_to_container.rules]]
network = "your_network"
dst_container = "your_container"
expose_port = [
"80:8080",
"443:8443",
]
```
The result of this is that your container will be reachable from the host-ports 80 and 443, from both IPv4 and IPv6.
## Breaking changes
### Coming from v0.x to v1.x
Starting with version 1.0, DFW introduced the [nftables] backend and made it the default firewall-backend used.
If you are upgrading DFW but don't want to switch to nftables, you can provide the `--firewall-backend iptables` parameter to DFW (this requires at least DFW v1.2).
Please note that no matter if you transition to nftables or not, **v1.0 introduced breaking changes to the configuration**.
Please consult the [migration documentation][migration-v0.x-to-v1.2] on how to update your configuration.
[nftables]: https://netfilter.org/projects/nftables/
## Supported architectures
The Docker image for DFW is pre-built for the following architectures:
* `amd64` (a.k.a. `x86_64`)
* `arm64` (a.k.a. `aarch64`)
* `arm/v7` (specifically `armhf`)
You don't have to do anything special to use the correct architecture: just `docker pull pitkley/dfw:1.3.0`.
Docker will take care of pulling the image that matches the architecture of your host.
In general, DFW should be able to run on any architecture that [Rust supports][rust-platform-support] and for which the `nftables` or `iptables` binaries exist.
[rust-platform-support]: https://doc.rust-lang.org/stable/rustc/platform-support.html
## Supported Docker versions
At least Docker 1.13.0 is required.
DFW is continuously and automatically tested with the following stable Docker versions (using the latest patch-version each):
* `24.0`
* `23.0`
* `20.10`
* `19.03`
* `18.09`
* `18.06`
Docker versions between 18.03 and 1.13 should also work, but are not automatically tested anymore due to lacking Docker BuildKit support.
## Version bump policy
In general, the versioning scheme for DFW follows the semantic versioning guidelines:
* The patch version is bumped when backwards compatible fixes are made (this includes updates to dependencies).
* The minor version is bumped when new features are introduced, but backwards compatibility is retained.
* The major version is bumped when a backwards incompatible change was made.
Special cases:
* A bump in the minimum supported Rust version (MSRV), which for DFW currently is 1.67.0, will be done in minor version updates (i.e. they do not require a major version bump).
* DFW is available both as a binary for direct use and as a library on [crates.io](https://crates.io/crates/dfw).
The target audience of DFW are the users of the binary, and support for the library's public API is only provided on a best-effort basis.
Thus, changes that break the API of the library will be done in minor version updates, i.e. consumers of the library might have to expect breaking changes in non-major releases.
## License
DFW is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or )
* MIT license ([LICENSE-MIT](LICENSE-MIT) or )
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in DFW by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.