Merge branch '1668-prometheus-access-restrictions' into 'develop'
[#1668] App metrics endpoint (Prometheus) access restrictions Closes #1668 See merge request pleroma/pleroma!3093
This commit is contained in:
parent
5f27a39152
commit
86b4149a13
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -5,14 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
## [2.2.0] - 2020-10-??
|
||||
|
||||
### Added
|
||||
- Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`)
|
||||
- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`)
|
||||
- Mix task option for force-unfollowing relays
|
||||
|
||||
### Changed
|
||||
|
||||
- **Breaking** Requires `libmagic` (or `file`) to guess file types.
|
||||
- **Breaking:** App metrics endpoint (`/api/pleroma/app_metrics`) is disabled by default, check `docs/API/prometheus.md` on enabling and configuring.
|
||||
- **Breaking:** Pleroma Admin API: emoji packs and files routes changed.
|
||||
- **Breaking:** Sensitive/NSFW statuses no longer disable link previews.
|
||||
- API: Empty parameter values for integer parameters are now ignored in non-strict validaton mode.
|
||||
|
@ -24,9 +20,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Minimum lifetime for ephmeral activities changed to 10 minutes and made configurable (`:min_lifetime` option).
|
||||
- Introduced optional dependencies on `ffmpeg`, `ImageMagick`, `exiftool` software packages. Please refer to `docs/installation/optional/media_graphics_packages.md`.
|
||||
|
||||
### Removed
|
||||
|
||||
- **Breaking:** `Pleroma.Workers.Cron.StatsWorker` setting from Oban `:crontab` (moved to a simpler implementation).
|
||||
- **Breaking:** `Pleroma.Workers.Cron.ClearOauthTokenWorker` setting from Oban `:crontab` (moved to scheduled jobs).
|
||||
- **Breaking:** `Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker` setting from Oban `:crontab` (moved to scheduled jobs).
|
||||
- Removed `:managed_config` option. In practice, it was accidentally removed with 2.0.0 release when frontends were
|
||||
switched to a new configuration mechanism, however it was not officially removed until now.
|
||||
|
||||
### Added
|
||||
- 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.
|
||||
- Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`)
|
||||
- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`)
|
||||
- Mix task option for force-unfollowing relays
|
||||
- App metrics: ability to restrict access to specified IP whitelist.
|
||||
|
||||
|
||||
<details>
|
||||
<summary>API Changes</summary>
|
||||
|
@ -37,13 +46,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
</details>
|
||||
|
||||
### Removed
|
||||
|
||||
- **Breaking:** `Pleroma.Workers.Cron.StatsWorker` setting from Oban `:crontab` (moved to a simpler implementation).
|
||||
- **Breaking:** `Pleroma.Workers.Cron.ClearOauthTokenWorker` setting from Oban `:crontab` (moved to scheduled jobs).
|
||||
- **Breaking:** `Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker` setting from Oban `:crontab` (moved to scheduled jobs).
|
||||
- Removed `:managed_config` option. In practice, it was accidentally removed with 2.0.0 release when frontends were
|
||||
switched to a new configuration mechanism, however it was not officially removed until now.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
@ -637,7 +637,12 @@
|
|||
|
||||
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,
|
||||
|
|
|
@ -3709,5 +3709,42 @@
|
|||
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]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -2,15 +2,37 @@
|
|||
|
||||
Pleroma includes support for exporting metrics via the [prometheus_ex](https://github.com/deadtrickster/prometheus.ex) library.
|
||||
|
||||
Config example:
|
||||
|
||||
```
|
||||
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter,
|
||||
enabled: true,
|
||||
auth: {:basic, "myusername", "mypassword"},
|
||||
ip_whitelist: ["127.0.0.1"],
|
||||
path: "/api/pleroma/app_metrics",
|
||||
format: :text
|
||||
```
|
||||
|
||||
* `enabled` (Pleroma extension) enables the endpoint
|
||||
* `ip_whitelist` (Pleroma extension) could be used to restrict access only to specified IPs
|
||||
* `auth` sets the authentication (`false` for no auth; configurable to HTTP Basic Auth, see [prometheus-plugs](https://github.com/deadtrickster/prometheus-plugs#exporting) documentation)
|
||||
* `format` sets the output format (`:text` or `:protobuf`)
|
||||
* `path` sets the path to app metrics page
|
||||
|
||||
|
||||
## `/api/pleroma/app_metrics`
|
||||
|
||||
### Exports Prometheus application metrics
|
||||
|
||||
* Method: `GET`
|
||||
* Authentication: not required
|
||||
* Authentication: not required by default (see configuration options above)
|
||||
* Params: none
|
||||
* Response: JSON
|
||||
* Response: text
|
||||
|
||||
## Grafana
|
||||
|
||||
### Config example
|
||||
|
||||
The following is a config example to use with [Grafana](https://grafana.com)
|
||||
|
||||
```
|
||||
|
|
19
lib/pleroma/helpers/inet_helper.ex
Normal file
19
lib/pleroma/helpers/inet_helper.ex
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Helpers.InetHelper do
|
||||
def parse_address(ip) when is_tuple(ip) do
|
||||
{:ok, ip}
|
||||
end
|
||||
|
||||
def parse_address(ip) when is_binary(ip) do
|
||||
ip
|
||||
|> String.to_charlist()
|
||||
|> parse_address()
|
||||
end
|
||||
|
||||
def parse_address(ip) do
|
||||
:inet.parse_address(ip)
|
||||
end
|
||||
end
|
|
@ -7,6 +7,8 @@ defmodule Pleroma.Web.Endpoint do
|
|||
|
||||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.Config
|
||||
|
||||
socket("/socket", Pleroma.Web.UserSocket)
|
||||
|
||||
plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
|
||||
|
@ -88,19 +90,19 @@ defmodule Pleroma.Web.Endpoint do
|
|||
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,
|
||||
|
@ -108,7 +110,7 @@ defmodule Pleroma.Web.Endpoint do
|
|||
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,
|
||||
|
@ -118,7 +120,7 @@ defmodule Pleroma.Web.Endpoint do
|
|||
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
|
||||
|
@ -138,8 +140,34 @@ defmodule MetricsExporter do
|
|||
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)
|
||||
|
||||
|
|
69
test/pleroma/web/endpoint/metrics_exporter_test.exs
Normal file
69
test/pleroma/web/endpoint/metrics_exporter_test.exs
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Endpoint.MetricsExporterTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
alias Pleroma.Web.Endpoint.MetricsExporter
|
||||
|
||||
defp config do
|
||||
Application.get_env(:prometheus, MetricsExporter)
|
||||
end
|
||||
|
||||
describe "with default config" do
|
||||
test "does NOT expose app metrics", %{conn: conn} do
|
||||
conn
|
||||
|> get(config()[:path])
|
||||
|> json_response(404)
|
||||
end
|
||||
end
|
||||
|
||||
describe "when enabled" do
|
||||
setup do
|
||||
initial_config = config()
|
||||
on_exit(fn -> Application.put_env(:prometheus, MetricsExporter, initial_config) end)
|
||||
|
||||
Application.put_env(
|
||||
:prometheus,
|
||||
MetricsExporter,
|
||||
Keyword.put(initial_config, :enabled, true)
|
||||
)
|
||||
end
|
||||
|
||||
test "serves app metrics", %{conn: conn} do
|
||||
conn = get(conn, config()[:path])
|
||||
assert response = response(conn, 200)
|
||||
|
||||
for metric <- [
|
||||
"http_requests_total",
|
||||
"http_request_duration_microseconds",
|
||||
"phoenix_controller_render_duration",
|
||||
"phoenix_controller_call_duration",
|
||||
"telemetry_scrape_duration",
|
||||
"erlang_vm_memory_atom_bytes_total"
|
||||
] do
|
||||
assert response =~ ~r/#{metric}/
|
||||
end
|
||||
end
|
||||
|
||||
test "when IP whitelist configured, " <>
|
||||
"serves app metrics only if client IP is whitelisted",
|
||||
%{conn: conn} do
|
||||
Application.put_env(
|
||||
:prometheus,
|
||||
MetricsExporter,
|
||||
Keyword.put(config(), :ip_whitelist, ["127.127.127.127", {1, 1, 1, 1}, '255.255.255.255'])
|
||||
)
|
||||
|
||||
conn
|
||||
|> get(config()[:path])
|
||||
|> json_response(404)
|
||||
|
||||
conn
|
||||
|> Map.put(:remote_ip, {127, 127, 127, 127})
|
||||
|> get(config()[:path])
|
||||
|> response(200)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue