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