diff --git a/.env.sample b/.env.sample index 4e9dbf09c..e04019d90 100644 --- a/.env.sample +++ b/.env.sample @@ -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. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0209e6c2e..f2479c0f8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/apps/fz_common/lib/fz_net.ex b/apps/fz_common/lib/fz_net.ex index 45839887b..51a174fca 100644 --- a/apps/fz_common/lib/fz_net.ex +++ b/apps/fz_common/lib/fz_net.ex @@ -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 diff --git a/apps/fz_http/lib/fz_http_web/channels/notification_channel.ex b/apps/fz_http/lib/fz_http_web/channels/notification_channel.ex index d45076258..19cdb74ac 100644 --- a/apps/fz_http/lib/fz_http_web/channels/notification_channel.ex +++ b/apps/fz_http/lib/fz_http_web/channels/notification_channel.ex @@ -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 } diff --git a/apps/fz_http/lib/fz_http_web/channels/user_socket.ex b/apps/fz_http/lib/fz_http_web/channels/user_socket.ex index 5c14379c3..0aa1f3078 100644 --- a/apps/fz_http/lib/fz_http_web/channels/user_socket.ex +++ b/apps/fz_http/lib/fz_http_web/channels/user_socket.ex @@ -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 diff --git a/apps/fz_http/lib/fz_http_web/endpoint.ex b/apps/fz_http/lib/fz_http_web/endpoint.ex index 9b30a8b93..bce262301 100644 --- a/apps/fz_http/lib/fz_http_web/endpoint.ex +++ b/apps/fz_http/lib/fz_http_web/endpoint.ex @@ -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 diff --git a/apps/fz_http/lib/fz_http_web/header_helpers.ex b/apps/fz_http/lib/fz_http_web/header_helpers.ex new file mode 100644 index 000000000..c52459d6d --- /dev/null +++ b/apps/fz_http/lib/fz_http_web/header_helpers.ex @@ -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 diff --git a/apps/fz_http/lib/fz_http_web/proxy_headers.ex b/apps/fz_http/lib/fz_http_web/proxy_headers.ex new file mode 100644 index 000000000..e22b95561 --- /dev/null +++ b/apps/fz_http/lib/fz_http_web/proxy_headers.ex @@ -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 diff --git a/apps/fz_http/mix.exs b/apps/fz_http/mix.exs index a165a7e33..ae4d03522 100644 --- a/apps/fz_http/mix.exs +++ b/apps/fz_http/mix.exs @@ -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 diff --git a/config/config.exs b/config/config.exs index afc1b7c20..586d1b215 100644 --- a/config/config.exs +++ b/config/config.exs @@ -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, diff --git a/config/dev.exs b/config/dev.exs index b3e17f77d..3d36f6ece 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -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, diff --git a/config/runtime.exs b/config/runtime.exs index 6339da997..341397824 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -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, diff --git a/docs/docs/deploy/reverse-proxies/README.md b/docs/docs/deploy/reverse-proxies/README.md new file mode 100644 index 000000000..9bc9d63a4 --- /dev/null +++ b/docs/docs/deploy/reverse-proxies/README.md @@ -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. diff --git a/docs/docs/deploy/reverse-proxies/apache.md b/docs/docs/deploy/reverse-proxies/apache.md new file mode 100644 index 000000000..be7312718 --- /dev/null +++ b/docs/docs/deploy/reverse-proxies/apache.md @@ -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. + +`` needs to be replaced with your domain name. + +This configuration needs to be placed in +`/etc/sites-available/.conf` + +and activated with `a2ensite ` + +```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 + + ServerName + 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] + +``` + +### 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 + + ServerName + 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" + +``` diff --git a/docs/docs/deploy/reverse-proxies/haproxy.md b/docs/docs/deploy/reverse-proxies/haproxy.md new file mode 100644 index 000000000..3335a72e7 --- /dev/null +++ b/docs/docs/deploy/reverse-proxies/haproxy.md @@ -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 +``` diff --git a/docs/docs/deploy/reverse-proxies/traefik.md b/docs/docs/deploy/reverse-proxies/traefik.md new file mode 100644 index 000000000..198296cf9 --- /dev/null +++ b/docs/docs/deploy/reverse-proxies/traefik.md @@ -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 +``` diff --git a/docs/docs/reference/configuration-file.md b/docs/docs/reference/configuration-file.md index edd838a9d..7e1dac6c9 100644 --- a/docs/docs/reference/configuration-file.md +++ b/docs/docs/reference/configuration-file.md @@ -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` | diff --git a/dummy b/dummy deleted file mode 100644 index 7e82c7c49..000000000 --- a/dummy +++ /dev/null @@ -1 +0,0 @@ -KORuM8G3PMPkIJVB1VMFsqAlLnprS/FgqIpFZ+C4M0E= diff --git a/mix.lock b/mix.lock index 2eeb76d28..191d2af49 100644 --- a/mix.lock +++ b/mix.lock @@ -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"}, diff --git a/omnibus/cookbooks/firezone/attributes/default.rb b/omnibus/cookbooks/firezone/attributes/default.rb index baadf022d..7ed0fd1b8 100644 --- a/omnibus/cookbooks/firezone/attributes/default.rb +++ b/omnibus/cookbooks/firezone/attributes/default.rb @@ -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 diff --git a/omnibus/cookbooks/firezone/libraries/config.rb b/omnibus/cookbooks/firezone/libraries/config.rb index c746eecde..f34d16069 100644 --- a/omnibus/cookbooks/firezone/libraries/config.rb +++ b/omnibus/cookbooks/firezone/libraries/config.rb @@ -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') diff --git a/omnibus/cookbooks/firezone/recipes/acme.rb b/omnibus/cookbooks/firezone/recipes/acme.rb index 5e2957348..d08047d8e 100644 --- a/omnibus/cookbooks/firezone/recipes/acme.rb +++ b/omnibus/cookbooks/firezone/recipes/acme.rb @@ -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'] diff --git a/omnibus/cookbooks/firezone/recipes/nginx.rb b/omnibus/cookbooks/firezone/recipes/nginx.rb index cb863ecc8..8f4d534b7 100644 --- a/omnibus/cookbooks/firezone/recipes/nginx.rb +++ b/omnibus/cookbooks/firezone/recipes/nginx.rb @@ -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 diff --git a/omnibus/cookbooks/firezone/recipes/ssl.rb b/omnibus/cookbooks/firezone/recipes/ssl.rb index 7686202d7..e6a58de5a 100644 --- a/omnibus/cookbooks/firezone/recipes/ssl.rb +++ b/omnibus/cookbooks/firezone/recipes/ssl.rb @@ -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 diff --git a/omnibus/cookbooks/firezone/templates/nginx.conf.erb b/omnibus/cookbooks/firezone/templates/nginx.conf.erb index a0c7073a8..e079a7a4f 100644 --- a/omnibus/cookbooks/firezone/templates/nginx.conf.erb +++ b/omnibus/cookbooks/firezone/templates/nginx.conf.erb @@ -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 -%> } diff --git a/omnibus/cookbooks/firezone/templates/phoenix.nginx.conf.erb b/omnibus/cookbooks/firezone/templates/phoenix.nginx.conf.erb index a5f3c3a54..50798f207 100644 --- a/omnibus/cookbooks/firezone/templates/phoenix.nginx.conf.erb +++ b/omnibus/cookbooks/firezone/templates/phoenix.nginx.conf.erb @@ -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;