From 5d163800c3fe4a71973e1ce93c1a212d6d200049 Mon Sep 17 00:00:00 2001 From: Bartosz Wieczorek Date: Mon, 9 Jun 2025 13:01:08 +0200 Subject: [PATCH] Add a proxy fallback, and read proxy from env --- PAM/ssh/bin/rublon_application.cpp | 34 +++++----- PAM/ssh/include/rublon/configuration.hpp | 79 +++++++++++++++++++++++- PAM/ssh/include/rublon/curl.hpp | 2 +- PAM/ssh/include/rublon/utils.hpp | 27 ++++---- PAM/ssh/include/rublon/websockets.hpp | 2 +- 5 files changed, 110 insertions(+), 34 deletions(-) diff --git a/PAM/ssh/bin/rublon_application.cpp b/PAM/ssh/bin/rublon_application.cpp index 4279918..ce07310 100644 --- a/PAM/ssh/bin/rublon_application.cpp +++ b/PAM/ssh/bin/rublon_application.cpp @@ -1,4 +1,3 @@ -#include "rublon/bits.hpp" #include #include #include @@ -6,6 +5,7 @@ #include #include +#include #include #include #include @@ -17,50 +17,50 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) { using namespace rublon; details::initLog(); PamStub pam{}; - + auto printAuthMessageAndExit = [&](const AuthenticationStatus status) { switch(status.action()) { case AuthenticationStatus::Action::Bypass: pam.print("RUBLON authentication BYPASSED"); return PAM_SUCCESS; - + case AuthenticationStatus::Action::Denied: pam.print("RUBLON authentication FAILED"); return PAM_MAXTRIES; - + case AuthenticationStatus::Action::Confirmed: pam.print("RUBLON authentication SUCCEEDED"); return PAM_SUCCESS; } - + pam.print("RUBLON connector has exited with unknown code, access DENY!\n"); return PAM_MAXTRIES; }; - + Session session{pam}; auto ok = rublon::RublonFactory{}.initializeSession(session); if(not ok.has_value()) { return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass); } - + if(!session.config().logging) { g_level = LogLevel::Warning; } - + CoreHandler_t CH{session.config()}; - + auto selectMethod = [&](const MethodSelect & selector) { // return selector.create(pam); }; - + auto confirmMethod = [&](const PostMethod & postMethod) { // return postMethod.handle(CH); }; - + auto confirmCode = [&](const MethodProxy & method) mutable { // return method.fire(session, CH, pam); }; - + auto finalizeTransaction = [&](const AuthenticationStatus & status) mutable -> tl::expected< AuthenticationStatus, Error > { if(status.userAuthorized()) { Finish finish{session, status.accessToken()}; @@ -68,15 +68,15 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) { } return status; }; - + auto allowLogin = [&](const AuthenticationStatus & status) -> tl::expected< int, Error > { // return printAuthMessageAndExit(status); }; - + auto mapError = [&](const Error & error) -> tl::expected< int, Error > { return printAuthMessageAndExit(rublon::ErrorHandler{pam, session.config()}.printErrorDetails(error)); }; - + { CheckApplication ca; auto ret = ca.call(CH, session.config().systemToken).or_else(mapError); @@ -85,7 +85,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) { return PAM_MAXTRIES; } } - + auto ret = Init{session} .handle(CH, pam) // .and_then(selectMethod) @@ -94,6 +94,6 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) { .and_then(finalizeTransaction) .and_then(allowLogin) .or_else(mapError); - + return ret.value_or(PAM_MAXTRIES); } diff --git a/PAM/ssh/include/rublon/configuration.hpp b/PAM/ssh/include/rublon/configuration.hpp index a987bc7..4622b9d 100644 --- a/PAM/ssh/include/rublon/configuration.hpp +++ b/PAM/ssh/include/rublon/configuration.hpp @@ -1,5 +1,6 @@ #pragma once +#include "tl/expected.hpp" #include #include #include @@ -60,7 +61,7 @@ class ConfigurationReader { memory::MonotonicStack_1k_Resource memoryResource; std::ifstream file(filepath.data()); if(not file.good()) - return ; + return; std::pmr::string line{&memoryResource}; line.reserve(300); @@ -74,7 +75,7 @@ class ConfigurationReader { continue; auto posEqual = line.find('='); - std::pmr::string key {line.substr(0, posEqual),&memoryResource}; + std::pmr::string key{line.substr(0, posEqual), &memoryResource}; std::pmr::string value{line.substr(posEqual + 1), &memoryResource}; keyValues[std::move(key)] = std::move(value); @@ -147,6 +148,64 @@ class ConfigurationReader { return std::nullopt; }; + auto parseProxyURL = [&](const char * envValue) -> bool { + // Very simple parser: scheme://[user[:pass]@]host[:port] + std::string_view url = envValue; + + std::string_view scheme{}; + std::string_view auth{}; + std::string_view hostport{}; + + auto scheme_pos = url.find("://"); + if(scheme_pos != std::string_view::npos) { + scheme = url.substr(0, scheme_pos); + url = url.substr(scheme_pos + 3); + } else { + scheme = "http"; // default + } + + auto at_pos = url.find('@'); + if(at_pos != std::string_view::npos) { + auth = url.substr(0, at_pos); + hostport = url.substr(at_pos + 1); + } else { + hostport = url; + } + + std::string_view host = hostport; + std::string_view port_str{}; + auto colon_pos = hostport.rfind(':'); + if(colon_pos != std::string_view::npos && colon_pos < hostport.size() - 1) { + host = hostport.substr(0, colon_pos); + port_str = hostport.substr(colon_pos + 1); + } + + config.proxyEnabled = true; + config.proxyType = scheme; + config.proxyServer = host; + + if(!port_str.empty()) { + config.proxyPort = conv::to_uint32opt(port_str); + if(not config.proxyPort) { + log(LogLevel::Error, "Invalid proxy port in environment variable"); + return false; + } + } + + if(!auth.empty()) { + auto colon = auth.find(':'); + if(colon != std::string_view::npos) { + config.proxyUsername = auth.substr(0, colon); + config.proxyPass = auth.substr(colon + 1); + } else { + config.proxyUsername = auth; + } + config.proxyAuthRequired = true; + } + + return true; + }; + /// NOTE: // getStringOpt can return a valid empty string, for example configuration entry like // option= @@ -183,6 +242,22 @@ class ConfigurationReader { config.proxyEnabled = getBool("proxyEnabled").value_or(false); config.proxyType = getStringOpt("proxyType"); config.proxyServer = getStringOpt("proxyServer"); + + + // Apply fallback if no config is set + if (config.proxyEnabled && (!config.proxyType || config.proxyType->empty()) && (!config.proxyServer || config.proxyServer->empty())) { + log(LogLevel::Info, "Proxy is enabled but no configuration for it is provided, trying to read from env"); + if (const char* https_proxy = std::getenv("https_proxy"); https_proxy && *https_proxy) { + if (parseProxyURL(https_proxy)) { + log(LogLevel::Info, "Loaded proxy config from HTTPS_PROXY"); + } + } else if (const char* http_proxy = std::getenv("http_proxy"); http_proxy && *http_proxy) { + if (parseProxyURL(http_proxy)) { + log(LogLevel::Info, "Loaded proxy config from HTTP_PROXY"); + } + } + } + if(config.proxyEnabled) { if(not config.proxyType or config.proxyType->empty()) { log(LogLevel::Error, "Proxy is enabled but proxy type is not present or empty"); diff --git a/PAM/ssh/include/rublon/curl.hpp b/PAM/ssh/include/rublon/curl.hpp index 8ace39f..a6add13 100644 --- a/PAM/ssh/include/rublon/curl.hpp +++ b/PAM/ssh/include/rublon/curl.hpp @@ -99,7 +99,7 @@ class CURL { proxyUrl = *conf().proxyType; proxyUrl += "://"; proxyUrl += *conf().proxyServer; - if(conf().proxyPort > 0) { + if(conf().proxyPort.value_or(0) > 0) { proxyUrl += ":"; proxyUrl += std::to_string(*conf().proxyPort); } diff --git a/PAM/ssh/include/rublon/utils.hpp b/PAM/ssh/include/rublon/utils.hpp index 685fd2f..1f36b46 100755 --- a/PAM/ssh/include/rublon/utils.hpp +++ b/PAM/ssh/include/rublon/utils.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -149,23 +150,23 @@ namespace conv { std::transform(userinput.cbegin(), userinput.cend(), buf.data(), asciitolower); return strcmp(buf.data(), "true") == 0; } - - inline tl::expected< std::uint32_t, Error > to_uint32(std::string_view userinput) noexcept { - try { - return std::stoi(userinput.data()); - } catch(const std::invalid_argument & e) { - return tl::make_unexpected(Error::NotANumber); - } catch(const std::out_of_range & e) { - return tl::make_unexpected(Error::OutOfRange); - } - } inline std::optional< std::uint32_t> to_uint32opt(std::string_view userinput) noexcept { - try { - return std::stoi(userinput.data()); - } catch(...) { + int out; + const std::from_chars_result result = std::from_chars(userinput.data(), userinput.data() + userinput.size(), out); + if(result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) { return std::nullopt; } + return out; + } + + inline tl::expected< std::uint32_t, Error > to_uint32(std::string_view userinput) noexcept { + int out; + const std::from_chars_result result = std::from_chars(userinput.data(), userinput.data() + userinput.size(), out); + if(result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) { + return tl::make_unexpected(Error::NotANumber); + } + return out; } } // namespace conv diff --git a/PAM/ssh/include/rublon/websockets.hpp b/PAM/ssh/include/rublon/websockets.hpp index c2807ad..bead9a8 100644 --- a/PAM/ssh/include/rublon/websockets.hpp +++ b/PAM/ssh/include/rublon/websockets.hpp @@ -93,7 +93,7 @@ class WebSocket { } proxyUrl += *cfg.proxyServer; - if(cfg.proxyPort > 0) { + if(cfg.proxyPort.value_or(0) > 0) { proxyUrl += ":"; proxyUrl += std::to_string(*cfg.proxyPort); }