#5: add IRuntimeConfig::certAuthTrusted() — gate X-SSL-Client-DN trust
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) <noreply@anthropic.com>
This commit is contained in:
parent
950012d946
commit
bccd57f47e
3 changed files with 29 additions and 5 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.14)
|
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
|
# Header-only interface library — no compilation, just an include path and
|
||||||
# a CMake config package so consumers do:
|
# a CMake config package so consumers do:
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ using TokenHasher = std::function<std::string(const std::string&)>;
|
||||||
* Order of checks:
|
* Order of checks:
|
||||||
* 1. Public path → pass.
|
* 1. Public path → pass.
|
||||||
* 2. Setup mode (empty users table + policy->setupModeActive()) → pseudo-admin.
|
* 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.
|
* 4. Session cookie / Bearer token → backend->resolveBySessionHash / resolveByApiKeyHash.
|
||||||
* 5. CSRF defence: sessions reject state-changing requests without X-Requested-With.
|
* 5. CSRF defence: sessions reject state-changing requests without X-Requested-With.
|
||||||
* 6. Readonly roles cannot mutate.
|
* 6. Readonly roles cannot mutate.
|
||||||
|
|
@ -208,9 +208,11 @@ public:
|
||||||
return nullptr;
|
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");
|
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))) {
|
if (auto p = m_backend->resolveByCertDn(std::string(*certDnH))) {
|
||||||
writeBundle(request, *p);
|
writeBundle(request, *p);
|
||||||
if (isReadonly(p->role) && isMutation(method)) {
|
if (isReadonly(p->role) && isMutation(method)) {
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,33 @@ public:
|
||||||
/** @brief Host the service is bound to ("127.0.0.1", "::1", "0.0.0.0", ...). */
|
/** @brief Host the service is bound to ("127.0.0.1", "::1", "0.0.0.0", ...). */
|
||||||
virtual std::string bindAddress() = 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() {
|
virtual bool isLoopback() {
|
||||||
const std::string a = bindAddress();
|
const std::string a = bindAddress();
|
||||||
return a == "127.0.0.1" || a == "::1" || a == "localhost";
|
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
|
} // namespace oatpp_authkit
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue