From d698b9657914d9557f6c382ec5657e702ae1c5cf Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 22 Oct 2026 11:21:00 +0400 Subject: [PATCH 1/2] fix(search): make the Qdrant clients trust the CAStore bundle The Qdrant and OpenAI clients do not go through Pleroma.HTTP, so on Hackney they trusted certifi. Both send API keys. Give them the configured adapter with the same TLS options. --- lib/pleroma/http/adapter_helper.ex | 18 ++++++++++ lib/pleroma/search/qdrant_search.ex | 4 +-- .../adapter_helper/tesla_adapter_test.exs | 34 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 test/pleroma/http/adapter_helper/tesla_adapter_test.exs diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index 32c1080f7..4f0e23f20 100644 --- a/lib/pleroma/http/adapter_helper.ex +++ b/lib/pleroma/http/adapter_helper.ex @@ -46,6 +46,24 @@ defmodule Pleroma.HTTP.AdapterHelper do |> adapter_helper().options(uri) end + @doc """ + The configured Tesla adapter with TLS options, for clients that do not go + through `Pleroma.HTTP`. + """ + @spec tesla_adapter() :: module() | {module(), keyword()} + def tesla_adapter do + case adapter() do + Tesla.Adapter.Hackney -> + {Tesla.Adapter.Hackney, Pleroma.HTTP.TLS.put_hackney_ssl_options([])} + + Tesla.Adapter.Gun -> + {Tesla.Adapter.Gun, certificates_verification: true} + + adapter -> + adapter + end + end + defp adapter, do: Application.get_env(:tesla, :adapter) defp adapter_helper do diff --git a/lib/pleroma/search/qdrant_search.ex b/lib/pleroma/search/qdrant_search.ex index 4d57cfa88..a8c5a3eb9 100644 --- a/lib/pleroma/search/qdrant_search.ex +++ b/lib/pleroma/search/qdrant_search.ex @@ -160,7 +160,7 @@ defmodule Pleroma.Search.QdrantSearch.OpenAIClient do end defp client do - Tesla.client(middleware()) + Tesla.client(middleware(), Pleroma.HTTP.AdapterHelper.tesla_adapter()) end defp middleware do @@ -191,7 +191,7 @@ defmodule Pleroma.Search.QdrantSearch.QdrantClient do end defp client do - Tesla.client(middleware()) + Tesla.client(middleware(), Pleroma.HTTP.AdapterHelper.tesla_adapter()) end defp middleware do diff --git a/test/pleroma/http/adapter_helper/tesla_adapter_test.exs b/test/pleroma/http/adapter_helper/tesla_adapter_test.exs new file mode 100644 index 000000000..1ee2f13ba --- /dev/null +++ b/test/pleroma/http/adapter_helper/tesla_adapter_test.exs @@ -0,0 +1,34 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.AdapterHelper.TeslaAdapterTest do + use ExUnit.Case, async: false + + alias Pleroma.HTTP.AdapterHelper + alias Pleroma.HTTP.TLS + + setup do + adapter = Application.get_env(:tesla, :adapter) + on_exit(fn -> Application.put_env(:tesla, :adapter, adapter) end) + end + + test "adds the TLS options to Hackney" do + Application.put_env(:tesla, :adapter, Tesla.Adapter.Hackney) + + assert AdapterHelper.tesla_adapter() == + {Tesla.Adapter.Hackney, ssl_options: TLS.ssl_options()} + end + + test "turns on certificate checks for Gun" do + Application.put_env(:tesla, :adapter, Tesla.Adapter.Gun) + + assert AdapterHelper.tesla_adapter() == {Tesla.Adapter.Gun, certificates_verification: true} + end + + test "keeps other adapters" do + Application.put_env(:tesla, :adapter, {Tesla.Adapter.Finch, name: MyFinch}) + + assert AdapterHelper.tesla_adapter() == {Tesla.Adapter.Finch, name: MyFinch} + end +end From accadd99f38fb9b15d67a85db79e4f4550e97ef4 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 22 Oct 2026 11:21:50 +0400 Subject: [PATCH 2/2] fix(mailer): make Mua trust the CAStore bundle Mua (the SMTP adapter in the docs) trusts the OS CA store unless it gets a CA. Pass the CAStore bundle, but keep a CA that the admin set. --- changelog.d/hackney-castore.security | 2 +- lib/pleroma/emails/mailer.ex | 17 +++++++++++++++- test/pleroma/emails/mailer_test.exs | 29 ++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/changelog.d/hackney-castore.security b/changelog.d/hackney-castore.security index 44cb90ce6..8d1a0cf4a 100644 --- a/changelog.d/hackney-castore.security +++ b/changelog.d/hackney-castore.security @@ -1 +1 @@ -Hackney and Finch now trust only the CAStore certificate bundle, which rejects the compromised eMudhra intermediate CAs +Hackney, Finch, Mua and the Swoosh HTTP mail adapters now trust only the CAStore certificate bundle, which rejects the compromised eMudhra intermediate CAs diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index fa6fd8020..e6abf4456 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -68,6 +68,21 @@ defmodule Pleroma.Emails.Mailer do end defp parse_config(config) do - Swoosh.Mailer.parse_config(@otp_app, __MODULE__, @mailer_config, config) + @otp_app + |> Swoosh.Mailer.parse_config(__MODULE__, @mailer_config, config) + |> put_mua_cacertfile() + end + + @doc false + # Mua otherwise trusts the OS CA store. Keep a CA that the admin set. + def put_mua_cacertfile(config) do + ssl = Keyword.get(config, :ssl, []) + + if config[:adapter] == Swoosh.Adapters.Mua and + not Keyword.has_key?(ssl, :cacertfile) and not Keyword.has_key?(ssl, :cacerts) do + Keyword.put(config, :ssl, [{:cacertfile, CAStore.file_path()} | ssl]) + else + config + end end end diff --git a/test/pleroma/emails/mailer_test.exs b/test/pleroma/emails/mailer_test.exs index 2c2b5a4af..d42e8b723 100644 --- a/test/pleroma/emails/mailer_test.exs +++ b/test/pleroma/emails/mailer_test.exs @@ -61,4 +61,33 @@ defmodule Pleroma.Emails.MailerTest do assert hackney_options[:ssl_options] == Pleroma.HTTP.TLS.ssl_options() end + + describe "put_mua_cacertfile/1" do + test "makes Mua trust the CAStore bundle" do + config = Mailer.put_mua_cacertfile(adapter: Swoosh.Adapters.Mua, relay: "mail.example.com") + + assert config[:ssl] == [cacertfile: CAStore.file_path()] + assert config[:relay] == "mail.example.com" + end + + test "keeps other ssl options" do + config = Mailer.put_mua_cacertfile(adapter: Swoosh.Adapters.Mua, ssl: [depth: 3]) + + assert config[:ssl] == [cacertfile: CAStore.file_path(), depth: 3] + end + + test "keeps a CA that the admin set" do + config = [adapter: Swoosh.Adapters.Mua, ssl: [cacertfile: "/etc/ssl/smtp.pem"]] + assert Mailer.put_mua_cacertfile(config) == config + + config = [adapter: Swoosh.Adapters.Mua, ssl: [cacerts: []]] + assert Mailer.put_mua_cacertfile(config) == config + end + + test "leaves other adapters alone" do + config = [adapter: Swoosh.Adapters.SMTP, relay: "mail.example.com"] + + assert Mailer.put_mua_cacertfile(config) == config + end + end end