Sovite.Core.Web (sovite v0.6.0)

Copy Markdown View Source

The web server: the status UI and the admin API, served with Plug and Cowboy on admin.address and admin.port (127.0.0.1:9025 by default), with TLS from the certificate store when admin.tls is set.

  • /api/... - the admin API (Sovite.Core.Web.API), for sovitectl, monitoring, and the UI.
  • /metrics - Prometheus metrics (Sovite.Core.Metrics).
  • Everything else - the UI: the static build of lib/core/sovite_web, from admin.web_root, or priv/web in the release.

Ports 80 and 443 stay with ACME (tls.acme) and the MTA-STS policy server (mta_sts.serve).

Security

The UI will have users of its own, so the web server is built for untrusted clients:

  • Only clients in admin.allowed_networks (loopback by default) get an answer other than 403.
  • The API and /metrics need the bearer token from admin.token_file (created with a random token, mode 0600, if it does not exist), compared in constant time. /metrics can be made public with admin.public_metrics.
  • Wrong tokens count as failed logins, like SMTP AUTH ones: they are logged as auth.failure (for fail2ban), and after auth.max_failures within auth.failure_window the client gets 429 for auth.ban_time.
  • Requests that change something (POST) from another site, by their Origin or Sec-Fetch-Site, get 403, and no cross-origin (CORS) access is granted, so pages elsewhere can neither read the API nor act on it.
  • Every response has strict security headers: a Content-Security-Policy that allows the UI's own scripts only (inline scripts of its pages by their hash), no framing, no referrer, nosniff, same-origin opener and resource policies, a permissions policy that turns off device access, and with TLS, Strict-Transport-Security. API responses are never cached.
  • Request lines, headers, and connections are limited, and slow clients time out.

Telemetry

  • [:sovite, :web, :request, :stop] - %{duration}, %{method, path, status, remote_ip}
  • [:sovite, :web, :request, :exception] - %{}, %{method, path, kind, reason}: a request that crashed, answered with 500.
  • [:sovite, :auth, :failure] with mechanism: "TOKEN" and reason: :invalid_token for a wrong token.

Summary

Functions

The child specs of the web server for config: the failed-token counter and the Cowboy listener. [] with admin.enabled = false, or when the token file cannot be used (logged).

Reads the token in path, or creates the file with a random token if it does not exist. Returns {:error, text} if it cannot.

Functions

child_specs(config, runtime)

@spec child_specs(Sovite.Core.Config.t(), map()) :: [Supervisor.child_spec()]

The child specs of the web server for config: the failed-token counter and the Cowboy listener. [] with admin.enabled = false, or when the token file cannot be used (logged).

token(path)

@spec token(Path.t()) :: {:ok, String.t()} | {:error, String.t()}

Reads the token in path, or creates the file with a random token if it does not exist. Returns {:error, text} if it cannot.