Docker dev polish (#803)

* checkpoint

* Docker dev environment final root cause analysis

* Update CONTRIBUTING.md

* Update apps/fz_common/lib/fz_net.ex

Co-authored-by: Po Chen <chenpaul914@gmail.com>

Co-authored-by: Po Chen <chenpaul914@gmail.com>
This commit is contained in:
Jamil
2022-07-14 17:40:53 -07:00
parent 7a14f178a4
commit 2de119a49e
16 changed files with 203 additions and 143 deletions

View File

@@ -8,7 +8,7 @@ localhost {
tls internal
}
:54321 {
:8443 {
handle /hello {
respond "HELLO
"

6
.devcontainer/README.md Normal file
View File

@@ -0,0 +1,6 @@
# DevContainer
Files in this directory are used exclusively for VS Code / Github Codespaces.
For a general overview of how to run Firezone in Docker locally, see [our
contributing guide](../CONTRIBUTING.md).

View File

@@ -0,0 +1,14 @@
# This config corresponds to the wireguard-client device
# created when the DB is bootstrapped from apps/fz_http/priv/repo/seeds.exs.
[Interface]
Address = 10.3.2.6/32,fd00::3:2:6/128
DNS = 127.0.0.11
PrivateKey = UJ3WN7k8mnRTj33BAoiA1lw0ag24oB7NsTg01MaeNUI=
MTU = 1280
[Peer]
PublicKey = is+0ov0/SZ9I+qyDD+adVoH9LreWHa85QQgpt6RUtA4=
PresharedKey = C+Tte1echarIObr6rq+nFeYQ1QO5xo5N29ygDjMlpS8=
PersistentKeepalive = 25
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = elixir:51820

View File

@@ -3,26 +3,30 @@
Thanks for considering contributing to Firezone! Please read this guide to get
started.
# Table of Contents
## Table of Contents
* [Overview](#overview)
* [Developer Environment Setup](#developer-environment-setup)
* [Prerequisites](#prerequisites)
* [asdf-vm](#asdf-vm)
* [Postgresql](#postgresql)
* [Pre-commit](#pre-commit)
* [Docker Setup](#docker-setup)
* [Docker Caveat](#docker-caveat)
* [asdf-vm](#asdf-vm)
* [Pre-commit](#pre-commit)
* [The .env File](#the-env-file)
* [Bootstrapping](#bootstrapping)
* [Ensure Everything Works](#ensure-everything-works)
* [Running this inside a Devcontainer](#running-this-inside-a-devcontainer)
* [Note: Devcontainer on Windows](#note--devcontainer-on-windows)
* [Reporting Bugs](#reporting-bugs)
* [Opening a Pull Request](#opening-a-pull-request)
* [Running Tests](#running-tests)
* [Run Tests](#run-tests)
* [Unit Tests](#unit-tests)
* [End-to-end Tests](#end-to-end-tests)
* [Use Detailed Commit Messages](#use-detailed-commit-messages)
* [Ensure Static Analysis Checks Pass](#ensure-static-analysis-checks-pass)
* [Code of Conduct](#code-of-conduct)
* [Asking for Help](#asking-for-help)
# Overview
## Overview
We deeply appreciate any and all contributions to the project and do our best to
ensure your contribution is included.
@@ -31,38 +35,62 @@ To maximize your chances of getting your pull request approved, please abide by
the following general guidelines:
1. Please adhere to our [code of conduct](CODE_OF_CONDUCT.md).
2. Please test with your code and include unit tests when possible.
3. It is up to you, the contributor, to make a case for why your change is a
1. Please test with your code and include unit tests when possible.
1. It is up to you, the contributor, to make a case for why your change is a
good idea.
4. For any security issues, please **do not** open a Github Issue. Please
1. For any security issues, please **do not** open a Github Issue. Please
follow responsible disclosure practices laid out in
[SECURITY.md](SECURITY.md)
# Developer Environment Setup
## Developer Environment Setup
We recommended macOS or Linux for developing for Firezone. You can (probably)
use Windows too with something like Windows subsystem for Linux, but we haven't
tried.
Docker is the preferred method of development Firezone locally. It (mostly)
works cross-platform, and can be used to develop Firezone on all three
major desktop OS. This also provides a small but somewhat realistic network
environment with working nftables and WireGuard subsystems for live development.
## Prerequisites
### Docker Setup
### asdf-vm
While not required, we use [asdf-vm](https://asdf-vm.com) to manage language
versions for Firezone. You'll need to install the language runtimes according
to the versions laid out in the [.tool-versions](.tool-versions) file.
We recommend [Docker Desktop](
https://docs.docker.com/engine/install/#desktop)
even if you're developing on Linux. This is what the Firezone core devs use and
comes with `compose` included.
#### Docker Caveat
Routing packets from the host's WireGuard client through the Firezone compose
cluster and out to the external network will not work. This is because Docker
Desktop
[rewrites the source address from containers to appear as if they originated the
host](
https://www.docker.com/blog/how-docker-desktop-networking-works-under-the-hood/)
, causing a routing loop:
1. Packet originates on Host
1. Enters WireGuard client tunnel
1. Forwarding through the Docker bridge net
1. Forward to the Firezone container, 127.0.0.1:51820
1. Firezone sends packet back out
1. Docker bridge net, Docker rewrites src IP to Host's LAN IP, (d'oh!)
1. Docker sends packet out to Host ->
1. Packet now has same src IP and dest IP as step 1 above, and the cycle
continues
However, packets destined for Firezone compose cluster IPs (172.28.0.0/16)
reach their destination through the tunnel just fine. Because of this, it's
recommended to use `172.28.0.0/16` for your `AllowedIPs` parameter when using
host-based WireGuard clients with Firezone running under Docker Desktop.
### asdf-vm Setup
While not strictly required, we use [asdf-vm](https://asdf-vm.com) to manage
language versions for Firezone. You'll need to install the language runtimes
according to the versions laid out in the [.tool-versions](.tool-versions) file.
If using asdf, simply run `asdf install` from the project root.
### Postgresql
Firezone development requires access to a Postgresql instance. Versions 9.6 or
higher should work fine. Access can be configured using the [
.env](#the-env-file) described below.
**Note:** To use the default configuration without specifying the database URL in the `.env` file, you need to configure the `postgres` user with the password `postgres` to do so use the following command:
```sql
ALTER USER postgres WITH PASSWORD 'postgres';
```
This is used to run static analysis checks during [pre-commit](#pre-commit) and
for any local, non-Docker development or testing.
### Pre-commit
@@ -72,54 +100,28 @@ pip: `pip install pre-commit`.
### The ENV file
In order to save local environment variables in your development environment,
you can use a `.env` file in the project root directory to store any commonly
used settings.
For running tests and developing Firezone outside of Docker, you'll need some
environment variables present in your shell's env.
.env.sample will give you an example of what this file may look like.
See .env.sample an example of what variables you need. We recommend copying this
file to `.env` and using a dotenv loader to apply this to your current shell
env.
Run the following command to 'source' the environment variables from .env on
`mix start`
For example, run the following command to 'source' the environment variables
from .env on `mix test`:
`env $(cat .env | grep -v \# | xargs) mix start`
## Bootstrapping
Assuming you've completed the steps above, you should be able to get everything
set up like this:
```bash
git clone https://github.com/firezone/firezone
cd firezone
asdf install
mix local.hex --force
mix local.rebar --force
mix deps.get
MIX_ENV=test mix do ecto.remigrate
mix test
```
`env $(cat .env | grep -v \# | xargs) mix test`
This will initialize everything and run the test suite. If you have no
failures, Firezone should be properly set up 🥳.
Then, to initialize assets, create seed data, and start the dev server:
To create seed data and start the development server:
```bash
cd apps/fz_http
mix ecto.reset
npm install --prefix assets
cd ../..
mix start
```
At this point you should be able to sign in to
[http://localhost:4000](http://localhost:4000) with email `firezone@localhost` and
password `firezone1234`.
## Run using Docker
### Bootstrapping
To run using docker follow these steps:
To start the local development cluster, follow these steps:
```
docker compose build
@@ -131,33 +133,24 @@ docker compose up
Now you should be able to connect to `https://localhost/`
and sign in with email `firezone@localhost` and password `firezone1234`.
### Testing wireguard connections and NAT using wireguard-client container
### Ensure Everything Works
There is a `wireguard-client` container in the docker-compose configuration that helps testing
wireguard connections, it's connected to a separate network from the `caddy` container but the
firezone server is connected to both network so you can verify the connections using:
There is a `client` container in the docker-compose configuration that
can be used to simulate a WireGuard client connecting to Firezone. It's already
provisioned in the Firezone development cluster and has a corresponding
WireGuard configuration located at .devcontainer/wg0.client.conf.
It's attached to the `isolation` Docker network which is isolated from the other
Firezone Docker services. By connecting to Firezone from the `client`
container, you can test the WireGuard tunnel is set up correctly by pinging the
`caddy` container:
* `docker compose exec wireguard-client ping 172.28.0.99`
* `docker compose exec wireguard-client curl -k 172.28.0.99:54321/hello` this should return `HELLO` text.
* `docker compose exec client ping 172.28.0.99`
* `docker compose exec client curl -k 172.28.0.99:8443/hello`: this
should return `HELLO` text.
To setup this test before doing `docker compose up` do this:
* Create a device in firezone using the default configuration except for:
* `DNS`: `127.0.0.11` (Docker internal DNS)
* `Endpoint`: `elixir:51820` (Need to edit after download)
* Download the generated configuration to `./tmp/config/wg0.conf`
* `docker compose up`
If the above commands indicate success, you should be good to go!
### Testing wireguard connections and NAT in Linux from the host
To test wireguard connections you can create an interface through the firezone website and add it
using [`wg-quick`](https://man7.org/linux/man-pages/man8/wg-quick.8.html) but after
`wg-quick up <interface_name>` you need to run `./scripts/post-up-wg.sh` now all traffic originating
from the host should be going through your wireguard interface into the docker container
(except for traffic outgoing from the docker bridge network).
After `wg-quick down <interface_name>` run `./scripts/post-down-wg.sh` to clean everything up.
## Running this inside a Devcontainer
### Running this inside a Devcontainer
You can run this using Github Codespaces or your own devcontainer using Docker.
@@ -175,12 +168,13 @@ command in the terminal:
`echo "https://${CODESPACE_NAME}-4000.githubpreview.dev"`
### Note: Devcontainer on Windows
#### Note: Devcontainer on Windows
If you are on Windows, make sure your git config `core.autocrlf` is off. Otherwise,
the `\r` characters confuse asdf, which in turn fails the devcontainer build.
# Reporting Bugs
## Reporting Bugs
We appreciate any and all bug reports.
To report a bug, please first [search for it in our issues
@@ -198,29 +192,35 @@ If it's not there, please open a new issue and include the following:
* Linux distribution
* Linux kernel version
# Opening a Pull Request
## Opening a Pull Request
We love pull requests! To ensure your pull request gets reviewed and merged
swiftly, please read the below *before* opening a pull request.
## Run Tests
### Run Tests
Please test your code. As a contributor, it is **your** responsibility to ensure
your code is bug-free, otherwise it may be rejected. It's also a good idea to
check the code coverage report to ensure your tests are covering your new
code. E.g.
### Unit Tests
#### Unit Tests
Unit tests can be run with `mix test` from the project root.
To view line coverage information, you may run `mix coveralls.html`
which will generate an HTML coverage report in `cover/`.
### End-to-end Tests
#### End-to-end Tests
More comprehensive e2e testing is performed in the CI pipeline, but for security
reasons these will not be triggered automatically by your pull request and must
be manually triggered by a reviewer.
## Use Detailed Commit Messages
### Use Detailed Commit Messages
This will help tremendously during our release engineering process. E.g.
```bash
read -r -d '' COMMIT_MSG << EOM
Updating the foobar widget to support additional widths
@@ -232,12 +232,15 @@ EOM
git commit -m "$COMMIT_MSG"
```
## Ensure Static Analysis Checks Pass
### Ensure Static Analysis Checks Pass
This should run automatically when you run `git commit`, but in case it doesn't:
```bash
pre-commit run --all-files
```
# Asking For Help
If you get stuck, don't hesitate to ask for help on our mailing list at
https://discourse.firez.one.
## Asking For Help
If you get stuck, don't hesitate to ask for help on our [mailing list](
https://discourse.firez.one).

View File

@@ -10,6 +10,8 @@ defmodule FzCommon.FzNet do
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
@cidr6_regex ~r/^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))$/
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
@host_regex ~r/\A(([a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)\.)*([a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)$\Z/
@fqdn_regex ~r/\A(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)\z/
# XXX: Consider using InetCidr for this
@@ -59,4 +61,8 @@ defmodule FzCommon.FzNet do
def valid_fqdn?(fqdn) when is_binary(fqdn) do
String.match?(fqdn, @fqdn_regex)
end
def valid_hostname?(hostname) when is_binary(hostname) do
String.match?(hostname, @host_regex)
end
end

View File

@@ -39,7 +39,21 @@ defmodule FzCommon.FzNetTest do
end
end
describe "valid_fqdn/1" do
describe "valid_host?/1" do
test "foobar is valid" do
assert FzNet.valid_hostname?("foobar")
end
test "-foobar is invalid" do
refute FzNet.valid_hostname?("-foobar")
end
test "1234 is valid" do
assert FzNet.valid_hostname?("1234")
end
end
describe "valid_fqdn?/1" do
test "foobar is invalid" do
refute FzNet.valid_fqdn?("foobar")
end

View File

@@ -9,6 +9,7 @@ defmodule FzHttp.SharedValidators do
only: [
valid_ip?: 1,
valid_fqdn?: 1,
valid_hostname?: 1,
valid_cidr?: 1
]
@@ -29,7 +30,7 @@ defmodule FzHttp.SharedValidators do
validate_change(changeset, field, fn _current_field, value ->
value
|> split_comma_list()
|> Enum.find(&(not (valid_ip?(&1) or valid_fqdn?(&1))))
|> Enum.find(&(not (valid_ip?(&1) or valid_fqdn?(&1) or valid_hostname?(&1))))
|> error_if(
&(!is_nil(&1)),
&{field, "is invalid: #{&1} is not a valid FQDN or IPv4 / IPv6 address"}

View File

@@ -19,7 +19,30 @@ alias FzHttp.{Devices, ConnectivityChecks, Users}
password_confirmation: "firezone1234"
})
{:ok, device} =
{:ok, _device} =
Devices.create_device(%{
user_id: user.id,
name: "wireguard-client",
description: """
Test device corresponding to the client configuration used in the wireguard-client container
""",
preshared_key: "C+Tte1echarIObr6rq+nFeYQ1QO5xo5N29ygDjMlpS8=",
public_key: "pSLWbPiQ2mKh26IG1dMFQQWuAstFJXV91dNk+olzEjA=",
ipv4: "10.3.2.6",
ipv6: "fd00::3:2:6",
mtu: 1280,
persistent_keepalive: 25,
allowed_ips: "0.0.0.0,::/0",
endpoint: "elixir:51820",
dns: "127.0.0.11",
use_site_allowed_ips: false,
use_site_dns: false,
use_site_endpoint: false,
use_site_mtu: false,
use_site_persistent_keepalive: false
})
{:ok, _device} =
Devices.create_device(%{
user_id: user.id,
name: "Factory Device 3",
@@ -31,19 +54,7 @@ alias FzHttp.{Devices, ConnectivityChecks, Users}
tx_bytes: 1_934_475_211_087_234
})
{:ok, device} =
Devices.create_device(%{
user_id: user.id,
name: "Factory Device 6",
description: "foo 4",
preshared_key: "24eCDMVRVFfMVS5Rfnn9n7as4t6MemGY/oghmdrwX2E=",
public_key: "33o+SBnDJ6hi8q4Pt3nWLwgjCVwvpjHL35qJeatKwEc=",
remote_ip: %Postgrex.INET{address: {127, 2, 0, 1}},
rx_bytes: 123_917_823,
tx_bytes: 1_934_475_211_087_234
})
{:ok, device} =
{:ok, _device} =
Devices.create_device(%{
user_id: user.id,
name: "Factory Device 5",
@@ -55,7 +66,7 @@ alias FzHttp.{Devices, ConnectivityChecks, Users}
tx_bytes: 1_934_475_211_087_234
})
{:ok, device} =
{:ok, _device} =
Devices.create_device(%{
user_id: user.id,
name: "Factory Device 4",
@@ -74,7 +85,7 @@ alias FzHttp.{Devices, ConnectivityChecks, Users}
password_confirmation: "firezone1234"
})
{:ok, device} =
{:ok, _device} =
Devices.create_device(%{
user_id: user.id,
name: "Factory Device 2",
@@ -86,7 +97,7 @@ alias FzHttp.{Devices, ConnectivityChecks, Users}
tx_bytes: 1_934_475_211_087_234
})
{:ok, device} =
{:ok, _device} =
Devices.create_device(%{
user_id: user.id,
name: "Factory Device",

View File

@@ -177,7 +177,7 @@ defmodule FzHttp.DevicesTest do
@invalid_endpoint_ipv4_attrs %{
use_site_endpoint: false,
endpoint: "265.1.1.1"
endpoint: "-265.1.1.1"
}
@invalid_endpoint_ipv6_attrs %{
@@ -236,7 +236,7 @@ defmodule FzHttp.DevicesTest do
{:error, changeset} = Devices.update_device(device, @invalid_endpoint_ipv4_attrs)
assert changeset.errors[:endpoint] == {
"is invalid: 265.1.1.1 is not a valid FQDN or IPv4 / IPv6 address",
"is invalid: -265.1.1.1 is not a valid FQDN or IPv4 / IPv6 address",
[]
}
end

View File

@@ -27,7 +27,7 @@ defmodule FzHttpWeb.SettingLive.SiteTest do
"site" => %{"dns" => "foobar"}
}
@invalid_endpoint %{
"site" => %{"endpoint" => "foobar"}
"site" => %{"endpoint" => "-foobar"}
}
@invalid_persistent_keepalive %{
"site" => %{"persistent_keepalive" => "-1"}
@@ -162,7 +162,7 @@ defmodule FzHttpWeb.SettingLive.SiteTest do
assert test_view =~ "is invalid"
assert test_view =~ """
<input class="input is-danger" id="site_form_component_endpoint" name="site[endpoint]" placeholder="127.0.0.1" type="text" value="foobar"/>\
<input class="input is-danger" id="site_form_component_endpoint" name="site[endpoint]" placeholder="127.0.0.1" type="text" value="-foobar"/>\
"""
end

View File

@@ -101,8 +101,8 @@ config :hammer,
# This will be changed per-env
config :fz_vpn,
wireguard_private_key_path: "dummy",
stats_push_service_enabled: true,
wireguard_private_key_path: "priv/.wg_dummy_private_key",
wireguard_interface_name: "wg-firezone",
wireguard_port: 51_820,
wireguard_endpoint: "127.0.0.1",

View File

@@ -49,6 +49,7 @@ config :fz_wall,
cli: fz_wall_cli_module
config :fz_vpn,
wireguard_private_key_path: "priv/wg_dev_private_key",
wg_adapter: fz_vpn_wgadapter_module
# Auth
@@ -122,6 +123,7 @@ config :phoenix, :stacktrace_depth, 20
config :phoenix, :plug_init_mode, :runtime
config :fz_http,
wireguard_allowed_ips: "172.28.0.0/16",
cookie_secure: false,
telemetry_module: FzCommon.MockTelemetry,
local_auth_enabled: local_auth_enabled

View File

@@ -64,23 +64,31 @@ services:
networks:
- app
wireguard-log:
image: ubuntu:jammy
volumes:
- /sys/kernel/debug:/sys/kernel/debug
# cap SYSLOG was enough for reading but privilege is required for tailing
privileged: true
command: bash -c 'dmesg -wT | grep wireguard:'
# Unfortunately the Linux VM kernel for Docker Desktop is not compiled with
# Dynamic Debug enabled, so we're unable to enable WireGuard debug logging.
# Since WireGuard is designed to be silent by default, this basically does
# nothing.
# wireguard-log:
# image: ubuntu:jammy
# # cap SYSLOG was enough for reading but privilege is required for tailing
# privileged: true
# command: >
# bash -c '
# mount -t debugfs none /sys/kernel/debug
# && echo module wireguard +p > /sys/kernel/debug/dynamic_debug/control
# && dmesg -wT | grep wireguard:'
wireguard-client:
client:
depends_on:
- elixir
image: linuxserver/wireguard:latest
environment:
- PUID=1000
- PGID=1000
- TZ=UTC
- ALLOWEDIPS=0.0.0.0/0
- ALLOWEDIPS="0.0.0.0/0,::/0"
volumes:
- ./tmp/config:/config
- ./.devcontainer/wg0.client.conf:/config/wg0.conf
cap_add:
- NET_ADMIN
- SYS_MODULE

View File

@@ -1,5 +0,0 @@
-- Execute this with your DB admin user
create role firezone;
alter role firezone with password 'postgres';
alter role firezone with login;
alter role firezone with superuser;

View File

@@ -1,8 +1,8 @@
#!/bin/bash
ip link add dev wg-firezone type wireguard
ip address add dev wg-firezone 10.3.2.1/24
ip -6 address add dev wg-firezone fd00::3:2:1/120
ip link set up dev wg-firezone
ip address replace dev wg-firezone 10.3.2.1/24
ip -6 address replace dev wg-firezone fd00::3:2:1/120
ip link set mtu 1280 up dev wg-firezone
mix start