Add a proxy fallback, and read proxy from env

This commit is contained in:
Bartosz Wieczorek 2025-06-09 13:01:08 +02:00
parent 4d456c56a8
commit 5d163800c3
5 changed files with 110 additions and 34 deletions

View File

@ -1,4 +1,3 @@
#include "rublon/bits.hpp"
#include <security/pam_appl.h> #include <security/pam_appl.h>
#include <security/pam_client.h> #include <security/pam_client.h>
#include <security/pam_ext.h> #include <security/pam_ext.h>
@ -6,6 +5,7 @@
#include <security/pam_modules.h> #include <security/pam_modules.h>
#include <syslog.h> #include <syslog.h>
#include <rublon/bits.hpp>
#include <rublon/check_application.hpp> #include <rublon/check_application.hpp>
#include <rublon/error.hpp> #include <rublon/error.hpp>
#include <rublon/error_handler.hpp> #include <rublon/error_handler.hpp>
@ -17,50 +17,50 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) {
using namespace rublon; using namespace rublon;
details::initLog(); details::initLog();
PamStub pam{}; PamStub pam{};
auto printAuthMessageAndExit = [&](const AuthenticationStatus status) { auto printAuthMessageAndExit = [&](const AuthenticationStatus status) {
switch(status.action()) { switch(status.action()) {
case AuthenticationStatus::Action::Bypass: case AuthenticationStatus::Action::Bypass:
pam.print("RUBLON authentication BYPASSED"); pam.print("RUBLON authentication BYPASSED");
return PAM_SUCCESS; return PAM_SUCCESS;
case AuthenticationStatus::Action::Denied: case AuthenticationStatus::Action::Denied:
pam.print("RUBLON authentication FAILED"); pam.print("RUBLON authentication FAILED");
return PAM_MAXTRIES; return PAM_MAXTRIES;
case AuthenticationStatus::Action::Confirmed: case AuthenticationStatus::Action::Confirmed:
pam.print("RUBLON authentication SUCCEEDED"); pam.print("RUBLON authentication SUCCEEDED");
return PAM_SUCCESS; return PAM_SUCCESS;
} }
pam.print("RUBLON connector has exited with unknown code, access DENY!\n"); pam.print("RUBLON connector has exited with unknown code, access DENY!\n");
return PAM_MAXTRIES; return PAM_MAXTRIES;
}; };
Session session{pam}; Session session{pam};
auto ok = rublon::RublonFactory{}.initializeSession(session); auto ok = rublon::RublonFactory{}.initializeSession(session);
if(not ok.has_value()) { if(not ok.has_value()) {
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass); return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
} }
if(!session.config().logging) { if(!session.config().logging) {
g_level = LogLevel::Warning; g_level = LogLevel::Warning;
} }
CoreHandler_t CH{session.config()}; CoreHandler_t CH{session.config()};
auto selectMethod = [&](const MethodSelect & selector) { // auto selectMethod = [&](const MethodSelect & selector) { //
return selector.create(pam); return selector.create(pam);
}; };
auto confirmMethod = [&](const PostMethod & postMethod) { // auto confirmMethod = [&](const PostMethod & postMethod) { //
return postMethod.handle(CH); return postMethod.handle(CH);
}; };
auto confirmCode = [&](const MethodProxy & method) mutable { // auto confirmCode = [&](const MethodProxy & method) mutable { //
return method.fire(session, CH, pam); return method.fire(session, CH, pam);
}; };
auto finalizeTransaction = [&](const AuthenticationStatus & status) mutable -> tl::expected< AuthenticationStatus, Error > { auto finalizeTransaction = [&](const AuthenticationStatus & status) mutable -> tl::expected< AuthenticationStatus, Error > {
if(status.userAuthorized()) { if(status.userAuthorized()) {
Finish finish{session, status.accessToken()}; Finish finish{session, status.accessToken()};
@ -68,15 +68,15 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) {
} }
return status; return status;
}; };
auto allowLogin = [&](const AuthenticationStatus & status) -> tl::expected< int, Error > { // auto allowLogin = [&](const AuthenticationStatus & status) -> tl::expected< int, Error > { //
return printAuthMessageAndExit(status); return printAuthMessageAndExit(status);
}; };
auto mapError = [&](const Error & error) -> tl::expected< int, Error > { auto mapError = [&](const Error & error) -> tl::expected< int, Error > {
return printAuthMessageAndExit(rublon::ErrorHandler{pam, session.config()}.printErrorDetails(error)); return printAuthMessageAndExit(rublon::ErrorHandler{pam, session.config()}.printErrorDetails(error));
}; };
{ {
CheckApplication ca; CheckApplication ca;
auto ret = ca.call(CH, session.config().systemToken).or_else(mapError); 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; return PAM_MAXTRIES;
} }
} }
auto ret = Init{session} auto ret = Init{session}
.handle(CH, pam) // .handle(CH, pam) //
.and_then(selectMethod) .and_then(selectMethod)
@ -94,6 +94,6 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) {
.and_then(finalizeTransaction) .and_then(finalizeTransaction)
.and_then(allowLogin) .and_then(allowLogin)
.or_else(mapError); .or_else(mapError);
return ret.value_or(PAM_MAXTRIES); return ret.value_or(PAM_MAXTRIES);
} }

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "tl/expected.hpp"
#include <rublon/error.hpp> #include <rublon/error.hpp>
#include <rublon/memory.hpp> #include <rublon/memory.hpp>
#include <rublon/static_string.hpp> #include <rublon/static_string.hpp>
@ -60,7 +61,7 @@ class ConfigurationReader {
memory::MonotonicStack_1k_Resource memoryResource; memory::MonotonicStack_1k_Resource memoryResource;
std::ifstream file(filepath.data()); std::ifstream file(filepath.data());
if(not file.good()) if(not file.good())
return ; return;
std::pmr::string line{&memoryResource}; std::pmr::string line{&memoryResource};
line.reserve(300); line.reserve(300);
@ -74,7 +75,7 @@ class ConfigurationReader {
continue; continue;
auto posEqual = line.find('='); 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}; std::pmr::string value{line.substr(posEqual + 1), &memoryResource};
keyValues[std::move(key)] = std::move(value); keyValues[std::move(key)] = std::move(value);
@ -147,6 +148,64 @@ class ConfigurationReader {
return std::nullopt; 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: /// NOTE:
// getStringOpt can return a valid empty string, for example configuration entry like // getStringOpt can return a valid empty string, for example configuration entry like
// option= // option=
@ -183,6 +242,22 @@ class ConfigurationReader {
config.proxyEnabled = getBool("proxyEnabled").value_or(false); config.proxyEnabled = getBool("proxyEnabled").value_or(false);
config.proxyType = getStringOpt("proxyType"); config.proxyType = getStringOpt("proxyType");
config.proxyServer = getStringOpt("proxyServer"); 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(config.proxyEnabled) {
if(not config.proxyType or config.proxyType->empty()) { if(not config.proxyType or config.proxyType->empty()) {
log(LogLevel::Error, "Proxy is enabled but proxy type is not present or empty"); log(LogLevel::Error, "Proxy is enabled but proxy type is not present or empty");

View File

@ -99,7 +99,7 @@ class CURL {
proxyUrl = *conf().proxyType; proxyUrl = *conf().proxyType;
proxyUrl += "://"; proxyUrl += "://";
proxyUrl += *conf().proxyServer; proxyUrl += *conf().proxyServer;
if(conf().proxyPort > 0) { if(conf().proxyPort.value_or(0) > 0) {
proxyUrl += ":"; proxyUrl += ":";
proxyUrl += std::to_string(*conf().proxyPort); proxyUrl += std::to_string(*conf().proxyPort);
} }

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <charconv>
#include <optional> #include <optional>
#include <rublon/memory.hpp> #include <rublon/memory.hpp>
#include <rublon/static_string.hpp> #include <rublon/static_string.hpp>
@ -149,23 +150,23 @@ namespace conv {
std::transform(userinput.cbegin(), userinput.cend(), buf.data(), asciitolower); std::transform(userinput.cbegin(), userinput.cend(), buf.data(), asciitolower);
return strcmp(buf.data(), "true") == 0; 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 { inline std::optional< std::uint32_t> to_uint32opt(std::string_view userinput) noexcept {
try { int out;
return std::stoi(userinput.data()); const std::from_chars_result result = std::from_chars(userinput.data(), userinput.data() + userinput.size(), out);
} catch(...) { if(result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) {
return std::nullopt; 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 } // namespace conv

View File

@ -93,7 +93,7 @@ class WebSocket {
} }
proxyUrl += *cfg.proxyServer; proxyUrl += *cfg.proxyServer;
if(cfg.proxyPort > 0) { if(cfg.proxyPort.value_or(0) > 0) {
proxyUrl += ":"; proxyUrl += ":";
proxyUrl += std::to_string(*cfg.proxyPort); proxyUrl += std::to_string(*cfg.proxyPort);
} }