Home page: offer TLS when a proxy terminates it

5e17a59 · Gabriel Morell · 2026-10-27 17:25

10 files +101 -4
Message
{commit_body(@commit)}

Files changed

modified config/runtime.exs
+8 −0
@@ -54,6 +54,14 @@ if pp = System.get_env("IRC_PROXY_PROTOCOL") do
54 54 proxy_protocol: pp in ~w(1 true yes on)
55 55 end
56 56
57 +# The port clients reach TLS on when the proxy terminates it in front of
58 +# the plain listener (Traefik's `ircds` entrypoint, which forwards to 6667
59 +# with a PROXY header). Only for the home page's connection details;
60 +# defaults to 6697 there.
61 +if port = System.get_env("IRC_PROXY_TLS_PORT") do
62 + config :oh_my_ex_oh_ircd, :proxy_tls_port, String.to_integer(port)
63 +end
64 +
57 65 # Host cloaking base domain. Client IPs are shown as `<hash>.<cloak_host>`
58 66 # to non-opers. Explicit `IRC_CLOAK_HOST` wins; otherwise prod defaults to
59 67 # `PHX_HOST` so it's on out of the box. Disable with
modified lib/oh_my_ex_oh_ircd_web/live/home_live.ex
+18 −4
@@ -26,8 +26,21 @@ defmodule OhMyExOhIrcdWeb.HomeLive do
26 26 listener_cfg = Application.get_env(:oh_my_ex_oh_ircd, OhMyExOhIrcd.IRC.Listener, [])
27 27 tls_cfg = Application.get_env(:oh_my_ex_oh_ircd, OhMyExOhIrcd.IRC.TLSListener, [])
28 28 port = Keyword.get(listener_cfg, :port, 6667)
29 − tls_enabled? = Keyword.get(tls_cfg, :enabled, false)
30 − tls_port = Keyword.get(tls_cfg, :port, 6697)
29 +
30 + # TLS is either our own listener (ACME certificates, standalone) or the
31 + # proxy in front of the plain one: behind PROXY protocol the pod speaks
32 + # plaintext, and Traefik terminates TLS on its own port.
33 + {tls_enabled?, tls_port, tls_via} =
34 + cond do
35 + Keyword.get(tls_cfg, :enabled, false) ->
36 + {true, Keyword.get(tls_cfg, :port, 6697), "ACME"}
37 +
38 + Keyword.get(listener_cfg, :proxy_protocol, false) ->
39 + {true, Application.get_env(:oh_my_ex_oh_ircd, :proxy_tls_port, 6697), "proxy"}
40 +
41 + true ->
42 + {false, Keyword.get(tls_cfg, :port, 6697), nil}
43 + end
31 44
32 45 # Prefer the TLS port in the snippets when it's available — irssi /
33 46 # weechat just need a flag flip. The plain port is still shown in the
@@ -41,6 +54,7 @@ defmodule OhMyExOhIrcdWeb.HomeLive do
41 54 |> assign(:port, port)
42 55 |> assign(:tls_enabled, tls_enabled?)
43 56 |> assign(:tls_port, tls_port)
57 + |> assign(:tls_via, tls_via)
44 58 |> assign(:allow_anonymous, Keyword.get(listener_cfg, :allow_anonymous, false))
45 59 |> assign(:channel_count, Stats.channel_count())
46 60 |> assign(:user_count, Stats.user_count())
@@ -117,10 +131,10 @@ defmodule OhMyExOhIrcdWeb.HomeLive do
117 131 <span class="ml-2 text-base-content">{@server_name}:{@port}</span>
118 132 </p>
119 133 <%= if @tls_enabled do %>
120 − <p>
134 + <p id="connect-tls">
121 135 <span class="text-base-content/60">Server (TLS)</span>
122 136 <span class="ml-2 text-base-content">{@server_name}:{@tls_port}</span>
123 − <span class="badge badge-success badge-xs align-middle ml-2">ACME</span>
137 + <span class="badge badge-success badge-xs align-middle ml-2">{@tls_via}</span>
124 138 </p>
125 139 <% end %>
126 140 <%= if @allow_anonymous do %>
added test/oh_my_ex_oh_ircd_web/live/home_live_test.exs
+75 −0
@@ -0,0 +1,75 @@
1 +defmodule OhMyExOhIrcdWeb.HomeLiveTest do
2 + @moduledoc """
3 + The home page's connection details offer TLS wherever a client can get
4 + it: our own listener (ACME), or the proxy in front of the plain one when
5 + the listener expects PROXY protocol (Traefik terminates TLS there).
6 +
7 + Not async: it changes application env the whole node reads.
8 + """
9 + use OhMyExOhIrcdWeb.ConnCase, async: false
10 +
11 + import Phoenix.LiveViewTest
12 +
13 + @app :oh_my_ex_oh_ircd
14 +
15 + setup do
16 + saved =
17 + for key <- [OhMyExOhIrcd.IRC.Listener, OhMyExOhIrcd.IRC.TLSListener, :proxy_tls_port],
18 + do: {key, Application.fetch_env(@app, key)}
19 +
20 + on_exit(fn ->
21 + for {key, value} <- saved do
22 + case value do
23 + {:ok, v} -> Application.put_env(@app, key, v)
24 + :error -> Application.delete_env(@app, key)
25 + end
26 + end
27 + end)
28 + end
29 +
30 + defp listener(opts) do
31 + base = Application.get_env(@app, OhMyExOhIrcd.IRC.Listener, [])
32 + Application.put_env(@app, OhMyExOhIrcd.IRC.Listener, Keyword.merge(base, opts))
33 + end
34 +
35 + test "plain only: no TLS line", %{conn: conn} do
36 + listener(proxy_protocol: false)
37 + Application.put_env(@app, OhMyExOhIrcd.IRC.TLSListener, enabled: false)
38 +
39 + {:ok, lv, _} = live(conn, ~p"/")
40 +
41 + refute has_element?(lv, "#connect-tls")
42 + end
43 +
44 + test "behind the proxy: TLS on 6697 by default, and the snippets use it", %{conn: conn} do
45 + listener(proxy_protocol: true)
46 + Application.put_env(@app, OhMyExOhIrcd.IRC.TLSListener, enabled: false)
47 + Application.delete_env(@app, :proxy_tls_port)
48 +
49 + {:ok, lv, html} = live(conn, ~p"/")
50 +
51 + assert has_element?(lv, "#connect-tls", ":6697")
52 + assert has_element?(lv, "#connect-tls", "proxy")
53 + assert html =~ "-tls -tls_verify"
54 + end
55 +
56 + test "behind the proxy: the port IRC_PROXY_TLS_PORT names", %{conn: conn} do
57 + listener(proxy_protocol: true)
58 + Application.put_env(@app, OhMyExOhIrcd.IRC.TLSListener, enabled: false)
59 + Application.put_env(@app, :proxy_tls_port, 7000)
60 +
61 + {:ok, lv, _} = live(conn, ~p"/")
62 +
63 + assert has_element?(lv, "#connect-tls", ":7000")
64 + end
65 +
66 + test "our own TLS listener wins over the proxy", %{conn: conn} do
67 + listener(proxy_protocol: true)
68 + Application.put_env(@app, OhMyExOhIrcd.IRC.TLSListener, enabled: true, port: 6698)
69 +
70 + {:ok, lv, _} = live(conn, ~p"/")
71 +
72 + assert has_element?(lv, "#connect-tls", ":6698")
73 + assert has_element?(lv, "#connect-tls", "ACME")
74 + end
75 +end

Parents: 96a50ad