From 238b8b41fae17f00a14cc2a704b5e5bd7d51b4bb Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Mon, 10 Aug 2026 21:55:45 +0200 Subject: [PATCH] fix(joomla): route server-side OIDC discovery through the Tor SOCKS proxy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Joomla's Keycloak plugin builds a jumbojett OpenIDConnectClient, which bottoms out in libcurl — and libcurl refuses a .onion host at name resolution, before it ever consults /etc/hosts, extra_hosts or DNS (RFC 7686). 20ddda8c1 measured that verbatim: `curl --noproxy '*'` exits 6, and even `--resolve auth.:80:127.0.0.1` exits 6. Only a configured proxy makes curl skip the check. So the `onion_oidc_socks.yml.j2` include the role already carries is a no-op for this client: its extra_hosts entry can never be reached. Server-side discovery against an onion IdP could not work, whatever the scheme — this class is unaffected by TLS on the onion, which is why it needs its own fix. buildOidcClient now calls setHttpProxy when JOOMLA_OIDC_SOCKS_PROXY is set, and env.j2 sets it only when the issuer is an .onion, so a clearnet deployment is untouched and never routes discovery through Tor. web-app-nextcloud is the working reference for the same construction. The swarm side needs nothing: the onion_sso overlay is declared provider-side in svc-net-tor/meta/networks.yml and attaches to every app with sso enabled. The six narration comments that fell out of Keycloak.php were pre-existing and lost their grandfathering when the file was touched. Co-Authored-By: Claude Opus 5 (1M context) --- .../plg_system_keycloak/src/Extension/Keycloak.php | 11 +++++------ roles/web-app-joomla/templates/env.j2 | 3 +++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/roles/web-app-joomla/files/joomla-oidc-plugin/plg_system_keycloak/src/Extension/Keycloak.php b/roles/web-app-joomla/files/joomla-oidc-plugin/plg_system_keycloak/src/Extension/Keycloak.php index 1cd7442305..dbac5d8ddf 100644 --- a/roles/web-app-joomla/files/joomla-oidc-plugin/plg_system_keycloak/src/Extension/Keycloak.php +++ b/roles/web-app-joomla/files/joomla-oidc-plugin/plg_system_keycloak/src/Extension/Keycloak.php @@ -36,6 +36,7 @@ class Keycloak extends CMSPlugin private const ENV_CLIENT_ID = 'JOOMLA_OIDC_CLIENT_ID'; private const ENV_CLIENT_SECRET = 'JOOMLA_OIDC_CLIENT_SECRET'; private const ENV_REDIRECT_URI = 'JOOMLA_OIDC_REDIRECT_URI'; + private const ENV_SOCKS_PROXY = 'JOOMLA_OIDC_SOCKS_PROXY'; private const ENV_FALLBACK = 'JOOMLA_OIDC_FALLBACK_ENABLED'; private const ENV_GROUP_ADMIN = 'JOOMLA_OIDC_GROUP_ADMIN'; private const ENV_GROUP_EDITOR = 'JOOMLA_OIDC_GROUP_EDITOR'; @@ -91,12 +92,6 @@ class Keycloak extends CMSPlugin } $input = $this->app->getInput(); $fallbackRequested = (string) $input->getCmd('fallback', '') === 'local'; - // The form-login POST that follows the initial GET does NOT - // carry the `fallback=local` query, so we sticky-bit a - // short-lived cookie when the query is first seen and honour - // it for the next ~10 minutes (covers the local form-login - // round-trip, then either the user is logged in or the - // cookie expires and the IdP gate re-engages). if ($fallbackRequested) { setcookie(self::FALLBACK_COOKIE, '1', [ 'expires' => time() + self::FALLBACK_COOKIE_TTL, @@ -170,6 +165,10 @@ class Keycloak extends CMSPlugin ); } $client = new OpenIDConnectClient($issuer, $clientId, $clientSecret); + $socksProxy = (string) (getenv(self::ENV_SOCKS_PROXY) ?: ''); + if ($socksProxy !== '') { + $client->setHttpProxy($socksProxy); + } $client->setRedirectURL($redirectUri); $client->addScope(['openid', 'profile', 'email', 'groups']); $client->setVerifyHost(true); diff --git a/roles/web-app-joomla/templates/env.j2 b/roles/web-app-joomla/templates/env.j2 index 522f13e69b..c0f6a61203 100644 --- a/roles/web-app-joomla/templates/env.j2 +++ b/roles/web-app-joomla/templates/env.j2 @@ -20,6 +20,9 @@ JOOMLA_OIDC_ISSUER_URL={{ JOOMLA_OIDC.issuer_url }} JOOMLA_OIDC_CLIENT_ID={{ JOOMLA_OIDC.client_id }} JOOMLA_OIDC_CLIENT_SECRET={{ JOOMLA_OIDC.client_secret | dotenv_quote }} JOOMLA_OIDC_REDIRECT_URI={{ JOOMLA_OIDC.redirect_uri }} +{% if JOOMLA_OIDC.issuer_url is search('\.onion(/|:|$)') %} +JOOMLA_OIDC_SOCKS_PROXY={{ lookup('tor_socks_proxy') }} +{% endif %} JOOMLA_OIDC_END_SESSION_URL={{ JOOMLA_OIDC.end_session_url }} JOOMLA_OIDC_FALLBACK_ENABLED={{ JOOMLA_OIDC.fallback_enabled }} JOOMLA_OIDC_GROUP_ADMIN={{ JOOMLA_OIDC.group_admin }}