Merge branch 'fix/emudhra-ca-trust' (#77) into release/2.11
# Conflicts: # lib/pleroma/http/adapter_helper/hackney.ex # mix.lock
This commit is contained in:
commit
e0ed6b5d39
12 changed files with 144 additions and 12 deletions
1
changelog.d/hackney-castore.security
Normal file
1
changelog.d/hackney-castore.security
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Hackney and Finch now trust only the CAStore certificate bundle, which rejects the compromised eMudhra intermediate CAs
|
||||||
1
changelog.d/mailer-tls-options.fix
Normal file
1
changelog.d/mailer-tls-options.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fix Swoosh HTTP mail adapters failing with `cacerts: :undefined` on OTP 26+
|
||||||
|
|
@ -66,6 +66,7 @@ defmodule Pleroma.Application do
|
||||||
|
|
||||||
if match?({Tesla.Adapter.Finch, _}, adapter) do
|
if match?({Tesla.Adapter.Finch, _}, adapter) do
|
||||||
Logger.info("Starting Finch")
|
Logger.info("Starting Finch")
|
||||||
|
use_castore_for_otp_cacerts()
|
||||||
Finch.start_link(name: MyFinch)
|
Finch.start_link(name: MyFinch)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -304,6 +305,15 @@ defmodule Pleroma.Application do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Mint trusts the OTP CA store, which comes from the OS. Make it the CAStore
|
||||||
|
# bundle that the other adapters trust. OTP 24 has no such store and Mint
|
||||||
|
# uses CAStore there anyway.
|
||||||
|
defp use_castore_for_otp_cacerts do
|
||||||
|
if Code.ensure_loaded?(:public_key) and function_exported?(:public_key, :cacerts_load, 1) do
|
||||||
|
:ok = :public_key.cacerts_load(CAStore.file_path())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp http_children_hackney do
|
defp http_children_hackney do
|
||||||
pools = [:federation, :media]
|
pools = [:federation, :media]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,11 @@ defmodule Pleroma.Emails.Mailer do
|
||||||
def deliver(email, config \\ [])
|
def deliver(email, config \\ [])
|
||||||
|
|
||||||
def deliver(email, config) do
|
def deliver(email, config) do
|
||||||
# temporary hackney fix until hackney max_connections bug is fixed
|
# Swoosh HTTP adapters use Hackney, which otherwise trusts its certifi bundle
|
||||||
# https://git.pleroma.social/pleroma/pleroma/-/issues/2101
|
|
||||||
email =
|
email =
|
||||||
Swoosh.Email.put_private(email, :hackney_options, ssl_options: [versions: [:"tlsv1.2"]])
|
Swoosh.Email.put_private(email, :hackney_options,
|
||||||
|
ssl_options: Pleroma.HTTP.TLS.ssl_options()
|
||||||
|
)
|
||||||
|
|
||||||
case enabled?() do
|
case enabled?() do
|
||||||
true -> Swoosh.Mailer.deliver(email, parse_config(config))
|
true -> Swoosh.Mailer.deliver(email, parse_config(config))
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do
|
||||||
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy)
|
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy)
|
||||||
|> Keyword.put(:follow_redirect, false)
|
|> Keyword.put(:follow_redirect, false)
|
||||||
|> Keyword.put(:force_redirect, false)
|
|> Keyword.put(:force_redirect, false)
|
||||||
|
|> Pleroma.HTTP.TLS.put_hackney_ssl_options()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_scheme_opts(opts, _), do: opts
|
defp add_scheme_opts(opts, _), do: opts
|
||||||
|
|
|
||||||
41
lib/pleroma/http/tls.ex
Normal file
41
lib/pleroma/http/tls.ex
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.HTTP.TLS do
|
||||||
|
@moduledoc """
|
||||||
|
TLS client options that trust the CAStore bundle.
|
||||||
|
|
||||||
|
Hackney otherwise trusts its own certifi bundle. Hackney 1 drops all of its
|
||||||
|
default TLS options, including certificate checks, as soon as any
|
||||||
|
`ssl_options` are given. Hackney 4 merges them, so `partial_chain` must be
|
||||||
|
set too: Hackney's own one trusts any certificate in the peer chain that is
|
||||||
|
in certifi.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@spec ssl_options(keyword()) :: keyword()
|
||||||
|
def ssl_options(overrides \\ []) do
|
||||||
|
Keyword.merge(
|
||||||
|
[
|
||||||
|
verify: :verify_peer,
|
||||||
|
cacertfile: CAStore.file_path(),
|
||||||
|
depth: 20,
|
||||||
|
customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)],
|
||||||
|
partial_chain: &__MODULE__.reject_partial_chain/1
|
||||||
|
],
|
||||||
|
overrides
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def reject_partial_chain(_certs), do: :unknown_ca
|
||||||
|
|
||||||
|
@spec put_hackney_ssl_options(keyword()) :: keyword()
|
||||||
|
def put_hackney_ssl_options(opts) do
|
||||||
|
if opts[:insecure] do
|
||||||
|
opts
|
||||||
|
else
|
||||||
|
Keyword.put(opts, :ssl_options, ssl_options(Keyword.get(opts, :ssl_options, [])))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -28,9 +28,9 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do
|
||||||
@impl true
|
@impl true
|
||||||
def request(method, url, headers, body, opts \\ []) do
|
def request(method, url, headers, body, opts \\ []) do
|
||||||
opts =
|
opts =
|
||||||
Keyword.put_new(opts, :path_encode_fun, fn path ->
|
opts
|
||||||
path
|
|> Keyword.put_new(:path_encode_fun, fn path -> path end)
|
||||||
end)
|
|> Pleroma.HTTP.TLS.put_hackney_ssl_options()
|
||||||
|
|
||||||
if opts[:follow_redirect] != false do
|
if opts[:follow_redirect] != false do
|
||||||
{_state, req_opts} = Access.get_and_update(opts, :follow_redirect, fn a -> {a, false} end)
|
{_state, req_opts} = Access.get_and_update(opts, :follow_redirect, fn a -> {a, false} end)
|
||||||
|
|
|
||||||
|
|
@ -52,4 +52,13 @@ defmodule Pleroma.Emails.MailerTest do
|
||||||
subject: "Pleroma test email"
|
subject: "Pleroma test email"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "keeps certificate checks for HTTP API adapters" do
|
||||||
|
Mailer.deliver(@email)
|
||||||
|
|
||||||
|
assert_received {:email, %Swoosh.Email{private: %{hackney_options: hackney_options}}}
|
||||||
|
|
||||||
|
assert hackney_options[:ssl_options] ==
|
||||||
|
Pleroma.HTTP.TLS.ssl_options()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -46,5 +46,20 @@ defmodule Pleroma.HTTP.AdapterHelper.HackneyTest do
|
||||||
assert opts[:b] == 1
|
assert opts[:b] == 1
|
||||||
refute Keyword.has_key?(opts, :proxy)
|
refute Keyword.has_key?(opts, :proxy)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "trusts the CAStore bundle", %{uri: uri} do
|
||||||
|
opts = Hackney.options([], uri)
|
||||||
|
|
||||||
|
assert opts[:ssl_options] == Pleroma.HTTP.TLS.ssl_options()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "keeps the CAStore bundle with configured ssl_options", %{uri: uri} do
|
||||||
|
clear_config([:http, :adapter], ssl_options: [versions: [:"tlsv1.2"]])
|
||||||
|
|
||||||
|
opts = Hackney.options([], uri)
|
||||||
|
|
||||||
|
assert opts[:ssl_options][:versions] == [:"tlsv1.2"]
|
||||||
|
assert opts[:ssl_options][:cacertfile] == CAStore.file_path()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
53
test/pleroma/http/tls_test.exs
Normal file
53
test/pleroma/http/tls_test.exs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.HTTP.TLSTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
alias Pleroma.HTTP.TLS
|
||||||
|
|
||||||
|
describe "ssl_options/1" do
|
||||||
|
test "verifies peers against the CAStore bundle" do
|
||||||
|
opts = TLS.ssl_options()
|
||||||
|
|
||||||
|
assert opts[:verify] == :verify_peer
|
||||||
|
assert opts[:cacertfile] == CAStore.file_path()
|
||||||
|
assert [match_fun: match_fun] = opts[:customize_hostname_check]
|
||||||
|
assert is_function(match_fun, 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not accept certificates from the peer chain as trust anchors" do
|
||||||
|
partial_chain = TLS.ssl_options()[:partial_chain]
|
||||||
|
|
||||||
|
assert partial_chain.([:certificate]) == :unknown_ca
|
||||||
|
end
|
||||||
|
|
||||||
|
test "keeps the CA bundle when other options are given" do
|
||||||
|
opts = TLS.ssl_options(versions: [:"tlsv1.2"])
|
||||||
|
|
||||||
|
assert opts[:versions] == [:"tlsv1.2"]
|
||||||
|
assert opts[:cacertfile] == CAStore.file_path()
|
||||||
|
assert opts[:verify] == :verify_peer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "put_hackney_ssl_options/1" do
|
||||||
|
test "adds the TLS options" do
|
||||||
|
opts = TLS.put_hackney_ssl_options(pool: :media)
|
||||||
|
|
||||||
|
assert opts[:pool] == :media
|
||||||
|
assert opts[:ssl_options] == TLS.ssl_options()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "merges given ssl_options into the TLS options" do
|
||||||
|
opts = TLS.put_hackney_ssl_options(ssl_options: [versions: [:"tlsv1.2"]])
|
||||||
|
|
||||||
|
assert opts[:ssl_options] == TLS.ssl_options(versions: [:"tlsv1.2"])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "leaves insecure requests alone" do
|
||||||
|
assert TLS.put_hackney_ssl_options(insecure: true) == [insecure: true]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -531,9 +531,9 @@ defmodule Pleroma.UserTest do
|
||||||
ObanHelpers.perform_all()
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
Pleroma.Emails.UserEmail.account_confirmation_email(registered_user)
|
Pleroma.Emails.UserEmail.account_confirmation_email(registered_user)
|
||||||
# temporary hackney fix until hackney max_connections bug is fixed
|
|> Swoosh.Email.put_private(:hackney_options,
|
||||||
# https://git.pleroma.social/pleroma/pleroma/-/issues/2101
|
ssl_options: Pleroma.HTTP.TLS.ssl_options()
|
||||||
|> Swoosh.Email.put_private(:hackney_options, ssl_options: [versions: [:"tlsv1.2"]])
|
)
|
||||||
|> assert_email_sent()
|
|> assert_email_sent()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1042,9 +1042,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
||||||
ObanHelpers.perform_all()
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
Pleroma.Emails.UserEmail.account_confirmation_email(first_user)
|
Pleroma.Emails.UserEmail.account_confirmation_email(first_user)
|
||||||
# temporary hackney fix until hackney max_connections bug is fixed
|
|> Swoosh.Email.put_private(:hackney_options,
|
||||||
# https://git.pleroma.social/pleroma/pleroma/-/issues/2101
|
ssl_options: Pleroma.HTTP.TLS.ssl_options()
|
||||||
|> Swoosh.Email.put_private(:hackney_options, ssl_options: [versions: [:"tlsv1.2"]])
|
)
|
||||||
|> assert_email_sent()
|
|> assert_email_sent()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue