From bccd57f47eba2610b7442cf8a1ea99d58039f26a Mon Sep 17 00:00:00 2001 From: Uwe Schuster Date: Sat, 25 Apr 2026 21:39:57 +0200 Subject: [PATCH] =?UTF-8?q?#5:=20add=20IRuntimeConfig::certAuthTrusted()?= =?UTF-8?q?=20=E2=80=94=20gate=20X-SSL-Client-DN=20trust?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New virtual hook on IRuntimeConfig, defaulting to isLoopback() so existing consumers keep their current behaviour. AuthInterceptor now consults certAuthTrusted() (instead of isLoopback() directly) to decide whether to honour an inbound X-SSL-Client-DN header. Operators with an SSH tunnel to a loopback bind, or a non-TLS proxy that forwards X-SSL-Client-DN from untrusted clients, can now override the hook to require additional gating (e.g. an env var, a TLS-only port). Bump to 0.3.5 (additive — no consumer break). Closes #5 Co-Authored-By: Claude Opus 4.7 (1M context) --- CMakeLists.txt | 2 +- .../oatpp-authkit/auth/AuthInterceptor.hpp | 8 ++++--- include/oatpp-authkit/auth/IRuntimeConfig.hpp | 24 ++++++++++++++++++- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0a50c6..6a7f88d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.14) -project(oatpp-authkit VERSION 0.3.4 LANGUAGES CXX) +project(oatpp-authkit VERSION 0.3.5 LANGUAGES CXX) # Header-only interface library — no compilation, just an include path and # a CMake config package so consumers do: diff --git a/include/oatpp-authkit/auth/AuthInterceptor.hpp b/include/oatpp-authkit/auth/AuthInterceptor.hpp index f40fb0b..a3a1eb7 100644 --- a/include/oatpp-authkit/auth/AuthInterceptor.hpp +++ b/include/oatpp-authkit/auth/AuthInterceptor.hpp @@ -27,7 +27,7 @@ using TokenHasher = std::function; * Order of checks: * 1. Public path → pass. * 2. Setup mode (empty users table + policy->setupModeActive()) → pseudo-admin. - * 3. X-SSL-Client-DN header (only trusted when bound to loopback) → cert auth. + * 3. X-SSL-Client-DN header (only trusted when `IRuntimeConfig::certAuthTrusted()`) → cert auth. * 4. Session cookie / Bearer token → backend->resolveBySessionHash / resolveByApiKeyHash. * 5. CSRF defence: sessions reject state-changing requests without X-Requested-With. * 6. Readonly roles cannot mutate. @@ -208,9 +208,11 @@ public: return nullptr; } - // TLS cert DN — only trusted when we're behind a reverse proxy (loopback). + // TLS cert DN — only trusted when the runtime hook says so (#5). + // `certAuthTrusted()` defaults to `isLoopback()`; consumers can override + // it to gate more strictly (e.g. require an env-var or a TLS-only port). auto certDnH = request->getHeader("X-SSL-Client-DN"); - if (m_runtime->isLoopback() && certDnH && !certDnH->empty()) { + if (m_runtime->certAuthTrusted() && certDnH && !certDnH->empty()) { if (auto p = m_backend->resolveByCertDn(std::string(*certDnH))) { writeBundle(request, *p); if (isReadonly(p->role) && isMutation(method)) { diff --git a/include/oatpp-authkit/auth/IRuntimeConfig.hpp b/include/oatpp-authkit/auth/IRuntimeConfig.hpp index 6ece0ff..81a4c4f 100644 --- a/include/oatpp-authkit/auth/IRuntimeConfig.hpp +++ b/include/oatpp-authkit/auth/IRuntimeConfig.hpp @@ -20,11 +20,33 @@ public: /** @brief Host the service is bound to ("127.0.0.1", "::1", "0.0.0.0", ...). */ virtual std::string bindAddress() = 0; - /** @brief Convenience: true iff `bindAddress()` is a loopback literal. */ + /** @brief Convenience: true iff `bindAddress()` is a loopback literal. + * + * Used as the *binding* gate (e.g. trusting `X-Forwarded-For` / `X-Real-IP`). + * For cert-DN trust, prefer `certAuthTrusted()` — operators with an SSH tunnel + * or a misconfigured proxy can forward `X-SSL-Client-DN` from untrusted clients + * even when the service binds to loopback. + */ virtual bool isLoopback() { const std::string a = bindAddress(); return a == "127.0.0.1" || a == "::1" || a == "localhost"; } + + /** @brief Whether incoming `X-SSL-Client-DN` headers should be trusted (#5). + * + * Default: `isLoopback()` — preserves the legacy behaviour for consumers + * that haven't overridden anything. Override to gate more strictly, e.g.: + * + * bool certAuthTrusted() override { + * return isLoopback() && std::getenv("TRUST_CERT_DN") != nullptr; + * } + * + * When this returns `false`, `AuthInterceptor` ignores any inbound + * `X-SSL-Client-DN` header and falls through to token / session auth. + */ + virtual bool certAuthTrusted() { + return isLoopback(); + } }; } // namespace oatpp_authkit