Merge branch '1668-prometheus-access-restrictions' into 'develop'
authorfeld <feld@feld.me>
Tue, 27 Oct 2020 17:47:56 +0000 (17:47 +0000)
committerfeld <feld@feld.me>
Tue, 27 Oct 2020 17:47:56 +0000 (17:47 +0000)
[#1668] App metrics endpoint (Prometheus) access restrictions

Closes #1668

See merge request pleroma/pleroma!3093

1  2 
CHANGELOG.md
config/config.exs
config/description.exs
lib/pleroma/web/endpoint.ex

diff --combined CHANGELOG.md
index afeaa930bb8591454b8d7b646baef95d749c83a7,9f6a31f23245abda439dd9f7c050f31ba514773d..ac91d4d9ee9f710990675479eb00f83296dc4281
@@@ -12,12 -12,14 +12,14 @@@ The format is based on [Keep a Changelo
  - Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details).
  - Pleroma API: Importing the mutes users from CSV files.
  - Experimental websocket-based federation between Pleroma instances.
+ - App metrics: ability to restrict access to specified IP whitelist.
  
  ### Changed
  
  - **Breaking** Requires `libmagic` (or `file`) to guess file types.
  - **Breaking:** Pleroma Admin API: emoji packs and files routes changed.
  - **Breaking:** Sensitive/NSFW statuses no longer disable link previews.
+ - **Breaking:** App metrics endpoint (`/api/pleroma/app_metrics`) is disabled by default, check `docs/API/prometheus.md` on enabling and configuring. 
  - Search: Users are now findable by their urls.
  - Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated.
  - Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated.
@@@ -47,7 -49,6 +49,7 @@@ switched to a new configuration mechani
  
  - Add documented-but-missing chat pagination.
  - Allow sending out emails again.
 +- Allow sending chat messages to yourself
  
  ## Unreleased (Patch)
  
diff --combined config/config.exs
index 124f30a77b3cc6713f3901b007b2f7a10f5e7cd8,a7aae58021972da526eacebe781013c48a80e82a..bd611fd426c45c1325e29ac48d030e7fa43014bd
@@@ -123,6 -123,7 +123,6 @@@ websocket_config = 
  
  # Configures the endpoint
  config :pleroma, Pleroma.Web.Endpoint,
 -  instrumenters: [Pleroma.Web.Endpoint.Instrumenter],
    url: [host: "localhost"],
    http: [
      ip: {127, 0, 0, 1},
    secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
    signing_salt: "CqaoopA2",
    render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
 -  pubsub: [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2],
 +  pubsub_server: Pleroma.PubSub,
    secure_cookie_flag: true,
    extra_cookie_attrs: [
      "SameSite=Lax"
@@@ -635,7 -636,12 +635,12 @@@ config :pleroma, Pleroma.Emails.UserEma
  
  config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: false
  
- config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
+ config :prometheus, Pleroma.Web.Endpoint.MetricsExporter,
+   enabled: false,
+   auth: false,
+   ip_whitelist: [],
+   path: "/api/pleroma/app_metrics",
+   format: :text
  
  config :pleroma, Pleroma.ScheduledActivity,
    daily_user_limit: 25,
diff --combined config/description.exs
index 0da1da57d85a55b35ef9f8e0a712b3e52ab37a90,11755d757b793c9fcea30ac6abacab6a28f81648..55363c45ae655135772325f889a92deb3c707ab4
@@@ -829,7 -829,13 +829,7 @@@ config :pleroma, :config_description, 
          key: :autofollowed_nicknames,
          type: {:list, :string},
          description:
 -          "Set to nicknames of (local) users that every new user should automatically follow",
 -        suggestions: [
 -          "lain",
 -          "kaniini",
 -          "lanodan",
 -          "rinpatch"
 -        ]
 +          "Set to nicknames of (local) users that every new user should automatically follow"
        },
        %{
          key: :attachment_links,
          suggestions: [2]
        }
      ]
+   },
+   %{
+     group: :prometheus,
+     key: Pleroma.Web.Endpoint.MetricsExporter,
+     type: :group,
+     description: "Prometheus app metrics endpoint configuration",
+     children: [
+       %{
+         key: :enabled,
+         type: :boolean,
+         description: "[Pleroma extension] Enables app metrics endpoint."
+       },
+       %{
+         key: :ip_whitelist,
+         type: [{:list, :string}, {:list, :charlist}, {:list, :tuple}],
+         description:
+           "[Pleroma extension] If non-empty, restricts access to app metrics endpoint to specified IP addresses."
+       },
+       %{
+         key: :auth,
+         type: [:boolean, :tuple],
+         description: "Enables HTTP Basic Auth for app metrics endpoint.",
+         suggestion: [false, {:basic, "myusername", "mypassword"}]
+       },
+       %{
+         key: :path,
+         type: :string,
+         description: "App metrics endpoint URI path.",
+         suggestions: ["/api/pleroma/app_metrics"]
+       },
+       %{
+         key: :format,
+         type: :atom,
+         description: "App metrics endpoint output format.",
+         suggestions: [:text, :protobuf]
+       }
+     ]
    }
  ]
index d0e01f3d94b8bd038cbcfe5f1537d566669eb486,1a8fdd8b9d1a7b740e3a428988d662c625aa9e9f..f26542e888c435917c48b08d62664fc569d4aa5d
@@@ -7,10 -7,10 +7,12 @@@ defmodule Pleroma.Web.Endpoint d
  
    require Pleroma.Constants
  
+   alias Pleroma.Config
    socket("/socket", Pleroma.Web.UserSocket)
  
 +  plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
 +
    plug(Pleroma.Web.Plugs.SetLocalePlug)
    plug(CORSPlug)
    plug(Pleroma.Web.Plugs.HTTPSecurityPlug)
    plug(Plug.Parsers,
      parsers: [
        :urlencoded,
-       {:multipart, length: {Pleroma.Config, :get, [[:instance, :upload_limit]]}},
+       {:multipart, length: {Config, :get, [[:instance, :upload_limit]]}},
        :json
      ],
      pass: ["*/*"],
      json_decoder: Jason,
-     length: Pleroma.Config.get([:instance, :upload_limit]),
+     length: Config.get([:instance, :upload_limit]),
      body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
    )
  
    plug(Plug.MethodOverride)
    plug(Plug.Head)
  
-   secure_cookies = Pleroma.Config.get([__MODULE__, :secure_cookie_flag])
+   secure_cookies = Config.get([__MODULE__, :secure_cookie_flag])
  
    cookie_name =
      if secure_cookies,
        else: "pleroma_key"
  
    extra =
-     Pleroma.Config.get([__MODULE__, :extra_cookie_attrs])
+     Config.get([__MODULE__, :extra_cookie_attrs])
      |> Enum.join(";")
  
    # The session will be stored in the cookie and signed,
      Plug.Session,
      store: :cookie,
      key: cookie_name,
-     signing_salt: Pleroma.Config.get([__MODULE__, :signing_salt], "CqaoopA2"),
+     signing_salt: Config.get([__MODULE__, :signing_salt], "CqaoopA2"),
      http_only: true,
      secure: secure_cookies,
      extra: extra
      use Prometheus.PlugExporter
    end
  
+   defmodule MetricsExporterCaller do
+     @behaviour Plug
+     def init(opts), do: opts
+     def call(conn, opts) do
+       prometheus_config = Application.get_env(:prometheus, MetricsExporter, [])
+       ip_whitelist = List.wrap(prometheus_config[:ip_whitelist])
+       cond do
+         !prometheus_config[:enabled] ->
+           conn
+         ip_whitelist != [] and
+             !Enum.find(ip_whitelist, fn ip ->
+               Pleroma.Helpers.InetHelper.parse_address(ip) == {:ok, conn.remote_ip}
+             end) ->
+           conn
+         true ->
+           MetricsExporter.call(conn, opts)
+       end
+     end
+   end
    plug(PipelineInstrumenter)
-   plug(MetricsExporter)
+   plug(MetricsExporterCaller)
  
    plug(Pleroma.Web.Router)