diff --git a/config/users.nix b/config/users.nix index 8ffd1cd..427ea78 100644 --- a/config/users.nix +++ b/config/users.nix @@ -43,5 +43,6 @@ unifi = uidGid 968; plugdev.gid = 967; tss = uidGid 966; + firezone-client = uidGid 965; }; } diff --git a/hosts/kroma/default.nix b/hosts/kroma/default.nix index e50a17e..5b89c21 100644 --- a/hosts/kroma/default.nix +++ b/hosts/kroma/default.nix @@ -122,4 +122,9 @@ dockerCompat = true; defaultNetwork.settings.dns_enabled = true; }; + + services.firezone.server.domain.enable = true; + services.firezone.server.domain.enableLocalDB = true; + services.firezone.server.web.enable = true; + services.firezone.server.api.enable = true; } diff --git a/modules/default.nix b/modules/default.nix index 260bdb2..0d7160e 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -8,6 +8,7 @@ ./backups.nix ./deterministic-ids.nix ./distributed-config.nix + ./firezone-server.nix ./globals.nix ./meta.nix ./netbird-client.nix diff --git a/modules/firezone-server.nix b/modules/firezone-server.nix new file mode 100644 index 0000000..d3f1ca2 --- /dev/null +++ b/modules/firezone-server.nix @@ -0,0 +1,455 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) + mkOption + mkIf + getExe + mkEnableOption + types + ; + + apiCfg = config.services.firezone.server.api; + domainCfg = config.services.firezone.server.domain; + webCfg = config.services.firezone.server.web; + + commonServiceConfig = { + # AmbientCapablities = "CAP_NET_ADMIN"; + # CapabilityBoundingSet = "CAP_CHOWN CAP_NET_ADMIN"; + # DeviceAllow = "/dev/net/tun"; + # LockPersonality = "true"; + # LogsDirectory = "dev.firezone.client"; + # LogsDirectoryMode = "755"; + # MemoryDenyWriteExecute = "true"; + # NoNewPrivileges = "true"; + # PrivateMounts = "true"; + # PrivateTmp = "true"; + # PrivateUsers = "false"; + # ProcSubset = "pid"; + # ProtectClock = "true"; + # ProtectControlGroups = "true"; + # ProtectHome = "true"; + # ProtectHostname = "true"; + # ProtectKernelLogs = "true"; + # ProtectKernelModules = "true"; + # ProtectKernelTunables = "true"; + # ProtectProc = "invisible"; + # ProtectSystem = "strict"; + # RestrictAddressFamilies = [ + # "AF_INET" + # "AF_INET6" + # "AF_NETLINK" + # "AF_UNIX" + # ]; + # RestrictNamespaces = "true"; + # RestrictRealtime = "true"; + # RestrictSUIDSGID = "true"; + # SystemCallArchitectures = "native"; + # SystemCallFilter = "@aio @basic-io @file-system @io-event @ipc @network-io @signal @system-service"; + # UMask = "077"; + + DynamicUser = true; + User = "firezone"; + + StateDirectory = "firezone"; + WorkingDirectory = "/var/lib/firezone"; + }; +in +{ + options.services.firezone.server = { + domain = { + enable = mkEnableOption "the Firezone domain server"; + # TODO: single package plus web and api passthrough. + # package = mkPackageOption pkgs "firezone-server" { }; + enableLocalDB = mkEnableOption "a local postgresql database for Firezone"; + + settings = lib.mkOption { + default = { }; + description = '' + TODO + ''; + example = { + }; + type = lib.types.submodule { + freeformType = types.attrsOf ( + types.oneOf [ + types.bool + types.float + types.int + types.str + types.path + types.package + ] + ); + options = { + }; + }; + }; + }; + web = { + enable = mkEnableOption "the Firezone web server"; + # TODO: single package plus web and api passthrough. + # package = mkPackageOption pkgs "firezone-server" { }; + + settings = lib.mkOption { + default = { }; + description = '' + TODO + ''; + example = { + }; + type = lib.types.submodule { + freeformType = types.attrsOf ( + types.oneOf [ + types.bool + types.float + types.int + types.str + types.path + types.package + ] + ); + options = { + }; + }; + }; + }; + api = { + enable = mkEnableOption "the Firezone api server"; + # TODO: single package plus web and api passthrough. + # package = mkPackageOption pkgs "firezone-server" { }; + + settings = lib.mkOption { + default = { }; + description = '' + TODO + ''; + example = { + }; + type = lib.types.submodule { + freeformType = types.attrsOf ( + types.oneOf [ + types.bool + types.float + types.int + types.str + types.path + types.package + ] + ); + options = { + }; + }; + }; + }; + }; + + config = { + services.postgresql = mkIf domainCfg.enableLocalDB { + enable = true; + ensureUsers = [ + { + name = "firezone"; + ensureDBOwnership = true; + } + ]; + ensureDatabases = [ "firezone" ]; + }; + + systemd.services.firezone-server-domain = mkIf domainCfg.enable { + description = "Firezone domain server"; + after = mkIf domainCfg.enableLocalDB [ "postgresql.service" ]; + wants = mkIf domainCfg.enableLocalDB [ "postgresql.service" ]; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + mkdir -p tzdata + ''; + + serviceConfig = commonServiceConfig // { + ExecStart = "${getExe pkgs.firezone-server-domain} start"; + }; + + environment = { + # **EXTERNAL_URL** + # PHOENIX_SECURE_COOKIES + # PHOENIX_HTTP_PORT + # PHOENIX_HTTP_PROTOCOL_OPTIONS + # PHOENIX_EXTERNAL_TRUSTED_PROXIES + # PHOENIX_PRIVATE_CLIENTS + # HTTP_CLIENT_SSL_OPTS + # DATABASE_HOST + # DATABASE_PORT + # DATABASE_NAME + # DATABASE_USER + # DATABASE_PASSWORD + # DATABASE_POOL_SIZE + # DATABASE_SSL_ENABLED + # DATABASE_SSL_OPTS + # RESET_ADMIN_ON_BOOT + # DEFAULT_ADMIN_EMAIL + # DEFAULT_ADMIN_PASSWORD + # **GUARDIAN_SECRET_KEY** + # **DATABASE_ENCRYPTION_KEY** + # **SECRET_KEY_BASE** + # **LIVE_VIEW_SIGNING_SALT** + # **COOKIE_SIGNING_SALT** + # **COOKIE_ENCRYPTION_SALT** + # ALLOW_UNPRIVILEGED_DEVICE_MANAGEMENT + # ALLOW_UNPRIVILEGED_DEVICE_CONFIGURATION + # VPN_SESSION_DURATION + # DEFAULT_CLIENT_PERSISTENT_KEEPALIVE + # DEFAULT_CLIENT_MTU + # DEFAULT_CLIENT_ENDPOINT + # DEFAULT_CLIENT_DNS + # DEFAULT_CLIENT_ALLOWED_IPS + # MAX_DEVICES_PER_USER + # LOCAL_AUTH_ENABLED + # DISABLE_VPN_ON_OIDC_ERROR + # SAML_ENTITY_ID + # SAML_KEYFILE_PATH + # SAML_CERTFILE_PATH + # OPENID_CONNECT_PROVIDERS + # SAML_IDENTITY_PROVIDERS + # WIREGUARD_PORT + # OUTBOUND_EMAIL_FROM + # OUTBOUND_EMAIL_ADAPTER + # OUTBOUND_EMAIL_ADAPTER_OPTS + # CONNECTIVITY_CHECKS_ENABLED + # CONNECTIVITY_CHECKS_INTERVAL + # TELEMETRY_ENABLED + # LOGO + + TZDATA_DIR = "/var/lib/firezone/tzdata"; + TELEMETRY_ENABLED = "false"; + + RELEASE_COOKIE = "agfea"; # TODO make option, if null generate automatically on first start + RELEASE_NAME = "domain"; + RELEASE_HOSTNAME = "localhost.localdomain"; + + HEALTHZ_PORT = "4000"; + + # Debugging; + LOG_LEVEL = "debug"; + + # Erlang; + ERLANG_DISTRIBUTION_PORT = "9002"; + ERLANG_CLUSTER_ADAPTER = "Elixir.Cluster.Strategy.Epmd"; + ERLANG_CLUSTER_ADAPTER_CONFIG = builtins.toJSON { + hosts = [ + "api@localhost.localdomain" + "web@localhost.localdomain" + "domain@localhost.localdomain" + ]; + }; + + # Database; + DATABASE_SOCKET_DIR = "/run/postgresql"; + DATABASE_PORT = "5432"; + DATABASE_NAME = "firezone"; + DATABASE_USER = "firezone"; + #DATABASE_HOST = "localhost"; + #DATABASE_PASSWORD = ""; + + # Auth; + AUTH_PROVIDER_ADAPTERS = "email,openid_connect,userpass,token,google_workspace,microsoft_entra,okta,jumpcloud"; + + # Secrets; + TOKENS_KEY_BASE = "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"; + SECRET_KEY_BASE = "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"; + TOKENS_SALT = "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"; + LIVE_VIEW_SIGNING_SALT = "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"; + COOKIE_SIGNING_SALT = "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"; + COOKIE_ENCRYPTION_SALT = "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"; + + # Seeds; + STATIC_SEEDS = "true"; + + OUTBOUND_EMAIL_FROM = "public-noreply@firez.one"; + OUTBOUND_EMAIL_ADAPTER = "Elixir.Swoosh.Adapters.Postmark"; + ## Warning= The token is for the blackhole Postmark server created in a separate isolated account,; + ## that WILL NOT send any actual emails, but you can see and debug them in the Postmark dashboard.; + OUTBOUND_EMAIL_ADAPTER_OPTS = ''{"api_key":"7da7d1cd-111c-44a7-b5ac-4027b9d230e5"}''; + + # Feature flags; + FEATURE_FLOW_ACTIVITIES_ENABLED = "true"; + FEATURE_POLICY_CONDITIONS_ENABLED = "true"; + FEATURE_MULTI_SITE_RESOURCES_ENABLED = "true"; + FEATURE_SELF_HOSTED_RELAYS_ENABLED = "true"; + FEATURE_IDP_SYNC_ENABLED = "true"; + FEATURE_REST_API_ENABLED = "true"; + FEATURE_INTERNET_RESOURCE_ENABLED = "true"; + }; + }; + + systemd.services.firezone-server-web = mkIf webCfg.enable { + description = "Firezone web server"; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + mkdir -p tzdata + ''; + + serviceConfig = commonServiceConfig // { + ExecStart = "${getExe pkgs.firezone-server-web} start"; + }; + + environment = { + TZDATA_DIR = "/var/lib/firezone/tzdata"; + TELEMETRY_ENABLED = "false"; + + RELEASE_COOKIE = "agfea"; # TODO make option, if null generate automatically on first start + RELEASE_NAME = "web"; + RELEASE_HOSTNAME = "localhost.localdomain"; + + # Web Server + WEB_EXTERNAL_URL = "http://localhost:8080/"; + API_EXTERNAL_URL = "http://localhost:8081/"; + PHOENIX_HTTP_WEB_PORT = "8080"; + PHOENIX_HTTP_API_PORT = "8081"; + PHOENIX_SECURE_COOKIES = "false"; + + HEALTHZ_PORT = "4001"; + + # Debugging; + LOG_LEVEL = "debug"; + + # Erlang; + ERLANG_DISTRIBUTION_PORT = "9001"; + ERLANG_CLUSTER_ADAPTER = "Elixir.Cluster.Strategy.Epmd"; + ERLANG_CLUSTER_ADAPTER_CONFIG = builtins.toJSON { + hosts = [ + "api@localhost.localdomain" + "web@localhost.localdomain" + "domain@localhost.localdomain" + ]; + }; + + # Database; + DATABASE_SOCKET_DIR = "/run/postgresql"; + DATABASE_PORT = "5432"; + DATABASE_NAME = "firezone"; + DATABASE_USER = "firezone"; + #DATABASE_HOST = "localhost"; + #DATABASE_PASSWORD = ""; + + # Auth; + AUTH_PROVIDER_ADAPTERS = "email,openid_connect,userpass,token,google_workspace,microsoft_entra,okta,jumpcloud"; + + # Secrets; + TOKENS_KEY_BASE = "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"; + SECRET_KEY_BASE = "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"; + TOKENS_SALT = "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"; + LIVE_VIEW_SIGNING_SALT = "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"; + COOKIE_SIGNING_SALT = "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"; + COOKIE_ENCRYPTION_SALT = "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"; + + # Seeds; + STATIC_SEEDS = "true"; + + OUTBOUND_EMAIL_FROM = "public-noreply@firez.one"; + OUTBOUND_EMAIL_ADAPTER = "Elixir.Swoosh.Adapters.Postmark"; + ## Warning= The token is for the blackhole Postmark server created in a separate isolated account,; + ## that WILL NOT send any actual emails, but you can see and debug them in the Postmark dashboard.; + OUTBOUND_EMAIL_ADAPTER_OPTS = ''{"api_key":"7da7d1cd-111c-44a7-b5ac-4027b9d230e5"}''; + + # Feature flags; + FEATURE_FLOW_ACTIVITIES_ENABLED = "true"; + FEATURE_POLICY_CONDITIONS_ENABLED = "true"; + FEATURE_MULTI_SITE_RESOURCES_ENABLED = "true"; + FEATURE_SELF_HOSTED_RELAYS_ENABLED = "true"; + FEATURE_IDP_SYNC_ENABLED = "true"; + FEATURE_REST_API_ENABLED = "true"; + FEATURE_INTERNET_RESOURCE_ENABLED = "true"; + }; + }; + + systemd.services.firezone-server-api = mkIf apiCfg.enable { + description = "Firezone api server"; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + mkdir -p tzdata + ''; + + serviceConfig = commonServiceConfig // { + ExecStart = "${getExe pkgs.firezone-server-api} start"; + }; + + environment = { + TZDATA_DIR = "/var/lib/firezone/tzdata"; + TELEMETRY_ENABLED = "false"; + + RELEASE_COOKIE = "agfea"; # TODO make option, if null generate automatically on first start + RELEASE_NAME = "api"; + RELEASE_HOSTNAME = "localhost.localdomain"; + + # Web Server + WEB_EXTERNAL_URL = "http://localhost:8080/"; + API_EXTERNAL_URL = "http://localhost:8081/"; + + HEALTHZ_PORT = "4002"; + + # Debugging; + LOG_LEVEL = "debug"; + + # Erlang; + ERLANG_DISTRIBUTION_PORT = "9000"; + ERLANG_CLUSTER_ADAPTER = "Elixir.Cluster.Strategy.Epmd"; + ERLANG_CLUSTER_ADAPTER_CONFIG = builtins.toJSON { + hosts = [ + "api@localhost.localdomain" + "web@localhost.localdomain" + "domain@localhost.localdomain" + ]; + }; + + # Database; + DATABASE_SOCKET_DIR = "/run/postgresql"; + DATABASE_PORT = "5432"; + DATABASE_NAME = "firezone"; + DATABASE_USER = "firezone"; + #DATABASE_HOST = "localhost"; + #DATABASE_PASSWORD = ""; + + # Auth; + AUTH_PROVIDER_ADAPTERS = "email,openid_connect,userpass,token,google_workspace,microsoft_entra,okta,jumpcloud"; + + # Secrets; + TOKENS_KEY_BASE = "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"; + SECRET_KEY_BASE = "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5S2"; + TOKENS_SALT = "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"; + LIVE_VIEW_SIGNING_SALT = "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"; + COOKIE_SIGNING_SALT = "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"; + COOKIE_ENCRYPTION_SALT = "t01wa0K4lUd7mKa0HAtZdE+jFOPDDej2"; + + # Seeds; + STATIC_SEEDS = "true"; + + OUTBOUND_EMAIL_FROM = "public-noreply@firez.one"; + OUTBOUND_EMAIL_ADAPTER = "Elixir.Swoosh.Adapters.Postmark"; + ## Warning= The token is for the blackhole Postmark server created in a separate isolated account,; + ## that WILL NOT send any actual emails, but you can see and debug them in the Postmark dashboard.; + OUTBOUND_EMAIL_ADAPTER_OPTS = ''{"api_key":"7da7d1cd-111c-44a7-b5ac-4027b9d230e5"}''; + + # Feature flags; + FEATURE_FLOW_ACTIVITIES_ENABLED = "true"; + FEATURE_POLICY_CONDITIONS_ENABLED = "true"; + FEATURE_MULTI_SITE_RESOURCES_ENABLED = "true"; + FEATURE_SELF_HOSTED_RELAYS_ENABLED = "true"; + FEATURE_IDP_SYNC_ENABLED = "true"; + FEATURE_REST_API_ENABLED = "true"; + FEATURE_INTERNET_RESOURCE_ENABLED = "true"; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ + oddlama + patrickdag + ]; +} diff --git a/pkgs/default.nix b/pkgs/default.nix index 27f9f3f..d551c37 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -19,6 +19,9 @@ _inputs: [ # (_pythonFinal: pythonPrev: { # }) # ]; + firezone-server-domain = prev.callPackage ./firezone-server-domain/package.nix { }; + firezone-server-web = prev.callPackage ./firezone-server-web/package.nix { }; + firezone-server-api = prev.callPackage ./firezone-server-api/package.nix { }; formats = prev.formats // { ron = import ./ron.nix { inherit (prev) lib pkgs; }; diff --git a/pkgs/firezone-server-api/package.nix b/pkgs/firezone-server-api/package.nix new file mode 100644 index 0000000..16a5e7e --- /dev/null +++ b/pkgs/firezone-server-api/package.nix @@ -0,0 +1,3 @@ +import ../firezone-server-domain/generic.nix { + mixReleaseName = "api"; +} diff --git a/pkgs/firezone-server-domain/a.patch b/pkgs/firezone-server-domain/a.patch new file mode 100644 index 0000000..7f82320 --- /dev/null +++ b/pkgs/firezone-server-domain/a.patch @@ -0,0 +1,68 @@ +diff --git a/apps/domain/lib/domain/config/definitions.ex b/apps/domain/lib/domain/config/definitions.ex +index 8cd2e8d0f..f27d67c69 100644 +--- a/apps/domain/lib/domain/config/definitions.ex ++++ b/apps/domain/lib/domain/config/definitions.ex +@@ -61,6 +61,7 @@ defmodule Domain.Config.Definitions do + {"Database", + [ + :database_host, ++ :database_socket_dir, + :database_port, + :database_name, + :database_user, +@@ -255,6 +256,11 @@ defmodule Domain.Config.Definitions do + """ + defconfig(:database_host, :string, default: "postgres") + ++ @doc """ ++ PostgreSQL socket directory (takes precedence over hostname). ++ """ ++ defconfig(:database_socket_dir, :string, default: nil) ++ + @doc """ + PostgreSQL port. + """ +diff --git a/apps/domain/lib/domain/telemetry.ex b/apps/domain/lib/domain/telemetry.ex +index af430358d..a544e706e 100644 +--- a/apps/domain/lib/domain/telemetry.ex ++++ b/apps/domain/lib/domain/telemetry.ex +@@ -13,7 +13,7 @@ defmodule Domain.Telemetry do + + children = [ + # We start a /healthz endpoint that is used for liveness probes +- {Bandit, plug: Telemetry.HealthzPlug, scheme: :http, port: 4000}, ++ {Bandit, plug: Telemetry.HealthzPlug, scheme: :http, port: System.get_env("HEALTHZ_PORT") |> String.to_integer()}, + + # Telemetry poller will execute the given period measurements + # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics +diff --git a/config/runtime.exs b/config/runtime.exs +index 15037e0a3..948f62cc8 100644 +--- a/config/runtime.exs ++++ b/config/runtime.exs +@@ -8,15 +8,17 @@ if config_env() == :prod do + ############################### + + config :domain, Domain.Repo, +- database: compile_config!(:database_name), +- username: compile_config!(:database_user), +- hostname: compile_config!(:database_host), +- port: compile_config!(:database_port), +- password: compile_config!(:database_password), +- pool_size: compile_config!(:database_pool_size), +- ssl: compile_config!(:database_ssl_enabled), +- ssl_opts: compile_config!(:database_ssl_opts), +- parameters: compile_config!(:database_parameters) ++ [ ++ {:database, compile_config!(:database_name)}, ++ {:username, compile_config!(:database_user)}, ++ {:port, compile_config!(:database_port)}, ++ {:pool_size, compile_config!(:database_pool_size)}, ++ {:ssl, compile_config!(:database_ssl_enabled)}, ++ {:ssl_opts, compile_config!(:database_ssl_opts)}, ++ {:parameters, compile_config!(:database_parameters)} ++ ] ++ ++ (if System.get_env("DATABASE_PASSWORD"), do: [{:password, compile_config!(:database_password)}], else: []) ++ ++ (if System.get_env("DATABASE_SOCKET_DIR"), do: [{:socket_dir, compile_config!(:database_socket_dir)}], else: [{:hostname, compile_config!(:database_host)}]) + + config :domain, Domain.Tokens, + key_base: compile_config!(:tokens_key_base), diff --git a/pkgs/firezone-server-domain/generic.nix b/pkgs/firezone-server-domain/generic.nix new file mode 100644 index 0000000..74ff678 --- /dev/null +++ b/pkgs/firezone-server-domain/generic.nix @@ -0,0 +1,129 @@ +{ + mixReleaseName, # "domain" "web" or "api" +}: +{ + lib, + fetchFromGitHub, + beamPackages, + pnpm_9, + nodejs, + tailwindcss, + esbuild, +}: + +beamPackages.mixRelease rec { + pname = "firezone-server-${mixReleaseName}"; + version = "unstable-2025-01-19"; + + src = "${ + fetchFromGitHub { + owner = "firezone"; + repo = "firezone"; + rev = "8c9427b7b133e5050be34c2ac0e831c12c08f02c"; + hash = "sha256-yccplADHRJQQiKrmHcJ5rvouswHrbx4K6ysnIAoZJR0="; + } + }/elixir"; + patches = [ ./a.patch ]; + + pnpmDeps = pnpm_9.fetchDeps { + inherit pname version; + src = "${src}/apps/web/assets"; + hash = "sha256-6rhhGv3jQY5MkOMNe1GEtNyrzJYXCSzvo8RLlKelP10="; + }; + pnpmRoot = "apps/web/assets"; + + preBuild = '' + cat >> config/config.exs <> config/runtime.exs <