Build/729/different reverse proxy (#795)

* Removes disabling SSL and adds remote_ip lib

* Fix to use remote_ip when not proxy

* Add trusted_proxy to config

* Add proxy_forwarded prameter to config and improve logging

* Fix trusted_proxy list parsing

* Fix ip formatting

* Expect JSON array for trusted_proxy

* fix proxied-related plug loading runtime

* fix typo

* checkpoint

* add traefik draft

* add logging

* woops

* adding logging for debug

* rollback debugging logs

* docs refinement

* Fix markdown lints

* remove disabling proxy_forwarded option for prod

* rename, improve docs and add clients config

* change dev_secure to secure_cookies

* Set proxy_forwarded as true by default

* remove proxy_forwarded in favor of nil trusted_proxies

* renaming and doc improvement

* build fix

* jamilbk changes

Co-authored-by: Jamil Bou Kheir <jamilbk@users.noreply.github.com>
This commit is contained in:
Gabi
2022-07-24 21:36:51 -03:00
committed by Jamil
parent ab70facee5
commit 23db81fae5
26 changed files with 546 additions and 122 deletions

View File

@@ -8,10 +8,6 @@ EXTERNAL_URL=http://localhost:4000
# Enable local authentication
LOCAL_AUTH_ENABLED=true
# Set PROXY_FORWARDED to true if you're running this behind a proxy or using
# GitHub codespaces
PROXY_FORWARDED=true
# Generated with `jq @json < .oidc_env.json`
# Set AUTH_OIDC to a JSON configuration string to enable
# generic OIDC auth.

View File

@@ -154,10 +154,10 @@ If the above commands indicate success, you should be good to go!
You can run this using Github Codespaces or your own devcontainer using Docker.
On GitHub Codespaces, follow the instructions above but start the server with
PROXY_FORWARDED enabled and pass in your Codespace external url:
On GitHub Codespaces, follow the instructions above but pass in your Codespace
external url:
`PROXY_FORWARDED=true EXTERNAL_URL=[your_devcontainer_url] MIX_ENV=dev mix start`
`EXTERNAL_URL=[your_devcontainer_url] MIX_ENV=dev mix start`
or using the `.env` file

View File

@@ -65,4 +65,20 @@ defmodule FzCommon.FzNet do
def valid_hostname?(hostname) when is_binary(hostname) do
String.match?(hostname, @host_regex)
end
# IPv4
def convert_ip({_, _, _, _} = address) when is_tuple(address) do
address
|> Tuple.to_list()
|> Enum.join(".")
end
# IPv6
def convert_ip(address) when is_tuple(address) do
address
|> Tuple.to_list()
|> Enum.join(":")
end
def convert_ip(address) when is_binary(address), do: address
end

View File

@@ -3,12 +3,15 @@ defmodule FzHttpWeb.NotificationChannel do
Handles dispatching realtime notifications to users' browser sessions.
"""
use FzHttpWeb, :channel
import FzCommon.FzNet, only: [convert_ip: 1]
alias FzHttp.Users
alias FzHttpWeb.Presence
@token_verify_opts [max_age: 86_400]
@impl Phoenix.Channel
def join("notification:session", %{"user_agent" => user_agent, "token" => token}, socket) do
case Phoenix.Token.verify(socket, "channel auth", token, max_age: 86_400) do
case Phoenix.Token.verify(socket, "channel auth", token, @token_verify_opts) do
{:ok, user_id} ->
socket =
socket
@@ -40,7 +43,7 @@ defmodule FzHttpWeb.NotificationChannel do
online_at: DateTime.utc_now(),
last_signed_in_at: user.last_signed_in_at,
last_signed_in_method: user.last_signed_in_method,
remote_ip: socket.assigns.remote_ip,
remote_ip: convert_ip(socket.assigns.remote_ip),
user_agent: socket.assigns.user_agent
}

View File

@@ -2,12 +2,19 @@ defmodule FzHttpWeb.UserSocket do
use Phoenix.Socket
alias FzHttp.Users
alias FzHttpWeb.HeaderHelpers
import FzCommon.FzNet, only: [convert_ip: 1]
@blank_ip_error {:error, "client IP couldn't be determined!"}
# 4 hour channel tokens
@token_verify_opts [max_age: 86_400]
require Logger
## Channels
# channel "room:*", FzHttpWeb.RoomChannel
channel "notification:session", FzHttpWeb.NotificationChannel
channel("notification:session", FzHttpWeb.NotificationChannel)
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
@@ -21,17 +28,25 @@ defmodule FzHttpWeb.UserSocket do
# See `Phoenix.Token` documentation for examples in
# performing token verification on connect.
def connect(%{"token" => token}, socket, connect_info) do
ip = get_ip_address(connect_info)
case get_ip_address(connect_info) do
ip when ip in ["", nil] ->
@blank_ip_error
case Phoenix.Token.verify(socket, "user auth", token, max_age: 86_400) do
ip ->
verify_token_and_assign_remote_ip(socket, token, convert_ip(ip))
end
end
defp verify_token_and_assign_remote_ip(socket, token, ip) do
case Phoenix.Token.verify(socket, "user auth", token, @token_verify_opts) do
{:ok, user_id} ->
{:ok,
socket
|> assign(:current_user, Users.get_user!(user_id))
|> assign(:remote_ip, ip)}
{:error, _} ->
:error
{:error, reason} ->
{:error, reason}
end
end
@@ -48,36 +63,15 @@ defmodule FzHttpWeb.UserSocket do
# def id(_socket), do: nil
def id(socket), do: "user_socket:#{socket.assigns.current_user.id}"
defp get_ip_address(%{x_headers: headers_list}) when length(headers_list) > 0 do
header =
Enum.find(headers_list, fn {key, _val} -> key == "x-real-ip" end) ||
Enum.find(headers_list, fn {key, _val} -> key == "x-forwarded-for" end)
case header do
{_key, value} -> value
_ -> nil
end
defp get_ip_address(%{x_headers: x_headers}) do
RemoteIp.from(x_headers,
proxies: HeaderHelpers.external_trusted_proxies(),
clients: HeaderHelpers.clients()
)
end
# No proxy
defp get_ip_address(%{peer_data: %{address: address}}) do
convert_ip(address)
address
|> Tuple.to_list()
|> Enum.join(".")
end
# IPv4
defp convert_ip({_, _, _, _} = address) do
address
|> Tuple.to_list()
|> Enum.join(".")
end
# IPv6
defp convert_ip(address) do
address
|> Tuple.to_list()
|> Enum.join(":")
end
end

View File

@@ -1,11 +1,9 @@
defmodule FzHttpWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :fz_http
alias FzHttpWeb.ProxyHeaders
alias FzHttpWeb.HeaderHelpers
alias FzHttpWeb.Session
if Application.get_env(:fz_http, FzHttpWeb.Endpoint, :proxy_forwarded) do
plug Plug.RewriteOn, [:x_forwarded_host, :x_forwarded_port, :x_forwarded_proto]
end
if Application.get_env(:fz_http, :sql_sandbox) do
plug Phoenix.Ecto.SQL.Sandbox
end
@@ -21,11 +19,15 @@ defmodule FzHttpWeb.Endpoint do
socket "/live", Phoenix.LiveView.Socket,
websocket: [
connect_info: [
:peer_data,
:x_headers,
:uri,
session: {Session, :options, []}
],
# XXX: csrf token should prevent CSWH but double check
check_origin: false
]
],
longpoll: false
# Serve at "/" the static files from "priv/static" directory.
#
@@ -65,6 +67,11 @@ defmodule FzHttpWeb.Endpoint do
plug Plug.MethodOverride
plug Plug.Head
plug(:session)
if HeaderHelpers.proxied?() do
plug ProxyHeaders
end
plug FzHttpWeb.Router
defp session(conn, _opts) do

View File

@@ -0,0 +1,15 @@
defmodule FzHttpWeb.HeaderHelpers do
@moduledoc """
Helper functionalities with regards to headers
"""
def external_trusted_proxies, do: conf(:external_trusted_proxies)
def clients, do: conf(:private_clients)
def proxied?, do: not (external_trusted_proxies() == false)
defp conf(key) when is_atom(key) do
Application.fetch_env!(:fz_http, key)
end
end

View File

@@ -0,0 +1,26 @@
defmodule FzHttpWeb.ProxyHeaders do
@moduledoc """
Loads proxy-related headers when it corresponds using runtime config
"""
import FzHttpWeb.HeaderHelpers
@behaviour Plug
require Logger
def init(opts), do: opts
def call(conn, _opts) do
conn
|> RemoteIp.call(remote_ip_opts())
|> Plug.RewriteOn.call(rewrite_opts())
end
defp remote_ip_opts do
RemoteIp.init(
proxies: external_trusted_proxies(),
clients: clients()
)
end
defp rewrite_opts, do: Plug.RewriteOn.init([:x_forwarded_proto])
end

View File

@@ -91,7 +91,8 @@ defmodule FzHttp.MixProject do
{:cidr, github: "firezone/cidr-elixir"},
{:telemetry, "~> 1.0"},
{:plug_cowboy, "~> 2.5"},
{:credo, "~> 1.5", only: [:dev, :test], runtime: false}
{:credo, "~> 1.5", only: [:dev, :test], runtime: false},
{:remote_ip, "~> 1.0"}
]
end

View File

@@ -53,6 +53,8 @@ config :fz_http, FzHttpWeb.Authentication,
secret_key: "GApJ4c4a/KJLrBePgTDUk0n67AbjCvI9qdypKZEaJFXl6s9H3uRcIhTt49Fij5UO"
config :fz_http,
external_trusted_proxies: [],
private_clients: [],
disable_vpn_on_oidc_error: true,
auto_create_oidc_users: true,
sandbox: true,
@@ -112,13 +114,12 @@ config :fz_vpn,
config :fz_http, FzHttpWeb.Endpoint,
render_errors: [view: FzHttpWeb.ErrorView, accepts: ~w(html json)],
pubsub_server: FzHttp.PubSub,
proxy_forwarded: false
pubsub_server: FzHttp.PubSub
# Configures Elixir's Logger
config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]
metadata: [:request_id, :remote_ip]
# Configures the vault
config :fz_http, FzHttp.Vault,

View File

@@ -119,6 +119,7 @@ config :phoenix, :stacktrace_depth, 20
config :phoenix, :plug_init_mode, :runtime
config :fz_http,
private_clients: ["172.28.0.0/16"],
wireguard_allowed_ips: "172.28.0.0/16",
cookie_secure: false,
telemetry_module: FzCommon.MockTelemetry,

View File

@@ -11,15 +11,11 @@ alias FzCommon.{CLI, FzInteger, FzString}
external_url = System.get_env("EXTERNAL_URL", "https://localhost")
config :fz_http, :external_url, external_url
# Enable Forwarded headers, e.g 'X-FORWARDED-HOST'
proxy_forwarded = FzString.to_boolean(System.get_env("PROXY_FORWARDED") || "false")
%{host: host, path: path, port: port, scheme: scheme} = URI.parse(external_url)
config :fz_http, FzHttpWeb.Endpoint,
url: [host: host, scheme: scheme, port: port, path: path],
check_origin: ["//127.0.0.1", "//localhost", "//#{host}"],
proxy_forwarded: proxy_forwarded
check_origin: ["//127.0.0.1", "//localhost", "//#{host}"]
# Formerly releases.exs - Only evaluated in production
if config_env() == :prod do
@@ -34,6 +30,9 @@ if config_env() == :prod do
database_parameters = Jason.decode!(System.fetch_env!("DATABASE_PARAMETERS"))
phoenix_listen_address = System.fetch_env!("PHOENIX_LISTEN_ADDRESS")
phoenix_port = String.to_integer(System.fetch_env!("PHOENIX_PORT"))
external_trusted_proxies = Jason.decode!(System.fetch_env!("EXTERNAL_TRUSTED_PROXIES"))
private_clients = Jason.decode!(System.fetch_env!("PRIVATE_CLIENTS"))
admin_email = System.fetch_env!("ADMIN_EMAIL")
default_admin_password = System.fetch_env!("DEFAULT_ADMIN_PASSWORD")
wireguard_private_key_path = System.fetch_env!("WIREGUARD_PRIVATE_KEY_PATH")
@@ -59,6 +58,7 @@ if config_env() == :prod do
guardian_secret_key = System.fetch_env!("GUARDIAN_SECRET_KEY")
disable_vpn_on_oidc_error = FzString.to_boolean(System.fetch_env!("DISABLE_VPN_ON_OIDC_ERROR"))
auto_create_oidc_users = FzString.to_boolean(System.fetch_env!("AUTO_CREATE_OIDC_USERS"))
secure = FzString.to_boolean(System.get_env("SECURE_COOKIES", "true"))
allow_unprivileged_device_management =
FzString.to_boolean(System.fetch_env!("ALLOW_UNPRIVILEGED_DEVICE_MANAGEMENT"))
@@ -104,6 +104,7 @@ if config_env() == :prod do
live_view_signing_salt = System.fetch_env!("LIVE_VIEW_SIGNING_SALT")
cookie_signing_salt = System.fetch_env!("COOKIE_SIGNING_SALT")
cookie_encryption_salt = System.fetch_env!("COOKIE_ENCRYPTION_SALT")
cookie_secure = secure
# Password is not needed if using bundled PostgreSQL, so use nil if it's not set.
database_password = System.get_env("DATABASE_PASSWORD")
@@ -198,10 +199,13 @@ if config_env() == :prod do
secret_key: guardian_secret_key
config :fz_http,
external_trusted_proxies: external_trusted_proxies,
private_clients: private_clients,
disable_vpn_on_oidc_error: disable_vpn_on_oidc_error,
auto_create_oidc_users: auto_create_oidc_users,
cookie_signing_salt: cookie_signing_salt,
cookie_encryption_salt: cookie_encryption_salt,
cookie_secure: cookie_secure,
allow_unprivileged_device_management: allow_unprivileged_device_management,
max_devices_per_user: max_devices_per_user,
local_auth_enabled: local_auth_enabled,

View File

@@ -0,0 +1,108 @@
---
title: Custom Reverse Proxy
sidebar_position: 4
---
:::note
This is an advanced configuration. The default bundled nginx proxy
is suitable for the vast majority of use cases and is recommended for most
users. There are important security risks if the Firezone reverse proxy is
not set up correctly. Use only if you know what you are doing.
:::
## Introduction
Firezone comes with a bundled [Nginx](https://www.nginx.com/) reverse-proxy,
however, in some cases you might want to deploy your own server such as when
using behind your own load-balancer.
## Requisites
Below you will find the requirements in order to setup firezone and the
reverse-proxies.
### Firezone configuration requirements
* Disable the bundled Nginx by setting `default['firezone']['nginx']['enabled']`
to `false` in the config file.
* If you have any immediate proxies between your primary reverse proxy and the
Firezone web app, add their IPs to
`default['firezone']['phoenix']['external_trusted_proxies']`. Because of the
way the [X-Forwarded-For header works](
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For),
this is needed to parse the actual client's IP address to prevent
spoofing.
:::note
The `external_trusted_proxies` list automatically implicitly includes the
following private CIDR ranges, even if they're not specified in the
configuration file:
* `127.0.0.0/8`
* `10.0.0.0/8`
* `172.16.0.0/12`
* `1926.0.0/16`
* `::1/128`
* `fc00::/7`
This means any web requests originating from these IPs are automatically ignored
from the `X-Forwarded-For` headers. If you're accessing Firezone from any IPs in
this range (as seen by the Firezone web app), be sure to add them to the
`default['firezone']['phoenix']['clients']` configuration option.
:::
Read more about the configuration options
[here](../../../reference/configuration-file.md).
### Proxy requirements
* All your proxies need to configure the `X-Forwarded-For` header as explained
[here](
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)
* Your proxy should also set the `X-Forwarded-Proto` to `https`.
* Your proxy **must** terminate SSL since we enforce [secure cookies](
https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies).
* Firezone requires the use of WebSockets to establish realtime connections. We
recommended following your proxy's specific documentation for supporting
WebSockets as each proxy varies. In general, your proxy needs to be able to
proxy HTTP 1.1 connections, and the Firezone web app expects the following
headers to be set:
* `Connection: upgrade`
* `Upgrade: websocket`
## Security considerations
In addition to the headers above, we recommend adding the following headers for
security purposes:
* `X-XSS-Protection: 1; mode=block`
* `X-Content-Type-Options nosniff`
* `Referrer-Policy no-referrer-when-downgrade`
* `Content-Security-Policy: default-src 'self' ws: wss: http: https: data: blob:
'unsafe-inline'; frame-ancestors 'self';`
* `Permissions-Policy: interest-cohort=()`
Since the upstream Firezone web app expects plain HTTP traffic, any requests the
proxy forwards is sent over HTTP and thus is **not encrypted**. In most cases,
the reverse proxy is installed in a trusted network, and this is not an issue.
But the connection between your trusted proxy and the Firezone web app spans
an untrusted network (such as the Internet), you may want to leave the bundled
`nginx` proxy enabled for SSL termination, and set up your custom
reverse proxy to proxy to that instead.
## Example configurations
* [Apache](../reverse-proxies/apache.md)
* [Traefik](../reverse-proxies/traefik.md)
* [HAProxy](../reverse-proxies/haproxy.md)
These configurations are written to be as simple as possible. They're designed
to function as a simple template which you can customize further to suit your
needs.
If you have a working configuration for a different reverse-proxy or a different
version of an existing one we appreciate any
[contribution](https://github.com/firezone/firezone/) to expand the examples for
the community.

View File

@@ -0,0 +1,65 @@
---
title: Apache
sidebar_position: 1
---
The following are example [apache](https://httpd.apache.org/) configurations
with and without SSL termination.
These expect the apache to be running on the same host as Firezone and
`default['firezone']['phoenix']['port']` to be `13000`.
### Without SSL termination
Take into account that a previous proxy will need to terminate SSL connections.
`<server-name>` needs to be replaced with your domain name.
This configuration needs to be placed in
`/etc/sites-available/<server-name>.conf`
and activated with `a2ensite <server-name>`
```conf
LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so
LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module /usr/lib/apache2/modules/mod_proxy_wstunnel.so
<VirtualHost *:80>
ServerName <server-name>
ProxyPassReverse "/" "http://127.0.0.1:13000/"
ProxyPass "/" "http://127.0.0.1:13000/"
RewriteEngine on
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://127.0.0.1:13000/$1" [P,L]
</VirtualHost>
```
### With SSL termination
This configuration should be used exactly like the previous and uses Firezone's
generated self-signed certs to terminate SSL.
```conf
LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so
LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module /usr/lib/apache2/modules/mod_proxy_wstunnel.so
LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so
LoadModule headers_module /usr/lib/apache2/modules/mod_headers.so
Listen 443
<VirtualHost *:443>
ServerName <server-name>
RequestHeader set X-Forwarded-Proto "https"
ProxyPassReverse "/" "http://127.0.0.1:13000/"
ProxyPass "/" "http://127.0.0.1:13000/"
RewriteEngine on
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://127.0.0.1:13000/$1" [P,L]
SSLEngine On
SSLCertificateFile "/var/opt/firezone/ssl/ca/acme-test.firez.one.crt"
SSLCertificateKeyFile "/var/opt/firezone/ssl/ca/acme-test.firez.one.key"
</VirtualHost>
```

View File

@@ -0,0 +1,32 @@
---
title: HAProxy
sidebar_position: 3
---
The following is an example configuration for the
[HAProxy](https://www.haproxy.org/) proxy. we assume
`default['firezone']['phoenix']['port']` to be `13000` and the proxy running on
the same host as the Firezone app.
There is not SSL termination in this configuration so a previous proxy will
need to terminate the SSL connection.
You can also configure HAProxy to handle the SSL termination as explained
[here](https://www.haproxy.com/blog/haproxy-ssl-termination/) but take into
account that the `pem` file expected by `ssl crt` option needs to contain
both the `crt` and `key` file.
`/etc/haproxy/haproxy.cfg`:
```conf
defaults
mode http
frontend app1
bind *:80
option forwardfor
default_backend backend_app1
backend backend_app1
server mybackendserver 127.0.0.1:13000
```

View File

@@ -0,0 +1,134 @@
---
title: Traefik
sidebar_position: 2
---
The following are examples for configuring the [Traefik](https://traefik.io/)
proxy.
As of right now Firezone can't be run as a container in production, although
this is a [planned feature](https://github.com/firezone/firezone/issues/260).
So, these example configurations expects Firezone to be deployed on the same
host as the proxy.
In these configurations we assume `default['firezone']['phoenix']['port']` to be
`13000`. Furthermore, for these configuration to work we need the Firezone app
to listen in the Docker interface so you should set:
* `default['firezone']['phoenix']['listen_address'] = '172.17.0.1'`
* `default['firezone']['external_trusted_proxies'] = ['172.18.0.2']`
In the [configuration file](../../reference/configuration-file.md).
## Without SSL termination
Take into account that a previous proxy will need to terminate SSL connections.
Set the following files
### `docker-compose.yml`
```conf
ubuntu@ip-172-31-79-208:~/traefik$ cat docker-compose.yml
version: '3'
services:
reverse-proxy:
#network_mode: "host"
# The official v2 Traefik docker image
image: traefik:v2.8
# Enables the web UI and tells Traefik to listen to docker
command:
- "--providers.docker"
- "--providers.file.filename=rules.yml"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.forwardedHeaders.insecure"
- "--log.level=DEBUG"
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
# The HTTP port
- "80:80"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
- "./rules.yml:/rules.yml"
```
### `rules.yml`
```conf
ubuntu@ip-172-31-79-208:~/traefik$ cat rules.yml
http:
routers:
test:
entryPoints:
- "web"
service: test
rule: "Host(`44.200.42.78`)"
services:
test:
loadBalancer:
servers:
- url: "http://host.docker.internal:13000"
```
And then you can start the Traefik proxy with `docker compose up`
## With SSL termination
This configuration use the auto-generated Firezone self-signed certs as the
default certificates for SSL.
### SSL `docker-compose.yml`
```conf
version: '3'
services:
reverse-proxy:
#network_mode: "host"
# The official v2 Traefik docker image
image: traefik:v2.8
# Enables the web UI and tells Traefik to listen to docker
command:
- "--providers.docker"
- "--providers.file.filename=rules.yml"
- "--entrypoints.web.address=:443"
- "--entrypoints.web.forwardedHeaders.insecure"
- "--log.level=DEBUG"
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
# The HTTP port
- "443:443"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
- "./rules.yml:/rules.yml"
- /var/opt/firezone/ssl/ca:/ssl:ro
```
### SSL `rules.yml`
```conf
http:
routers:
test:
entryPoints:
- "web"
service: test
rule: "Host(`44.200.42.78`)"
tls: {}
services:
test:
loadBalancer:
servers:
- url: "http://host.docker.internal:13000"
tls:
stores:
default:
defaultCertificate:
certFile: /ssl/ip-172-31-79-208.ec2.internal.crt
keyFile: /ssl/ip-172-31-79-208.ec2.internal.key
```

View File

@@ -120,6 +120,8 @@ Shown below is a complete listing of the configuration options available in
| `default['firezone']['phoenix']['log_rotation']['file_maxbytes']` | Firezone web application log file size. | `104857600` |
| `default['firezone']['phoenix']['log_rotation']['num_to_keep']` | Number of Firezone web application log files to keep. | `10` |
| `default['firezone']['phoenix']['crash_detection']['enabled']` | Enable or disable bringing down the Firezone web application when a crash is detected. | `true` |
| `default['firezone']['phoenix']['external_trusted_proxies']` | List of trusted reverse proxies formatted as an Array of IPs and/or CIDRs. | `[]` |
| `default['firezone']['phoenix']['clients']` | List of private network HTTP clients, formatted an Array of IPs and/or CIDRs. | `[]` |
| `default['firezone']['wireguard']['enabled']` | Enable or disable bundled WireGuard management. | `true` |
| `default['firezone']['wireguard']['log_directory']` | Log directory for bundled WireGuard management. | `"#{node['firezone']['log_directory']}/wireguard"` |
| `default['firezone']['wireguard']['log_rotation']['file_maxbytes']` | WireGuard log file max size. | `104857600` |
@@ -141,7 +143,6 @@ Shown below is a complete listing of the configuration options available in
| `default['firezone']['wireguard']['ipv6']['address']` | WireGuard interface IPv6 address. Must be within IPv6 address pool. | `'fd00::3:2:1'` |
| `default['firezone']['runit']['svlogd_bin']` | Runit svlogd bin location. | `"#{node['firezone']['install_directory']}/embedded/bin/svlogd"` |
| `default['firezone']['ssl']['directory']` | SSL directory for storing generated certs. | `'/var/opt/firezone/ssl'` |
| `default['firezone']['ssl']['enabled']` | Enable or disable SSL for nginx. | `true` |
| `default['firezone']['ssl']['email_address']` | Email address to use for self-signed certs and ACME protocol renewal notices. | `'you@example.com'` |
| `default['firezone']['ssl']['acme']['enabled']` | Enable ACME for automatic SSL cert provisioning. See [here](https://docs.firezone.dev/docs/deploy/prerequisites/#create-an-ssl-certificate) for more instructions. | `false` |
| `default['firezone']['ssl']['acme']['server']` | ACME server to use for certificate issuance/renewal. Can be any [valid acme.sh server](https://github.com/acmesh-official/acme.sh/wiki/Server) | `letsencrypt` |

1
dummy
View File

@@ -1 +0,0 @@
KORuM8G3PMPkIJVB1VMFsqAlLnprS/FgqIpFZ+C4M0E=

View File

@@ -6,6 +6,7 @@
"cidr": {:git, "https://github.com/firezone/cidr-elixir.git", "9072aaab069bca38ef55fd901a37448861596532", []},
"cloak": {:hex, :cloak, "1.1.2", "7e0006c2b0b98d976d4f559080fabefd81f0e0a50a3c4b621f85ceeb563e80bb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "940d5ac4fcd51b252930fd112e319ea5ae6ab540b722f3ca60a85666759b9585"},
"cloak_ecto": {:hex, :cloak_ecto, "1.2.0", "e86a3df3bf0dc8980f70406bcb0af2858bac247d55494d40bc58a152590bd402", [:mix], [{:cloak, "~> 1.1.1", [hex: :cloak, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "8bcc677185c813fe64b786618bd6689b1707b35cd95acaae0834557b15a0c62f"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
@@ -69,6 +70,7 @@
"postgrex": {:hex, :postgrex, "0.16.3", "fac79a81a9a234b11c44235a4494d8565303fa4b9147acf57e48978a074971db", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "aeaae1d2d1322da4e5fe90d241b0a564ce03a3add09d7270fb85362166194590"},
"posthog": {:hex, :posthog, "0.1.0", "0abe2af719c0c30fe6a24569a8947a19f0edfa63f6fed61685a219a8d5655786", [:mix], [{:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ee0426999bd35edf3dfa84141bbd3de17ae07e04d62d269fd5ee581925f1c222"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"remote_ip": {:hex, :remote_ip, "1.0.0", "3d7fb45204a5704443f480cee9515e464997f52c35e0a60b6ece1f81484067ae", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9e9fcad4e50c43b5234bb6a9629ed6ab223f3ed07147bd35470e4ee5c8caf907"},
"rustler_precompiled": {:hex, :rustler_precompiled, "0.5.1", "93df423bd7b14b67dcacf994443d132d300623f80756974cac4febeab40af74a", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "3f8cbc8e92eef4e1a71bf441b568b868b16a3730f63f5b803c68073017e30b13"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"swoosh": {:hex, :swoosh, "1.7.3", "febb47c8c3ce76747eb9e3ea25ed694c815f72069127e3bb039b7724082ec670", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76abac313f95b6825baa8ceec269d597e8395950c928742fc6451d3456ca256d"},

View File

@@ -135,6 +135,30 @@ default['firezone']['authentication']['oidc'] = {}
# }
# }
# ## Custom Reverse Proxy
#
# An array of IPs that Firezone will trust as reverse proxies.
#
# Read more here:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#selecting_an_ip_address
#
# By default the following IPs are included:
# * IPv4: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
# * IPv6: ::1/128, fc00::/7
#
# If any client requests will actually be coming from these private IPs, add them to
# default['firezone']['phoenix']['private_clients'] below instead of here.
#
# If set to false Firezone will assume that it is not running behind a proxy
default['firezone']['external_trusted_proxies'] = []
# An array of IPs that Firezone will assume are clients, and thus, not a trusted
# proxy for the purpose of determining the client's IP. By default the bundled
# See more here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#selecting_an_ip_address
# This will supersede any proxy configured manually or by default by
# default['firezone']['external_trusted_proxies']
default['firezone']['phoenix']['private_clients'] = []
# ## Nginx
# These attributes control Firezone-specific portions of the Nginx
@@ -386,9 +410,6 @@ default['firezone']['runit']['svlogd_bin'] = "#{node['firezone']['install_direct
default['firezone']['ssl']['directory'] = '/var/opt/firezone/ssl'
# Enable / disable SSL
default['firezone']['ssl']['enabled'] = true
# Email to use for self signed certs and ACME cert issuance and renewal notices.
# Defaults to default['firezone']['admin_email'] if nil.
default['firezone']['ssl']['email_address'] = nil
@@ -399,7 +420,6 @@ default['firezone']['ssl']['email_address'] = nil
# 2. Port 80/tcp is accessible; this is used for domain validation.
# 3. default['firezone']['ssl']['email_address'] is set properly. This will be used for renewal notices.
# 4. default['firezone']['nginx']['non_ssl_port'] is set to 80
# 5. default['firezone']['ssl']['enabled'] is set to true
default['firezone']['ssl']['acme']['enabled'] = false
# Set the ACME server directory for ACME protocol SSL certificate issuance
@@ -521,3 +541,9 @@ default['firezone']['connectivity_checks']['enabled'] = true
# Amount of time to sleep between connectivity checks, in seconds.
# Default: 3600 (1 hour). Minimum: 60 (1 minute). Maximum: 86400 (1 day).
default['firezone']['connectivity_checks']['interval'] = 3_600
# ## Cookies settings
# Enable or disable the secure attributes for Firezone cookies. It's highly
# recommended you leave this enabled unless you know what you're doing.
default['firezone']['phoenix']['secure_cookies'] = true

View File

@@ -225,6 +225,8 @@ class Firezone
'DATABASE_PARAMETERS' => attributes['database']['parameters'].to_json,
'PHOENIX_LISTEN_ADDRESS' => attributes['phoenix']['listen_address'].to_s,
'PHOENIX_PORT' => attributes['phoenix']['port'].to_s,
'EXTERNAL_TRUSTED_PROXIES' => Chef::JSONCompat.to_json(attributes['phoenix']['external_trusted_proxies']),
'PRIVATE_CLIENTS' => Chef::JSONCompat.to_json(attributes['phoenix']['private_clients']),
'EXTERNAL_URL' => attributes['external_url'] || fqdn_url,
'ADMIN_EMAIL' => attributes['admin_email'],
'WIREGUARD_INTERFACE_NAME' => attributes['wireguard']['interface_name'],
@@ -244,6 +246,7 @@ class Firezone
'WIREGUARD_IPV6_ADDRESS' => attributes['wireguard']['ipv6']['address'],
'MAX_DEVICES_PER_USER' => attributes['max_devices_per_user'].to_s,
'ALLOW_UNPRIVILEGED_DEVICE_MANAGEMENT' => attributes['allow_unprivileged_device_management'].to_s,
# Allow env var to override config
'TELEMETRY_ENABLED' => ENV.fetch('TELEMETRY_ENABLED',
attributes['telemetry']['enabled'] == false ? 'false' : 'true'),
@@ -275,7 +278,10 @@ class Firezone
'LIVE_VIEW_SIGNING_SALT' => attributes['live_view_signing_salt'],
'COOKIE_SIGNING_SALT' => attributes['cookie_signing_salt'],
'COOKIE_ENCRYPTION_SALT' => attributes['cookie_encryption_salt'],
'DATABASE_ENCRYPTION_KEY' => attributes['database_encryption_key']
'DATABASE_ENCRYPTION_KEY' => attributes['database_encryption_key'],
# cookies
'SECURE_COOKIES' => attributes['phoenix']['secure_cookies'].to_s
}
env.merge!('DATABASE_PASSWORD' => attributes['database']['password']) if attributes.dig('database', 'password')

View File

@@ -25,8 +25,7 @@ end
# Enable ACME if set to enabled and user-specified certs are disabled, maintains
# backwards compatibility during upgrades.
if node['firezone']['ssl']['acme']['enabled'] && !node['firezone']['ssl']['certificate'] &&
node['firezone']['ssl']['enabled']
if node['firezone']['ssl']['acme']['enabled'] && !node['firezone']['ssl']['certificate']
keylength = node['firezone']['ssl']['acme']['keylength']
server = node['firezone']['ssl']['acme']['server']

View File

@@ -50,8 +50,7 @@ template 'nginx.conf' do
mode '0600'
variables(
logging_enabled: node['firezone']['logging']['enabled'],
nginx: node['firezone']['nginx'],
ssl_enabled: node['firezone']['ssl']['enabled']
nginx: node['firezone']['nginx']
)
end

View File

@@ -30,61 +30,59 @@ include_recipe 'firezone::config'
end
end
# Unless SSL is disabled, sets up SSL certificates.
# Sets up SSL certificates.
# Creates a self-signed cert if none is provided.
if node['firezone']['ssl']['enabled']
firezone_ca_dir = File.join(node['firezone']['ssl']['directory'], 'ca')
ssl_dhparam = File.join(firezone_ca_dir, 'dhparams.pem')
firezone_ca_dir = File.join(node['firezone']['ssl']['directory'], 'ca')
ssl_dhparam = File.join(firezone_ca_dir, 'dhparams.pem')
# Generate dhparams.pem for perfect forward secrecy
openssl_dhparam ssl_dhparam do
# Generate dhparams.pem for perfect forward secrecy
openssl_dhparam ssl_dhparam do
key_length 2048
generator 2
owner 'root'
group 'root'
mode '0644'
end
node.default['firezone']['ssl']['ssl_dhparam'] ||= ssl_dhparam
if node['firezone']['ssl']['certificate']
# A certificate has been supplied
# Link the standard CA cert into our certs directory
link "#{node['firezone']['ssl']['directory']}/cacert.pem" do
to "#{node['firezone']['install_directory']}/embedded/ssl/certs/cacert.pem"
end
elsif node['firezone']['ssl']['acme']['enabled']
# No certificate provided but acme enabled don't
# auto-generate and ensure acme directory is setup
directory "#{node['firezone']['var_directory']}/ssl/acme" do
owner 'root'
group 'root'
mode '0600'
end
# No certificate has been supplied; generate one
else
host = URI.parse(node['firezone']['external_url']).host
ssl_keyfile = File.join(firezone_ca_dir, "#{host}.key")
ssl_crtfile = File.join(firezone_ca_dir, "#{host}.crt")
openssl_x509_certificate ssl_crtfile do
common_name host
org node['firezone']['ssl']['company_name']
org_unit node['firezone']['ssl']['organizational_unit_name']
country node['firezone']['ssl']['country_name']
key_length 2048
generator 2
expire 3650
owner 'root'
group 'root'
mode '0644'
end
node.default['firezone']['ssl']['ssl_dhparam'] ||= ssl_dhparam
node.default['firezone']['ssl']['certificate'] ||= ssl_crtfile
node.default['firezone']['ssl']['certificate_key'] ||= ssl_keyfile
if node['firezone']['ssl']['certificate']
# A certificate has been supplied
# Link the standard CA cert into our certs directory
link "#{node['firezone']['ssl']['directory']}/cacert.pem" do
to "#{node['firezone']['install_directory']}/embedded/ssl/certs/cacert.pem"
end
elsif node['firezone']['ssl']['acme']['enabled']
# No certificate provided but acme enabled don't
# auto-generate and ensure acme directory is setup
directory "#{node['firezone']['var_directory']}/ssl/acme" do
owner 'root'
group 'root'
mode '0600'
end
# No certificate has been supplied; generate one
else
host = URI.parse(node['firezone']['external_url']).host
ssl_keyfile = File.join(firezone_ca_dir, "#{host}.key")
ssl_crtfile = File.join(firezone_ca_dir, "#{host}.crt")
openssl_x509_certificate ssl_crtfile do
common_name host
org node['firezone']['ssl']['company_name']
org_unit node['firezone']['ssl']['organizational_unit_name']
country node['firezone']['ssl']['country_name']
key_length 2048
expire 3650
owner 'root'
group 'root'
mode '0644'
end
node.default['firezone']['ssl']['certificate'] ||= ssl_crtfile
node.default['firezone']['ssl']['certificate_key'] ||= ssl_keyfile
link "#{node['firezone']['ssl']['directory']}/cacert.pem" do
to ssl_crtfile
end
link "#{node['firezone']['ssl']['directory']}/cacert.pem" do
to ssl_crtfile
end
end

View File

@@ -94,7 +94,5 @@ http {
include <%= @nginx['dir'] %>/conf.d/*.conf;
include <%= @nginx['dir'] %>/sites-enabled/*;
<% if @ssl_enabled -%>
include <%= @nginx['dir'] %>/redirect.conf;
<% end -%>
}

View File

@@ -14,7 +14,6 @@ log_format cache '$remote_addr - [$time_local] "$request" $upstream_cache_status
<% end %>
server {
<% if @ssl['enabled'] -%>
listen <%= @nginx['ssl_port'] %> default_server ssl;
<% if @nginx['ipv6'] -%>
listen [::]:<%= @nginx['ssl_port'] %> default_server ssl;
@@ -35,12 +34,6 @@ server {
ssl_protocols <%= @ssl['protocols'] %>;
ssl_session_cache <%= @ssl['session_cache'] %>;
ssl_session_timeout <%= @ssl['session_timeout'] %>;
<% else -%>
listen <%= @nginx['non_ssl_port'] %> default_server;
<% if @nginx['ipv6'] -%>
listen [::]:<%= @nginx['non_ssl_port'] %> default_server;
<% end -%>
<% end -%>
<% if @nginx['redirect_to_canonical'] -%>
set $redirect_to_canonical 0;