Compare commits
7 Commits
bwi/e2e_te
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c3c4131c6 | ||
|
|
e13699db43 | ||
|
|
351964199a | ||
| 469b4aaa2d | |||
|
|
de26451445 | ||
|
|
0934902bba | ||
|
|
af64f8e9e3 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -77,3 +77,4 @@ CMakeLists.txt.user*
|
||||
|
||||
# Output files
|
||||
build/
|
||||
_tmp/
|
||||
|
||||
@ -6,8 +6,8 @@ include(CTest)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(PROJECT_VERSION_MAJOR 2)
|
||||
set(PROJECT_VERSION_MINOR 1)
|
||||
set(PROJECT_VERSION_PATCH 0)
|
||||
set(PROJECT_VERSION_MINOR 3)
|
||||
set(PROJECT_VERSION_PATCH 2)
|
||||
set(RUBLON_VERSION_STRING "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
@ -16,6 +16,9 @@ set(CMAKE_CXX_EXTENSIONS NO)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE YES)
|
||||
|
||||
add_compile_options(-Wall -Wextra -Wno-format-security)
|
||||
add_compile_options(-fpermissive)
|
||||
# add_compile_options(-g -fsanitize=address,undefined,float-divide-by-zero,float-cast-overflow,null -fsanitize-address-use-after-scope -fno-sanitize-recover=all -fno-sanitize=alignment -fno-omit-frame-pointer)
|
||||
# add_link_options(-g -fsanitize=address,undefined,float-divide-by-zero,float-cast-overflow,null -fsanitize-address-use-after-scope -fno-sanitize-recover=all -fno-sanitize=alignment -fno-omit-frame-pointer)
|
||||
|
||||
add_compile_definitions(RUBLON_VERSION_STRING="${RUBLON_VERSION_STRING}")
|
||||
|
||||
@ -25,13 +28,16 @@ add_custom_target(INSTSCRIPTS_IDE SUORCES ${CMAKE_CURRENT_LIST_DIR}/service/help
|
||||
|
||||
execute_process (
|
||||
COMMAND bash -c "awk -F= '/^ID=/{print $2}' /etc/os-release |tr -d '\n' | tr -d '\"'"
|
||||
OUTPUT_VARIABLE outOS
|
||||
RESULT_VARIABLE outOS
|
||||
)
|
||||
|
||||
if ( ${outOS} MATCHES "ubuntu" OR ${outOS} MATCHES "debian" OR ${outOS} MATCHES "FREEBSD" )
|
||||
install(
|
||||
FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/rsc/rublon.config.defaults
|
||||
${CMAKE_CURRENT_LIST_DIR}/service/01-rublon-ssh_pubkey.conf.default
|
||||
${CMAKE_CURRENT_LIST_DIR}/service/01-rublon-ssh.conf.default
|
||||
${CMAKE_CURRENT_LIST_DIR}/service/inst_pubkey.sh
|
||||
DESTINATION
|
||||
share/rublon
|
||||
COMPONENT
|
||||
@ -41,15 +47,20 @@ install(
|
||||
OWNER_WRITE
|
||||
GROUP_READ
|
||||
)
|
||||
|
||||
if (NOT ${outOS} MATCHES "ubuntu" OR NOT ${outOS} MATCHES "debian" OR NOT ${outOS} MATCHES "FREEBSD")
|
||||
else ()
|
||||
install(
|
||||
FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/rsc/rublon.config.defaults
|
||||
${CMAKE_CURRENT_LIST_DIR}/service/01-rublon-ssh_pubkey.conf.default
|
||||
${CMAKE_CURRENT_LIST_DIR}/service/01-rublon-ssh.conf.default
|
||||
${CMAKE_CURRENT_LIST_DIR}/service/login_rublon.mod
|
||||
${CMAKE_CURRENT_LIST_DIR}/service/login_rublon.pp
|
||||
${CMAKE_CURRENT_LIST_DIR}/service/login_rublon.te
|
||||
${CMAKE_CURRENT_LIST_DIR}/service/pam_service.txt
|
||||
${CMAKE_CURRENT_LIST_DIR}/service/rublon_veritas
|
||||
${CMAKE_CURRENT_LIST_DIR}/service/inst_pubkey_rhel_9.sh
|
||||
${CMAKE_CURRENT_LIST_DIR}/service/inst_pubkey_rhel_8.sh
|
||||
${CMAKE_CURRENT_LIST_DIR}/service/inst_pubkey.sh
|
||||
DESTINATION
|
||||
share/rublon
|
||||
COMPONENT
|
||||
|
||||
@ -8,8 +8,8 @@ set(INC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler_interface.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/curl.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/error.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/error_handler.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/error.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/finish.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/init.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/json.hpp
|
||||
@ -18,8 +18,8 @@ set(INC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/method_select.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/OTP.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/passcode_based_auth.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/PUSH.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/phone_call.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/PUSH.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/SMS.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/SmsLink.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/websocket_based_auth.hpp
|
||||
@ -27,6 +27,7 @@ set(INC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/non_owning_ptr.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/pam_action.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/pam.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/pam_stub.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/rublon.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/session.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/sign.hpp
|
||||
@ -79,6 +80,7 @@ set(LWS_UNIX_SOCK OFF)
|
||||
|
||||
set(LWS_WITH_DIR OFF)
|
||||
set(LWS_WITH_FILE_OPS OFF)
|
||||
set(LWS_FOR_GITOHASHI OFF)
|
||||
set(LWS_WITH_HTTP2 OFF)
|
||||
set(LWS_WITH_HTTP_BASIC_AUTH OFF)
|
||||
set(LWS_WITH_JPEG OFF)
|
||||
@ -94,6 +96,9 @@ set(LWS_WITH_SECURE_STREAMS OFF)
|
||||
set(LWS_WITH_WOL OFF)
|
||||
set(LWS_WITH_UPNG OFF)
|
||||
set(LWS_WITH_UDP OFF)
|
||||
set(LWS_WITH_HTTP_STREAM_COMPRESSION OFF)
|
||||
set(LWS_WITH_HTTP_BROTLI OFF)
|
||||
set(LWS_WITH_ZLIB OFF)
|
||||
|
||||
set(RAPIDJSON_BUILD_DOC OFF CACHE BOOL "" FORCE)
|
||||
set(RAPIDJSON_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
@ -114,10 +119,17 @@ FetchContent_Declare(
|
||||
|
||||
FetchContent_MakeAvailable(libwebsockets)
|
||||
|
||||
|
||||
set(RAPIDJSON_BUILD_DOC OFF)
|
||||
set(RAPIDJSON_BUILD_EXAMPLES OFF)
|
||||
set(RAPIDJSON_BUILD_TESTS OFF)
|
||||
set(RAPIDJSON_HAS_STDSTRING OFF)
|
||||
|
||||
FetchContent_Declare(
|
||||
RapidJSON
|
||||
URL https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.zip
|
||||
URL_HASH MD5=ceb1cf16e693a3170c173dc040a9d2bd
|
||||
PATCH_COMMAND patch -p1 < ${CMAKE_CURRENT_LIST_DIR}/patches/rapidjson.patch
|
||||
)
|
||||
|
||||
if(NOT RapidJSON_POPULATED)
|
||||
@ -128,6 +140,6 @@ endif()
|
||||
add_subdirectory(lib)
|
||||
add_subdirectory(bin)
|
||||
|
||||
# if(${ENABLE_TESTS})
|
||||
# if(${ENABLE_TESTS}):
|
||||
# add_subdirectory(tests)
|
||||
# endif()
|
||||
|
||||
@ -1,10 +1,4 @@
|
||||
#include <security/pam_appl.h>
|
||||
#include <security/pam_client.h>
|
||||
#include <security/pam_ext.h>
|
||||
#include <security/pam_misc.h>
|
||||
#include <security/pam_modules.h>
|
||||
#include <syslog.h>
|
||||
|
||||
#include <rublon/bits.hpp>
|
||||
#include <rublon/check_application.hpp>
|
||||
#include <rublon/error.hpp>
|
||||
#include <rublon/error_handler.hpp>
|
||||
@ -12,7 +6,7 @@
|
||||
#include <rublon/rublon.hpp>
|
||||
#include <rublon/utils.hpp>
|
||||
|
||||
int main([[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) {
|
||||
int main(int argc, const char ** argv) {
|
||||
using namespace rublon;
|
||||
details::initLog();
|
||||
PamStub pam{};
|
||||
@ -36,16 +30,17 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) {
|
||||
return PAM_MAXTRIES;
|
||||
};
|
||||
|
||||
auto session = rublon::RublonFactory{}.startSession(pam);
|
||||
if(not session.has_value()) {
|
||||
Session session{pam};
|
||||
auto ok = rublon::RublonFactory{}.initializeSession(session, 0, argc, argv);
|
||||
if(not ok.has_value()) {
|
||||
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
|
||||
}
|
||||
|
||||
if(!session->config().logging) {
|
||||
if(!session.config().logging) {
|
||||
g_level = LogLevel::Warning;
|
||||
}
|
||||
|
||||
auto & CH = session.value().coreHandler();
|
||||
CoreHandler_t CH{session.config()};
|
||||
|
||||
auto selectMethod = [&](const MethodSelect & selector) { //
|
||||
return selector.create(pam);
|
||||
@ -56,13 +51,12 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) {
|
||||
};
|
||||
|
||||
auto confirmCode = [&](const MethodProxy & method) mutable { //
|
||||
return method.fire(session.value(), CH, pam);
|
||||
return method.fire(session, CH, pam);
|
||||
};
|
||||
|
||||
auto finalizeTransaction = [&](const AuthenticationStatus & status) mutable -> tl::expected< AuthenticationStatus, Error > {
|
||||
if(status.userAuthorized()) {
|
||||
auto tok = std::string{status.accessToken().data()};
|
||||
Finish finish{session.value(), std::move(tok)};
|
||||
Finish finish{session, status.accessToken()};
|
||||
finish.handle(CH);
|
||||
}
|
||||
return status;
|
||||
@ -73,20 +67,19 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) {
|
||||
};
|
||||
|
||||
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;
|
||||
auto ret =
|
||||
ca.call(CH, {session.value().config().systemToken.data(), session.value().config().systemToken.size()}).or_else(mapError);
|
||||
CheckApplication ca{session};
|
||||
auto ret = ca.call(CH, session.config().systemToken).or_else(mapError);
|
||||
if(not ret.has_value()) {
|
||||
log(LogLevel::Error, "Check Application step failed, check configration");
|
||||
return PAM_MAXTRIES;
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = Init{session.value()}
|
||||
auto ret = Init{session}
|
||||
.handle(CH, pam) //
|
||||
.and_then(selectMethod)
|
||||
.and_then(confirmMethod)
|
||||
|
||||
@ -29,6 +29,17 @@ class AuthenticationStep {
|
||||
auto & alloc = body.GetAllocator();
|
||||
body.AddMember("accessToken", Value{token.data(), static_cast< unsigned >(token.length()), alloc}, alloc);
|
||||
}
|
||||
|
||||
template < typename Hander_t >
|
||||
tl::expected< AuthenticationStatus, Error > waitForCoreConfirmation(const CoreHandlerInterface< Hander_t > & eventListener) const {
|
||||
auto event = eventListener.listen();
|
||||
if(event.status == transactionConfirmed ){
|
||||
log(LogLevel::Debug, " Transaction confirmed");
|
||||
return AuthenticationStatus{AuthenticationStatus::Action::Confirmed, event.accessToken.value().c_str()};
|
||||
}
|
||||
log(LogLevel::Debug, " Transaction denied");
|
||||
return AuthenticationStatus{AuthenticationStatus::Action::Denied};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace rublon
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <rublon/configuration.hpp>
|
||||
#include <rublon/session.hpp>
|
||||
#include <rublon/bits.hpp>
|
||||
#include <rublon/core_handler.hpp>
|
||||
#include <rublon/curl.hpp>
|
||||
@ -8,15 +10,18 @@
|
||||
#include <rublon/memory.hpp>
|
||||
#include <rublon/utils.hpp>
|
||||
|
||||
#include <rapidjson/prettywriter.h>
|
||||
#include <rapidjson/rapidjson.h>
|
||||
|
||||
namespace rublon {
|
||||
|
||||
struct closefile_deleter {
|
||||
void operator()(FILE * f) const {
|
||||
pclose(f);
|
||||
}
|
||||
};
|
||||
|
||||
std::string exec(const char * cmd) {
|
||||
std::array< char, 128 > buffer;
|
||||
std::string result;
|
||||
std::unique_ptr< FILE, decltype(&pclose) > pipe(popen(cmd, "r"), pclose);
|
||||
std::unique_ptr< FILE, closefile_deleter > pipe(popen(cmd, "r"));
|
||||
if(!pipe) {
|
||||
return "";
|
||||
}
|
||||
@ -48,6 +53,7 @@ class Status {
|
||||
std::string_view _appTypeKey = "/type";
|
||||
std::string_view _paramSystemName = "/params/os";
|
||||
std::string_view _paramSSHDBase = "/params/sshd_config/";
|
||||
std::string_view _configBase = "/config/";
|
||||
|
||||
public:
|
||||
Status() : _alloc{}, _data{&_alloc}, _statusUpdated{false} {
|
||||
@ -59,7 +65,7 @@ class Status {
|
||||
|
||||
std::ifstream ifs{_statusFilePath.data()};
|
||||
if(!ifs.is_open()) {
|
||||
/// TODO handle no file error
|
||||
return;
|
||||
}
|
||||
|
||||
rapidjson::IStreamWrapper isw{ifs};
|
||||
@ -67,7 +73,7 @@ class Status {
|
||||
}
|
||||
|
||||
void updateAppVersion(std::string_view newVersion) {
|
||||
RapidJSONPMRStackAlloc< 128 > stackAlloc;
|
||||
RapidJSONPMRStackAlloc< 512 > stackAlloc;
|
||||
auto jsonPointer = JSONPointer{_appVersionKey.data(), &stackAlloc};
|
||||
auto version = jsonPointer.Get(_data);
|
||||
if(not version || version->GetString() != newVersion) {
|
||||
@ -77,7 +83,7 @@ class Status {
|
||||
}
|
||||
|
||||
void updateSystemVersion(std::string_view system) {
|
||||
RapidJSONPMRStackAlloc< 128 > stackAlloc;
|
||||
RapidJSONPMRStackAlloc< 512 > stackAlloc;
|
||||
auto jsonPointer = JSONPointer{_paramSystemName.data(), &stackAlloc};
|
||||
auto version = jsonPointer.Get(_data);
|
||||
if(not version || version->GetString() != system) {
|
||||
@ -88,7 +94,7 @@ class Status {
|
||||
|
||||
void updateSSHDConfig() {
|
||||
using namespace std::string_view_literals;
|
||||
constexpr auto keys = make_array("authenticationmethods"sv,
|
||||
constexpr auto keys = make_array< std::string_view >("authenticationmethods"sv,
|
||||
"challengeresponseauthentication"sv,
|
||||
"kbdinteractiveauthentication"sv,
|
||||
"logingracetime"sv,
|
||||
@ -119,6 +125,85 @@ class Status {
|
||||
}
|
||||
}
|
||||
|
||||
template < typename T >
|
||||
void updateRublonConfigParameter(std::string_view name, const T & newParam) {
|
||||
memory::MonotonicStack_1k_Resource memoryResource{};
|
||||
RapidJSONPMRAlloc stackAlloc{&memoryResource};
|
||||
|
||||
std::pmr::string jsonPath{&memoryResource};
|
||||
jsonPath += _configBase;
|
||||
jsonPath += name;
|
||||
|
||||
JSONPointer jsonPointer{jsonPath.c_str(), &stackAlloc};
|
||||
auto * param = jsonPointer.Get(_data);
|
||||
|
||||
Document::AllocatorType & alloc = _data.GetAllocator();
|
||||
Value newValue;
|
||||
|
||||
if constexpr(std::is_same_v< T, std::pmr::string > || std::is_same_v< T, std::string_view >) {
|
||||
newValue.SetString(newParam.data(), static_cast< rapidjson::SizeType >(newParam.size()), alloc);
|
||||
if(!param || !param->IsString() || param->GetString() != newParam) {
|
||||
markUpdated();
|
||||
jsonPointer.Set(_data, newValue);
|
||||
}
|
||||
} else if constexpr(std::is_same_v< T, bool >) {
|
||||
newValue.SetBool(newParam);
|
||||
if(!param || !param->IsBool() || param->GetBool() != newParam) {
|
||||
markUpdated();
|
||||
jsonPointer.Set(_data, newValue);
|
||||
}
|
||||
} else if constexpr(std::is_integral_v< T >) {
|
||||
newValue.SetInt(static_cast< int >(newParam));
|
||||
if(!param || !param->IsInt() || param->GetInt() != static_cast< int >(newParam)) {
|
||||
markUpdated();
|
||||
jsonPointer.Set(_data, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For std::optional<T>
|
||||
template < typename T >
|
||||
void updateRublonConfigParameter(std::string_view name, const std::optional< T > & optParam) {
|
||||
memory::MonotonicStack_1k_Resource memoryResource{};
|
||||
RapidJSONPMRAlloc stackAlloc{&memoryResource};
|
||||
|
||||
std::pmr::string fullPath{&memoryResource};
|
||||
fullPath += _configBase;
|
||||
fullPath += name;
|
||||
|
||||
JSONPointer jsonPointer{fullPath.c_str(), &stackAlloc};
|
||||
Value* existing = jsonPointer.Get(_data);
|
||||
|
||||
if (optParam.has_value()) {
|
||||
// Delegate to regular (non-optional) setter
|
||||
updateRublonConfigParameter(name, *optParam);
|
||||
} else {
|
||||
// Set the entire field to `null` if no value
|
||||
if (!existing || !existing->IsNull()) {
|
||||
markUpdated();
|
||||
Value nullValue(rapidjson::kNullType);
|
||||
jsonPointer.Set(_data, nullValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateRublonConfig(const Configuration & config) {
|
||||
updateRublonConfigParameter("prompt", config.prompt);
|
||||
updateRublonConfigParameter("logging", config.logging);
|
||||
updateRublonConfigParameter("autopushPrompt", config.autopushPrompt);
|
||||
updateRublonConfigParameter("failMode", static_cast<int>(config.failMode));
|
||||
updateRublonConfigParameter("nonInteractiveMode", config.nonInteractiveMode);
|
||||
|
||||
updateRublonConfigParameter("proxyType", config.proxyType);
|
||||
updateRublonConfigParameter("proxyHost", config.proxyHost);
|
||||
updateRublonConfigParameter("proxyUsername", config.proxyUsername);
|
||||
updateRublonConfigParameter("proxyPassword", config.proxyPass);
|
||||
updateRublonConfigParameter("proxyPort", config.proxyPort);
|
||||
|
||||
updateRublonConfigParameter("proxyAuthRequired", config.proxyAuthRequired);
|
||||
updateRublonConfigParameter("proxyEnabled", config.proxyEnabled);
|
||||
}
|
||||
|
||||
void markUpdated() {
|
||||
_statusUpdated = true;
|
||||
}
|
||||
@ -129,7 +214,7 @@ class Status {
|
||||
|
||||
void save() {
|
||||
if(updated()) {
|
||||
memory::Monotonic_8k_HeapResource tmpResource;
|
||||
memory::Monotonic_8k_Resource tmpResource;
|
||||
RapidJSONPMRAlloc alloc{&tmpResource};
|
||||
FileWriter s{_statusFilePath};
|
||||
rapidjson::PrettyWriter< FileWriter, rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc > writer{s, &alloc};
|
||||
@ -140,7 +225,7 @@ class Status {
|
||||
|
||||
std::string print() {
|
||||
std::string result;
|
||||
memory::Monotonic_8k_HeapResource tmpResource;
|
||||
memory::Monotonic_8k_Resource tmpResource;
|
||||
RapidJSONPMRAlloc alloc{&tmpResource};
|
||||
StringWriter s{result};
|
||||
rapidjson::PrettyWriter< StringWriter< std::string >, rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc > writer{s, &alloc};
|
||||
@ -156,6 +241,7 @@ class Status {
|
||||
};
|
||||
|
||||
class CheckApplication {
|
||||
const Session & _sesion;
|
||||
tl::expected< bool, Error > persistStatus(Status & status) const {
|
||||
status.data().RemoveMember("systemToken");
|
||||
status.save();
|
||||
@ -163,9 +249,11 @@ class CheckApplication {
|
||||
}
|
||||
|
||||
public:
|
||||
CheckApplication(const Session & session) : _sesion{session} {}
|
||||
|
||||
tl::expected< int, Error > call(const CoreHandler_t & coreHandler, std::string_view systemToken) const {
|
||||
memory::Monotonic_1k_HeapResource mr;
|
||||
RapidJSONPMRStackAlloc< 2048 > alloc{};
|
||||
memory::MonotonicStack_2k_Resource mr;
|
||||
RapidJSONPMRAlloc alloc{&mr};
|
||||
constexpr std::string_view api = "/api/app/init";
|
||||
Status status;
|
||||
|
||||
@ -174,6 +262,7 @@ class CheckApplication {
|
||||
status.updateAppVersion(RUBLON_VERSION_STRING);
|
||||
status.updateSystemVersion(details::osName(&mr));
|
||||
status.updateSSHDConfig();
|
||||
status.updateRublonConfig(_sesion.config());
|
||||
|
||||
if(status.updated()) {
|
||||
auto & alloc = status.data().GetAllocator();
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <rublon/error.hpp>
|
||||
#include <rublon/memory.hpp>
|
||||
#include <rublon/static_string.hpp>
|
||||
#include <rublon/utils.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
template < typename T >
|
||||
constexpr bool is_static_string_v = std::is_base_of_v< rublon::details::StaticStringBase, T >;
|
||||
|
||||
static_assert(is_static_string_v< rublon::StaticString< 32 > >);
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
|
||||
namespace rublon {
|
||||
class ConfigurationFactory;
|
||||
@ -16,181 +15,324 @@ class ConfigurationFactory;
|
||||
enum class FailMode { bypass, deny };
|
||||
|
||||
class Configuration {
|
||||
private:
|
||||
std::pmr::memory_resource * memoryResource;
|
||||
|
||||
public:
|
||||
Configuration(std::pmr::memory_resource * mr) : memoryResource{mr} {}
|
||||
|
||||
// change to StaticString
|
||||
StaticString< 32 > systemToken{};
|
||||
StaticString< 32 > secretKey{};
|
||||
StaticString< 4096 > apiServer{};
|
||||
std::pmr::string systemToken{memoryResource};
|
||||
std::pmr::string secretKey{memoryResource};
|
||||
std::pmr::string apiServer{memoryResource};
|
||||
|
||||
int prompt{};
|
||||
bool enablePasswdEmail{};
|
||||
bool enablePasswdEmail{}; // obsolete
|
||||
bool logging{};
|
||||
bool autopushPrompt{};
|
||||
FailMode failMode{};
|
||||
bool nonInteractiveMode{};
|
||||
|
||||
std::optional< std::pmr::string > proxyType{memoryResource};
|
||||
std::optional< std::pmr::string > proxyHost{memoryResource};
|
||||
std::optional< std::pmr::string > proxyUsername{memoryResource};
|
||||
std::optional< std::pmr::string > proxyPass{memoryResource};
|
||||
std::optional< int > proxyPort{};
|
||||
bool proxyAuthRequired{}; // defaulted
|
||||
bool proxyEnabled{}; // defaulted
|
||||
};
|
||||
|
||||
namespace {
|
||||
template < class C, typename T >
|
||||
T member_ptr_t(T C::*v);
|
||||
|
||||
template < typename T >
|
||||
tl::expected< T, ConfigurationError > to(std::string_view);
|
||||
|
||||
template < class T >
|
||||
auto to_string(std::string_view arg) -> tl::expected< T, ConfigurationError > {
|
||||
T value{};
|
||||
assert(arg.size() <= (value.size() - 1));
|
||||
std::memcpy(value.data(), arg.data(), arg.size());
|
||||
return value;
|
||||
}
|
||||
|
||||
template <>
|
||||
auto to(std::string_view arg) -> tl::expected< bool, ConfigurationError > {
|
||||
return conv::to_bool(arg);
|
||||
}
|
||||
template <>
|
||||
auto to(std::string_view arg) -> tl::expected< int, ConfigurationError > {
|
||||
return conv::to_uint32(arg).value_or(0);
|
||||
}
|
||||
|
||||
template <>
|
||||
auto to(std::string_view arg) -> tl::expected< FailMode, ConfigurationError > {
|
||||
if(arg == "bypass")
|
||||
return FailMode::bypass;
|
||||
if(arg == "deny")
|
||||
return FailMode::deny;
|
||||
return tl::unexpected{ConfigurationError{ConfigurationError::ErrorClass::BadFailMode}};
|
||||
}
|
||||
|
||||
template < typename T >
|
||||
auto parse(std::string_view arg) -> tl::expected< T, ConfigurationError > {
|
||||
if(arg.empty()) {
|
||||
return tl::unexpected{ConfigurationError::ErrorClass::Empty};
|
||||
} else {
|
||||
if constexpr(is_static_string_v< T >) {
|
||||
return to_string< T >(arg);
|
||||
} else {
|
||||
return to< T >(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
struct Entry {
|
||||
enum class Source { UserInput, DefaultValue };
|
||||
template < auto member >
|
||||
static constexpr auto make_read_function() {
|
||||
using pType = decltype(member_ptr_t(member));
|
||||
|
||||
return
|
||||
[](const Entry * _this, Configuration * configuration, std::string_view userInput) -> tl::expected< Source, ConfigurationError > {
|
||||
const auto setDefaultValue = [&](const ConfigurationError & error) -> tl::expected< Source, ConfigurationError > {
|
||||
log(LogLevel::Warning, "applying user provided value for %s parameter, faild with %s", _this->name, error.what());
|
||||
if(_this->defaultValue != nullptr) {
|
||||
configuration->*member = parse< pType >(_this->defaultValue).value();
|
||||
return Source::DefaultValue;
|
||||
} else {
|
||||
log(LogLevel::Error, "parameter %s has not been found and has no default value", _this->name);
|
||||
if(userInput.empty())
|
||||
return tl::unexpected{ConfigurationError::ErrorClass::RequiredValueNotFound};
|
||||
else
|
||||
return tl::unexpected{ConfigurationError::ErrorClass::BadInput};
|
||||
}
|
||||
};
|
||||
|
||||
const auto saveValue = [&](const auto & value) -> tl::expected< Source, ConfigurationError > {
|
||||
configuration->*member = value;
|
||||
return Source::UserInput;
|
||||
};
|
||||
|
||||
return parse< pType >(userInput).and_then(saveValue).or_else(setDefaultValue);
|
||||
};
|
||||
}
|
||||
|
||||
const char * name;
|
||||
const char * defaultValue;
|
||||
tl::expected< Source, ConfigurationError > (*_read)(const Entry * _this, Configuration * configuration, std::string_view userInput);
|
||||
|
||||
bool read(Configuration * configuration, std::optional< std::string_view > userInput) const {
|
||||
constexpr const auto emptyString = "";
|
||||
const auto logStored = [&](const auto & source) -> tl::expected< Source, ConfigurationError > {
|
||||
rublon::log(LogLevel::Debug,
|
||||
"Configuration parameter '%s' was set to '%s'%s",
|
||||
this->name,
|
||||
this->defaultValue,
|
||||
source == Source::DefaultValue ? " (default)" : "");
|
||||
return source;
|
||||
};
|
||||
|
||||
const auto logError = [&](const auto & error) -> tl::expected< Source, ConfigurationError > {
|
||||
rublon::log(LogLevel::Error,
|
||||
"Configuration parameter '%s' has no default value and is not provided in user configuraion, aborting",
|
||||
this->name);
|
||||
return tl::unexpected{error};
|
||||
};
|
||||
|
||||
return _read(this, configuration, userInput.value_or(emptyString)).and_then(logStored).or_else(logError).has_value();
|
||||
}
|
||||
};
|
||||
|
||||
template < auto member >
|
||||
constexpr auto make_entry(const char * name, const char * defaultValue) {
|
||||
return Entry{name, defaultValue, Entry::make_read_function< member >()};
|
||||
}
|
||||
|
||||
constexpr static inline std::array< Entry, 8 > configurationVariables = { //
|
||||
make_entry< &Configuration::logging >("logging", "true"),
|
||||
make_entry< &Configuration::systemToken >("systemToken", nullptr),
|
||||
make_entry< &Configuration::secretKey >("secretKey", nullptr),
|
||||
make_entry< &Configuration::apiServer >("rublonApiServer", nullptr),
|
||||
make_entry< &Configuration::prompt >("prompt", "1"),
|
||||
make_entry< &Configuration::enablePasswdEmail >("enablePasswdEmail", "true"),
|
||||
make_entry< &Configuration::autopushPrompt >("autopushPrompt", "false"),
|
||||
make_entry< &Configuration::failMode >("failMode", "deny")};
|
||||
|
||||
class ConfigurationFactory {
|
||||
class ConfigurationReader {
|
||||
public:
|
||||
ConfigurationFactory() = default;
|
||||
ConfigurationReader(std::pmr::memory_resource * memResource, std::string_view filepath, int argc, const char **argv) : _memoryResource(memResource) {
|
||||
loadFromFile(filepath);
|
||||
loadFromArgv(argc, argv);
|
||||
}
|
||||
|
||||
std::optional< Configuration > systemConfig() {
|
||||
memory::MonotonicStackResource< 8 * 1024 > stackResource;
|
||||
Configuration configuration{};
|
||||
|
||||
std::ifstream file(std::filesystem::path{"/etc/rublon.config"});
|
||||
// Load config from file path
|
||||
void loadFromFile(std::string_view filepath) {
|
||||
using namespace memory::literals;
|
||||
memory::MonotonicStack_1k_Resource memoryResource;
|
||||
std::ifstream file(filepath.data());
|
||||
if(not file.good())
|
||||
return std::nullopt;
|
||||
return;
|
||||
|
||||
std::pmr::string line{&stackResource};
|
||||
line.reserve(100);
|
||||
std::pmr::map< std::pmr::string, std::pmr::string > parameters{&stackResource};
|
||||
|
||||
const auto readParameterByName = [&](std::string_view name) -> std::optional< std::string_view > {
|
||||
return parameters.count(name.data()) ? std::optional< std::string_view >{parameters.at(name.data())} : std::nullopt;
|
||||
};
|
||||
std::pmr::string line{&memoryResource};
|
||||
line.reserve(300);
|
||||
|
||||
while(std::getline(file, line)) {
|
||||
std::pmr::string key{&stackResource};
|
||||
std::pmr::string value{&stackResource};
|
||||
|
||||
if(!line.length())
|
||||
continue;
|
||||
|
||||
if(line[0] == '#' || line[0] == ';')
|
||||
continue;
|
||||
|
||||
auto posEqual = line.find('=');
|
||||
key = line.substr(0, posEqual);
|
||||
value = line.substr(posEqual + 1);
|
||||
|
||||
parameters[std::move(key)] = std::move(value);
|
||||
parseLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto & entry : configurationVariables) {
|
||||
if(not entry.read(&configuration, readParameterByName(entry.name)))
|
||||
void loadFromArgv(int argc, const char **argv) {
|
||||
using namespace memory::literals;
|
||||
|
||||
for(int i = 0; i < argc; ++i) {
|
||||
parseLine(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void parseLine(std::string_view line) {
|
||||
line = details::trim(line);
|
||||
|
||||
if (line.empty())
|
||||
return;
|
||||
|
||||
if (line.front() == '#' || line.front() == ';')
|
||||
return;
|
||||
|
||||
auto posEqual = line.find('=');
|
||||
if (posEqual == std::string::npos)
|
||||
return;
|
||||
|
||||
std::pmr::string key{line.substr(0, posEqual), _memoryResource};
|
||||
std::pmr::string value{line.substr(posEqual + 1), _memoryResource};
|
||||
|
||||
details::trimInPlace(key);
|
||||
details::trimInPlace(value);
|
||||
|
||||
if (auto it = _keyValues.find(key); it != _keyValues.end()) {
|
||||
log(LogLevel::Debug, "Overwriting existing key: %s (old value: %s) with %s", key.c_str(), it->second.c_str(), value.c_str());
|
||||
}
|
||||
|
||||
_keyValues[std::move(key)] = std::move(value);
|
||||
}
|
||||
|
||||
// Load values into Configuration object, with defaults provided
|
||||
tl::expected< bool, ConfigurationError > applyTo(Configuration & config) const {
|
||||
using string = std::pmr::string;
|
||||
|
||||
auto logRequiredFieldNotAvailable = [](auto fieldname) { log(LogLevel::Error, "%s field is not set", fieldname); };
|
||||
|
||||
// Helper lambdas for conversion
|
||||
auto getStringOpt = [&](const string & key) -> std::optional< std::pmr::string > {
|
||||
auto it = _keyValues.find(key);
|
||||
if(it == _keyValues.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return configuration;
|
||||
}
|
||||
return string{it->second.data(), it->second.size(), _memoryResource};
|
||||
};
|
||||
|
||||
auto getStringReq = [&](const string & key) -> tl::expected< std::pmr::string, ConfigurationError > {
|
||||
auto val = getStringOpt(key);
|
||||
if(val.has_value()) {
|
||||
if(val->empty()) {
|
||||
return tl::unexpected{ConfigurationError::ErrorClass::RequiredValueEmpty};
|
||||
}
|
||||
return std::move(val.value());
|
||||
}
|
||||
return tl::unexpected{ConfigurationError::ErrorClass::RequiredValueNotFound};
|
||||
};
|
||||
|
||||
auto getInt = [&](const string & key) -> std::optional< int > {
|
||||
auto it = _keyValues.find(key);
|
||||
if(it == _keyValues.end())
|
||||
return std::nullopt;
|
||||
return conv::to_uint32opt(it->second);
|
||||
};
|
||||
|
||||
auto getBool = [&](const string & key) -> std::optional< bool > {
|
||||
memory::MonotonicStackResource< 32 > memoryResource;
|
||||
auto it = _keyValues.find(key);
|
||||
if(it == _keyValues.end())
|
||||
return std::nullopt;
|
||||
|
||||
if(it->second.size() > 5) {
|
||||
log(LogLevel::Warning, "Configuration value %s is too long, please check", key.c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::pmr::string val{&memoryResource};
|
||||
val = it->second;
|
||||
std::transform(val.begin(), val.end(), val.begin(), [](auto c) { return std::tolower(c); });
|
||||
|
||||
if(val == "1" || val == "true" || val == "yes" || val == "on")
|
||||
return true;
|
||||
if(val == "0" || val == "false" || val == "no" || val == "off")
|
||||
return false;
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
auto getFailMode = [&](const string & key) -> std::optional< FailMode > {
|
||||
auto it = _keyValues.find(key);
|
||||
if(it == _keyValues.end())
|
||||
return std::nullopt;
|
||||
auto val = it->second;
|
||||
std::transform(val.begin(), val.end(), val.begin(), [](auto c) { return std::tolower(c); });
|
||||
if(val == "bypass")
|
||||
return FailMode::bypass;
|
||||
if(val == "deny")
|
||||
return FailMode::deny;
|
||||
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
auto parseProxyURL = [&](std::string_view url) -> bool {
|
||||
// Very simple parser: scheme://[user[:pass]@]host[:port]
|
||||
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.proxyHost = 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;
|
||||
};
|
||||
|
||||
auto toLowerCaseOpt = [](auto & str) {
|
||||
if(str)
|
||||
std::transform(str->cbegin(), str->cend(), str->begin(), [](auto c) { return std::tolower(c); });
|
||||
};
|
||||
|
||||
/// NOTE:
|
||||
// getStringOpt can return a valid empty string, for example configuration entry like
|
||||
// option=
|
||||
// will return a optional<string> val which contains empty string.
|
||||
// getStringReq on the other hand, returns error in case when
|
||||
// * configuration is not found -> RequiredValueNotFound
|
||||
// * configuration value is empty -> RequiredValueEmpty
|
||||
|
||||
// Reading required fields
|
||||
if(auto val = getStringReq("systemToken"); not val.has_value()) {
|
||||
logRequiredFieldNotAvailable("systemToken");
|
||||
return tl::unexpected{val.error()};
|
||||
} else {
|
||||
config.systemToken = std::move(val.value());
|
||||
}
|
||||
|
||||
if(auto val = getStringReq("secretKey"); not val.has_value()) {
|
||||
logRequiredFieldNotAvailable("secretKey");
|
||||
return tl::unexpected{val.error()};
|
||||
} else {
|
||||
config.secretKey = std::move(val.value());
|
||||
}
|
||||
|
||||
if(auto val = getStringReq("rublonApiServer"); not val.has_value()) {
|
||||
logRequiredFieldNotAvailable("rublonApiServer");
|
||||
return tl::unexpected{val.error()};
|
||||
} else {
|
||||
config.apiServer = std::move(val.value());
|
||||
}
|
||||
|
||||
// optional configuration options
|
||||
config.prompt = getInt("prompt").value_or(1);
|
||||
config.enablePasswdEmail = getBool("enablePasswdEmail").value_or(true);
|
||||
config.logging = getBool("logging").value_or(true);
|
||||
config.autopushPrompt = getBool("autopushPrompt").value_or(false);
|
||||
config.nonInteractiveMode = getBool("nonInteractiveMode").value_or(false);
|
||||
config.failMode = getFailMode("failMode").value_or(FailMode::deny);
|
||||
|
||||
// reading proxy configuration
|
||||
config.proxyEnabled = getBool("proxyEnabled").value_or(false);
|
||||
config.proxyType = getStringOpt("proxyType");
|
||||
config.proxyHost = getStringOpt("proxyHost");
|
||||
|
||||
// Apply fallback if no config is set
|
||||
if(config.proxyEnabled && (!config.proxyType || config.proxyType->empty()) && (!config.proxyHost || config.proxyHost->empty())) {
|
||||
log(LogLevel::Info, "Proxy is enabled but no configuration for it is provided, trying to read from env");
|
||||
if(auto 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(auto 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");
|
||||
return tl::unexpected{ConfigurationError::ErrorClass::BadConfiguration};
|
||||
}
|
||||
if(not config.proxyHost or config.proxyHost->empty()) {
|
||||
log(LogLevel::Error, "Proxy is enabled but proxy server is not present or empty");
|
||||
return tl::unexpected{ConfigurationError::ErrorClass::BadConfiguration};
|
||||
}
|
||||
}
|
||||
config.proxyAuthRequired = getBool("proxyAuthRequired").value_or(false);
|
||||
config.proxyUsername = getStringOpt("proxyUsername");
|
||||
config.proxyPass = getStringOpt("proxyPassword");
|
||||
if(config.proxyAuthRequired) {
|
||||
if(not config.proxyUsername or config.proxyUsername->empty()) {
|
||||
log(LogLevel::Error, "Proxy auth is required but proxy proxy username is not present or empty");
|
||||
return tl::unexpected{ConfigurationError::ErrorClass::BadConfiguration};
|
||||
}
|
||||
if(not config.proxyPass) {
|
||||
log(LogLevel::Error,
|
||||
"Proxy is enabled but proxy password is not present, "
|
||||
"if no password is required add an empty entry to configuration file");
|
||||
return tl::unexpected{ConfigurationError::ErrorClass::BadConfiguration};
|
||||
}
|
||||
}
|
||||
|
||||
toLowerCaseOpt(config.proxyType);
|
||||
toLowerCaseOpt(config.proxyHost);
|
||||
|
||||
auto defaultProxyPort = [&]() -> int {
|
||||
if(config.proxyType.value_or("").find("socks") != std::pmr::string::npos) {
|
||||
return 1080;
|
||||
}
|
||||
return 8080;
|
||||
};
|
||||
|
||||
if(config.proxyEnabled and not config.proxyPort) {
|
||||
config.proxyPort = getInt("proxyPort").value_or(defaultProxyPort());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::pmr::memory_resource * _memoryResource;
|
||||
std::pmr::map< std::pmr::string, std::pmr::string > _keyValues{_memoryResource};
|
||||
};
|
||||
|
||||
} // namespace rublon
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "rublon/static_string.hpp"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <rublon/configuration.hpp>
|
||||
#include <rublon/core_handler_interface.hpp>
|
||||
#include <rublon/curl.hpp>
|
||||
@ -12,19 +9,22 @@
|
||||
#include <rublon/utils.hpp>
|
||||
#include <rublon/websockets.hpp>
|
||||
|
||||
#include <string_view>
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
namespace rublon {
|
||||
|
||||
template < typename HttpHandler = CURL >
|
||||
class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
||||
Configuration _config;
|
||||
std::reference_wrapper< const Configuration > _config;
|
||||
mutable std::unique_ptr< WebSocket > _ws; /// TODO try to remove mutable modyfier
|
||||
HttpHandler http{};
|
||||
|
||||
const Configuration & config() const noexcept {
|
||||
return _config.get();
|
||||
}
|
||||
|
||||
void signRequest(Request & request) const {
|
||||
request.headers["X-Rublon-Signature"] =
|
||||
std::pmr::string{signData(request.body, _config.secretKey).c_str(), request.headers.get_allocator()};
|
||||
request.headers["X-Rublon-Signature"] = signData(request.body, config().secretKey).c_str();
|
||||
}
|
||||
|
||||
bool hasSignature(const Response & response) const {
|
||||
@ -49,7 +49,7 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
||||
|
||||
bool signatureIsNatValid(const Response & response) const {
|
||||
const auto & xRubResp = response.headers.at("x-rublon-signature");
|
||||
const auto & sign = signData(response.body, _config.secretKey);
|
||||
const auto & sign = signData(response.body, config().secretKey);
|
||||
const bool signatureMatch = xRubResp == sign.data();
|
||||
if(not signatureMatch)
|
||||
log(LogLevel::Error, "Signature mismatch %s != %s ", xRubResp.c_str(), sign.data());
|
||||
@ -57,27 +57,26 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
||||
}
|
||||
|
||||
bool hasException(const Document & coreResponse) const {
|
||||
log(LogLevel::Trace, "Checking error status in core response");
|
||||
using namespace std::string_view_literals;
|
||||
return coreResponse.HasMember("status") and coreResponse["status"].GetString() == "ERROR"sv;
|
||||
}
|
||||
|
||||
bool isUnHealthy(const Document & coreResponse) const {
|
||||
log(LogLevel::Trace, "Checking errors in core response");
|
||||
return coreResponse.HasParseError();
|
||||
}
|
||||
|
||||
protected:
|
||||
HttpHandler http{};
|
||||
|
||||
public:
|
||||
CoreHandler() = delete;
|
||||
CoreHandler(const Configuration & config) : _config{config}, _ws{std::make_unique<WebSocket>(_config.apiServer)}, http{} {
|
||||
log(LogLevel::Debug, "Core Handler apiServer: %s", _config.apiServer.c_str());
|
||||
CoreHandler(const Configuration & baseconfig) : _config{baseconfig}, _ws{std::make_unique< WebSocket >(_config)}, http{config()} {
|
||||
log(LogLevel::Debug, "Core Handler apiServer: %s", config().apiServer.c_str());
|
||||
}
|
||||
CoreHandler(CoreHandler &&) noexcept = default;
|
||||
CoreHandler & operator=(CoreHandler &&) = default;
|
||||
|
||||
CoreHandler(const CoreHandler &) = delete;
|
||||
CoreHandler(CoreHandler &&) noexcept = delete;
|
||||
|
||||
CoreHandler & operator=(const CoreHandler &) = delete;
|
||||
CoreHandler & operator=(CoreHandler &&) = delete;
|
||||
|
||||
~CoreHandler() noexcept = default;
|
||||
|
||||
@ -102,7 +101,7 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
||||
tl::expected< Document, Error > resp{&alloc};
|
||||
resp->Parse(response.body.c_str());
|
||||
|
||||
log(LogLevel::Debug, "Begin, Core Response validation");
|
||||
log(LogLevel::Debug, "Starting Core Response validation");
|
||||
|
||||
if(isUnHealthy(*resp)) {
|
||||
log(LogLevel::Error, "Rublon Core responded with broken data");
|
||||
@ -123,11 +122,12 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
||||
return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}};
|
||||
}
|
||||
|
||||
log(LogLevel::Debug, "Core Response validated OK");
|
||||
return resp;
|
||||
}
|
||||
|
||||
tl::unexpected< Error > handleCoreException(std::string_view exceptionString) const {
|
||||
// can happen only dyring check application step
|
||||
// can happen only during check application step
|
||||
if(auto error = RublonCheckApplicationException::fromString(exceptionString); error.has_value())
|
||||
return tl::unexpected{Error{error.value()}};
|
||||
|
||||
@ -139,8 +139,9 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
||||
if(auto error = WerificationError::fromString(exceptionString); error.has_value())
|
||||
return tl::unexpected{Error{error.value()}};
|
||||
|
||||
// CoreHandlerError::TransactionAccessTokenExpiredException
|
||||
|
||||
// other exceptions, just "throw"
|
||||
// TODO Handle
|
||||
return tl::unexpected{Error{CoreHandlerError{CoreHandlerError::RublonCoreException}}};
|
||||
}
|
||||
|
||||
@ -149,24 +150,24 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
||||
}
|
||||
|
||||
tl::expected< Document, Error > request(RapidJSONPMRAlloc & mr, std::string_view path, const Document & body) const {
|
||||
memory::StrictMonotonic_8k_HeapResource memoryResource;
|
||||
memory::Monotonic_4k_Resource memoryResource;
|
||||
|
||||
const auto validateSignature = [&](const auto & arg) { return this->validateSignature(arg); };
|
||||
const auto validateResponse = [&](const auto & arg) { return this->validateResponse(mr, arg); };
|
||||
const auto handleError = [&](const auto & error) { return this->handleError(error); };
|
||||
const auto pmrs = [&](const auto & txt) { return std::pmr::string{txt, &memoryResource}; };
|
||||
|
||||
Request request{&memoryResource};
|
||||
Response response{&memoryResource};
|
||||
|
||||
request.headers["Content-Type"] = pmrs("application/json");
|
||||
request.headers["Accept"] = pmrs("application/json");
|
||||
request.headers["Content-Type"] = "application/json";
|
||||
request.headers["Accept"] = "application/json";
|
||||
|
||||
stringifyTo(body, request.body);
|
||||
|
||||
signRequest(request);
|
||||
std::pmr::string uri{&memoryResource};
|
||||
uri += _config.apiServer.c_str();
|
||||
uri.reserve(conservative_estimate(config().apiServer, path.size()));
|
||||
uri += config().apiServer;
|
||||
uri += path.data();
|
||||
|
||||
return http
|
||||
@ -178,7 +179,7 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
||||
|
||||
bool createWSConnection(std::string_view tid) const {
|
||||
if(not _ws) {
|
||||
_ws.reset(new WebSocket (_config.apiServer));
|
||||
_ws.reset(new WebSocket(config()));
|
||||
}
|
||||
/// TODO connect can be separated from subscribtion on event
|
||||
/// TODO status of attach is not checked
|
||||
|
||||
@ -16,7 +16,7 @@ class CoreHandlerInterface {
|
||||
}
|
||||
|
||||
bool createWSConnection(std::string_view tid) const {
|
||||
rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::listen");
|
||||
rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::createWSConnection");
|
||||
return static_cast< const Impl * >(this)->createWSConnection(tid);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <rublon/configuration.hpp>
|
||||
#include <rublon/error.hpp>
|
||||
#include <rublon/utils.hpp>
|
||||
|
||||
@ -51,30 +52,91 @@ struct Response {
|
||||
|
||||
class CURL {
|
||||
std::unique_ptr< ::CURL, void (*)(::CURL *) > curl;
|
||||
std::reference_wrapper< const Configuration > _config;
|
||||
|
||||
const Configuration & conf() const noexcept {
|
||||
return _config.get();
|
||||
}
|
||||
|
||||
public:
|
||||
CURL() : curl{std::unique_ptr< ::CURL, void (*)(::CURL *) >(curl_easy_init(), curl_easy_cleanup)} {}
|
||||
CURL(const Configuration & config)
|
||||
: curl{std::unique_ptr< ::CURL, void (*)(::CURL *) >(curl_easy_init(), curl_easy_cleanup)}, _config{config} {}
|
||||
|
||||
CURL(const CURL &) = delete;
|
||||
CURL(CURL &&) = delete;
|
||||
|
||||
CURL & operator=(const CURL &) = delete;
|
||||
CURL & operator=(CURL &&) = delete;
|
||||
|
||||
tl::expected< std::reference_wrapper< Response >, ConnectionError >
|
||||
request(std::string_view uri, const Request & request, Response & response) const {
|
||||
memory::MonotonicStackResource< 4 * 1024 > stackResource;
|
||||
using namespace memory::literals;
|
||||
memory::Monotonic_8k_Resource memoryResource;
|
||||
|
||||
std::pmr::string response_data{&stackResource};
|
||||
response_data.reserve(3000);
|
||||
std::pmr::string response_data{&memoryResource};
|
||||
response_data.reserve(4_kB);
|
||||
|
||||
/// TODO this can be done on stack using pmr
|
||||
auto curl_headers = std::unique_ptr< curl_slist, void (*)(curl_slist *) >(nullptr, curl_slist_free_all);
|
||||
std::for_each(request.headers.begin(), request.headers.end(), [&](auto header) {
|
||||
log(LogLevel::Debug, "%s header: %s: %s", "CURL", header.first.c_str(), header.second.c_str());
|
||||
curl_headers.reset(curl_slist_append(curl_headers.release(), (header.first + ": " + header.second).c_str()));
|
||||
});
|
||||
|
||||
// Optional: Build full proxy URL if proxy is enabled
|
||||
if(conf().proxyEnabled) {
|
||||
// configuration reader check if proxy has needed fields
|
||||
assert(conf().proxyType.has_value());
|
||||
assert(conf().proxyHost.has_value());
|
||||
log(LogLevel::Debug, "CURL using proxy");
|
||||
|
||||
std::pmr::string proxyUrl{&memoryResource};
|
||||
proxyUrl.reserve(conservative_estimate(conf().proxyType, conf().proxyHost, conf().proxyPort) + 10);
|
||||
|
||||
if(conf().proxyType == "http" || conf().proxyType == "https" || conf().proxyType == "socks4" || conf().proxyType == "socks5") {
|
||||
proxyUrl = *conf().proxyType;
|
||||
proxyUrl += "://";
|
||||
proxyUrl += *conf().proxyHost;
|
||||
if(conf().proxyPort.value_or(0) > 0) {
|
||||
proxyUrl += ":";
|
||||
proxyUrl += std::to_string(*conf().proxyPort);
|
||||
}
|
||||
|
||||
log(LogLevel::Debug, "CURL using %s", proxyUrl.c_str());
|
||||
curl_easy_setopt(curl.get(), CURLOPT_PROXY, proxyUrl.c_str());
|
||||
|
||||
if(conf().proxyType == "socks4") {
|
||||
curl_easy_setopt(curl.get(), CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
|
||||
} else if(conf().proxyType == "socks5") {
|
||||
curl_easy_setopt(curl.get(), CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
|
||||
} else {
|
||||
curl_easy_setopt(curl.get(), CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
|
||||
}
|
||||
|
||||
if(conf().proxyAuthRequired) {
|
||||
assert(conf().proxyUsername.has_value());
|
||||
assert(conf().proxyPass.has_value());
|
||||
|
||||
std::pmr::string proxyAuth{&memoryResource};
|
||||
proxyAuth.reserve(conservative_estimate(conf().proxyUsername, conf().proxyPass));
|
||||
|
||||
proxyAuth += *conf().proxyUsername;
|
||||
if(conf().proxyPass->size()) {
|
||||
// can proxy have name but no pass?
|
||||
proxyAuth += ":";
|
||||
proxyAuth += *conf().proxyPass;
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl.get(), CURLOPT_PROXYUSERPWD, proxyAuth.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl.get(), CURLOPT_VERBOSE, 0);
|
||||
curl_easy_setopt(curl.get(), CURLOPT_URL, uri.data());
|
||||
curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, curl_headers.get());
|
||||
curl_easy_setopt(curl.get(), CURLOPT_POST, 1);
|
||||
curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, request.body.data());
|
||||
curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDSIZE, static_cast< u_int32_t >(request.body.size()));
|
||||
curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDSIZE, static_cast< uint32_t >(request.body.size()));
|
||||
curl_easy_setopt(curl.get(), CURLOPT_HEADER, 1);
|
||||
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
|
||||
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_data);
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <tl/expected.hpp>
|
||||
#include <string_view>
|
||||
|
||||
#include <rublon/non_owning_ptr.hpp>
|
||||
#include <rublon/stdlib.hpp>
|
||||
#include <rublon/utils.hpp>
|
||||
|
||||
namespace rublon {
|
||||
|
||||
template < typename... Types >
|
||||
constexpr std::array< std::string_view, sizeof...(Types) > make_array(Types... names) {
|
||||
return {std::forward< Types >(names)...};
|
||||
}
|
||||
|
||||
class ConfigurationError {
|
||||
public:
|
||||
enum class ErrorClass { //
|
||||
RequiredValueNotFound,
|
||||
RequiredValueEmpty,
|
||||
BadFailMode,
|
||||
BadInput,
|
||||
BadConfiguration,
|
||||
Empty
|
||||
};
|
||||
constexpr static auto errorClassPrettyName = make_array( //
|
||||
constexpr static auto errorClassPrettyName = make_array< std::string_view >( //
|
||||
"RequiredValueNotFound",
|
||||
"RequiredValueEmpty",
|
||||
"BadFailMode",
|
||||
"BadInput",
|
||||
"BadConfiguration",
|
||||
"Empty");
|
||||
constexpr static auto prettyName = "Configurtion Error";
|
||||
|
||||
@ -43,7 +43,7 @@ class ConnectionError {
|
||||
HttpError,
|
||||
ClientError
|
||||
};
|
||||
constexpr static auto errorClassPrettyName = make_array( //
|
||||
constexpr static auto errorClassPrettyName = make_array< std::string_view >( //
|
||||
"Timeout",
|
||||
"Error",
|
||||
"Client Error");
|
||||
@ -72,7 +72,7 @@ class CoreHandlerError {
|
||||
TransactionAccessTokenExpiredException, // code: 11
|
||||
TooManyRequestsException
|
||||
};
|
||||
constexpr static auto errorClassPrettyName = make_array( //
|
||||
constexpr static auto errorClassPrettyName = make_array< std::string_view >( //
|
||||
"BadSigature",
|
||||
"RublonCoreException",
|
||||
"BrokenData",
|
||||
@ -107,7 +107,7 @@ class CoreHandlerError {
|
||||
class MethodError {
|
||||
public:
|
||||
enum ErrorClass { BadMethod, BadUserInput, NoMethodAvailable };
|
||||
constexpr static auto errorClassPrettyName = make_array("BadMethod", "BadUserInput", "NoMethodAvailable");
|
||||
constexpr static auto errorClassPrettyName = make_array< std::string_view >("BadMethod", "BadUserInput", "NoMethodAvailable");
|
||||
constexpr static auto prettyName = "Method Error";
|
||||
|
||||
constexpr MethodError(ErrorClass e = BadMethod) : errorClass{e} {}
|
||||
@ -125,12 +125,14 @@ class WerificationError {
|
||||
BadInput, // User input has incorrect characters or length
|
||||
SecurityKeyException, // code: 16
|
||||
PasscodeException, // code: 18
|
||||
SendPushException, // code: 21 | this code really should not be here as rest of the codes are strictly for passcode verification
|
||||
TooManyRequestsException // code: 101
|
||||
};
|
||||
constexpr static auto errorClassPrettyName = make_array( //
|
||||
constexpr static auto errorClassPrettyName = make_array< std::string_view >( //
|
||||
"BadInput",
|
||||
"SecurityKeyException",
|
||||
"PasscodeException",
|
||||
"SendPushException",
|
||||
"TooManyRequestsException");
|
||||
constexpr static inline auto prettyName = "Werification Error";
|
||||
|
||||
@ -162,7 +164,7 @@ class RublonAuthenticationInterrupt {
|
||||
UserWaiting,
|
||||
UserNotFound
|
||||
};
|
||||
constexpr static auto errorClassPrettyName = make_array( //
|
||||
constexpr static auto errorClassPrettyName = make_array< std::string_view >( //
|
||||
"UserBypassedException",
|
||||
"UserDenied",
|
||||
"UserPending",
|
||||
@ -197,7 +199,7 @@ class RublonCheckApplicationException {
|
||||
UnsupportedVersionException
|
||||
};
|
||||
|
||||
constexpr static auto errorClassPrettyName = make_array( //
|
||||
constexpr static auto errorClassPrettyName = make_array< std::string_view >( //
|
||||
"MissingFieldException",
|
||||
"ApplicationNotFoundException",
|
||||
"InvalidSignatureException",
|
||||
@ -222,6 +224,41 @@ class RublonCheckApplicationException {
|
||||
ErrorClass errorClass;
|
||||
};
|
||||
|
||||
|
||||
class RublonRuntimeException {
|
||||
public:
|
||||
enum ErrorClass {
|
||||
CantCreateSocket,
|
||||
ConnectionFailed,
|
||||
CantGetSocketName,
|
||||
CantReadLocalIP
|
||||
};
|
||||
|
||||
constexpr static auto errorClassPrettyName = make_array< std::string_view >( //
|
||||
"CantCreateSocket",
|
||||
"ConnectionFailed",
|
||||
"CantGetSocketName",
|
||||
"CantReadLocalIP");
|
||||
constexpr static auto prettyName = "Rublon Runtime Exception";
|
||||
|
||||
RublonRuntimeException(ErrorClass e = CantReadLocalIP) : errorClass{e} {}
|
||||
|
||||
constexpr const char * what() const {
|
||||
return errorClassPrettyName[static_cast< int >(errorClass)].data();
|
||||
}
|
||||
|
||||
static std::optional< RublonRuntimeException > fromString(std::string_view name) {
|
||||
for(std::size_t i{}; i < errorClassPrettyName.size(); i++) {
|
||||
if(errorClassPrettyName.at(i) == name) {
|
||||
return std::make_optional(RublonRuntimeException{static_cast< ErrorClass >(i)});
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ErrorClass errorClass;
|
||||
};
|
||||
|
||||
class Error {
|
||||
using Error_t = std::variant< ConfigurationError,
|
||||
CoreHandlerError,
|
||||
@ -229,7 +266,8 @@ class Error {
|
||||
WerificationError,
|
||||
MethodError,
|
||||
RublonAuthenticationInterrupt,
|
||||
RublonCheckApplicationException >;
|
||||
RublonCheckApplicationException,
|
||||
RublonRuntimeException >;
|
||||
Error_t _error;
|
||||
|
||||
public:
|
||||
@ -240,7 +278,8 @@ class Error {
|
||||
k_WerificationError,
|
||||
k_MethodError,
|
||||
k_RublonAuthenticationInterrupt,
|
||||
k_RublonCheckApplication
|
||||
k_RublonCheckApplication,
|
||||
k_RublonRuntimeException
|
||||
};
|
||||
|
||||
Error() = default;
|
||||
@ -252,6 +291,7 @@ class Error {
|
||||
Error(WerificationError error) : _error{error} {}
|
||||
Error(RublonAuthenticationInterrupt error) : _error{error} {}
|
||||
Error(RublonCheckApplicationException error) : _error{error} {}
|
||||
Error(RublonRuntimeException error) : _error{error} {}
|
||||
|
||||
Error(const Error &) = default;
|
||||
Error(Error &&) = default;
|
||||
|
||||
@ -44,7 +44,7 @@ class ErrorHandler {
|
||||
}
|
||||
|
||||
if(error.is< ConnectionError >()) {
|
||||
if(config.failMode == FailMode::deny) {
|
||||
if(config.failMode == FailMode::bypass) {
|
||||
pam.print("Incorrect response from the Rublon API, user bypassed");
|
||||
return AuthenticationStatus::Action::Bypass;
|
||||
} else {
|
||||
@ -66,12 +66,15 @@ class ErrorHandler {
|
||||
case WerificationError::ErrorClass::BadInput:
|
||||
pam.print(R"(Ensure that the Secret Key is correct.)");
|
||||
return AuthenticationStatus::Action::Denied;
|
||||
case rublon::WerificationError::SecurityKeyException:
|
||||
case WerificationError::SecurityKeyException:
|
||||
pam.print(R"(Ensure that the Secret Key is correct.)");
|
||||
return AuthenticationStatus::Action::Denied;
|
||||
case rublon::WerificationError::TooManyRequestsException:
|
||||
case WerificationError::TooManyRequestsException:
|
||||
pam.print(R"(Too many attempts.)");
|
||||
return AuthenticationStatus::Action::Denied;
|
||||
case WerificationError::SendPushException:
|
||||
pam.print(R"(Rublon failed to reach authenticator app)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,7 +92,7 @@ class ErrorHandler {
|
||||
log(LogLevel::Error, R"(The provided version of the app is unsupported.)");
|
||||
log(LogLevel::Error, R"(Try changing the app version.)");
|
||||
return AuthenticationStatus::Action::Denied;
|
||||
case rublon::RublonCheckApplicationException::MissingFieldException:
|
||||
case RublonCheckApplicationException::MissingFieldException:
|
||||
log(LogLevel::Error, R"(The provided version of the app is unsupported.)");
|
||||
log(LogLevel::Error, R"(Try changing the app version.)");
|
||||
return AuthenticationStatus::Action::Denied;
|
||||
|
||||
@ -4,16 +4,17 @@
|
||||
#include <rublon/configuration.hpp>
|
||||
#include <rublon/json.hpp>
|
||||
#include <rublon/method/method_select.hpp>
|
||||
#include <string_view>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
namespace rublon {
|
||||
class Finish : public AuthenticationStep {
|
||||
const char * apiPath = "/api/transaction/credentials";
|
||||
const std::string _accessToken;
|
||||
const std::string_view _accessToken; //
|
||||
|
||||
void addAccessToken(Document & coreRequest) const {
|
||||
auto & alloc = coreRequest.GetAllocator();
|
||||
coreRequest.AddMember("accessToken", Value{_accessToken.c_str(), alloc}, alloc);
|
||||
coreRequest.AddMember("accessToken", Value{_accessToken.data(), alloc}, alloc);
|
||||
}
|
||||
|
||||
tl::expected< bool, Error > returnOk(const Document & /*coreResponse*/) const {
|
||||
@ -23,7 +24,7 @@ class Finish : public AuthenticationStep {
|
||||
public:
|
||||
const char * name = "Finalization";
|
||||
|
||||
Finish(Session & session, std::string accessToken) : AuthenticationStep(session), _accessToken{std::move(accessToken)} {}
|
||||
Finish(Session & session, std::string_view accessToken) : AuthenticationStep(session), _accessToken{accessToken} {}
|
||||
|
||||
template < typename Hander_t >
|
||||
tl::expected< bool, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const {
|
||||
|
||||
@ -1,18 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "rublon/memory.hpp"
|
||||
#include <rublon/bits.hpp>
|
||||
|
||||
#include "rublon/utils.hpp"
|
||||
#include <rublon/authentication_step_interface.hpp>
|
||||
#include <rublon/bits.hpp>
|
||||
#include <rublon/configuration.hpp>
|
||||
#include <rublon/json.hpp>
|
||||
#include <rublon/method/method_select.hpp>
|
||||
|
||||
#include <rublon/session.hpp>
|
||||
|
||||
#include <sys/utsname.h>
|
||||
|
||||
[[deprecated]] extern std::string g_tid;
|
||||
#include <rublon/ip_utils.hpp>
|
||||
|
||||
namespace rublon {
|
||||
|
||||
@ -23,16 +18,19 @@ class Init : public AuthenticationStep {
|
||||
const char * apiPath = "/api/transaction/init";
|
||||
|
||||
tl::expected< MethodSelect_t, Error > createMethod(const Document & coreResponse) const {
|
||||
memory::MonotonicStackResource< 256 > stackResource;
|
||||
memory::MonotonicStackResource< 512 > stackResource;
|
||||
RapidJSONPMRAlloc alloc{&stackResource};
|
||||
|
||||
const auto & rublonResponse = coreResponse["result"];
|
||||
|
||||
// const auto * rublonMethods = JSONPointer{"/result/methods", &alloc}.Get(coreResponse);
|
||||
const auto * rublonMethods = JSONPointer{"/result/methods", &alloc}.Get(coreResponse);
|
||||
const auto * rublonTid = JSONPointer{"/result/tid", &alloc}.Get(coreResponse);
|
||||
_session.updateTransactionId(rublonTid);
|
||||
|
||||
return MethodSelect_t{_session, rublonResponse["methods"], _session.config().prompt, _session.config().autopushPrompt};
|
||||
if(not rublonMethods)
|
||||
log(LogLevel::Error, "core response has no methods");
|
||||
if(not rublonTid)
|
||||
log(LogLevel::Error, "core response has no transaction ID");
|
||||
|
||||
_session.updateTransactionId(rublonTid);
|
||||
return MethodSelect_t{_session, *rublonMethods, _session.config().prompt, _session.config().autopushPrompt};
|
||||
}
|
||||
|
||||
void addPamInfo(Document & coreRequest, const Pam_t & pam) const {
|
||||
@ -41,43 +39,65 @@ class Init : public AuthenticationStep {
|
||||
}
|
||||
|
||||
void addParams(Document & coreRequest, const Pam_t & pam) const {
|
||||
memory::MonotonicStackResource< 512 > stackResource;
|
||||
std::pmr::string releaseInfo{&stackResource};
|
||||
using namespace memory::literals;
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
memory::MonotonicStackResource< 2_kB > memoryResource;
|
||||
auto & alloc = coreRequest.GetAllocator();
|
||||
|
||||
const auto os = details::osName(&stackResource);
|
||||
const auto os = details::osName(&memoryResource);
|
||||
const auto host = details::hostname(&memoryResource);
|
||||
|
||||
if(os == "unknown") {
|
||||
log(LogLevel::Warning, "No OS information available");
|
||||
}
|
||||
|
||||
Value osNamePretty{os.data(), static_cast< unsigned >(os.size()), alloc};
|
||||
Value ip{pam.ip().get(), alloc};
|
||||
|
||||
Value params{rapidjson::kObjectType};
|
||||
params.AddMember("userIP", ip, alloc);
|
||||
params.AddMember("appVer", RUBLON_VERSION_STRING, alloc);
|
||||
params.AddMember("os", osNamePretty, alloc);
|
||||
if(not host.empty())
|
||||
params.AddMember("hostName", Value{os.c_str(), static_cast< unsigned >(host.size()), alloc}, alloc);
|
||||
|
||||
if(_session.inNonInteractiveMode())
|
||||
params.AddMember("mode", "noninteractive", alloc);
|
||||
|
||||
params.AddMember("os", Value{os.c_str(), static_cast< unsigned >(os.size()), alloc}, alloc);
|
||||
|
||||
auto ip = pam.ip();
|
||||
// ip has always a value, can be empty but the value should be set
|
||||
if(ip.get() != ""sv){
|
||||
params.AddMember("userIP", Value{pam.ip().get(), alloc}, alloc);
|
||||
} else {
|
||||
getDefaultRouteIp(&memoryResource) //
|
||||
.and_then([&](const auto & ip) {
|
||||
params.AddMember("userIP", Value{ip.c_str(), alloc}, alloc);
|
||||
return tl::expected< void, Error >{};
|
||||
})
|
||||
.or_else([](auto) { log(LogLevel::Warning, "There is no UserIP that can be used"); });
|
||||
}
|
||||
|
||||
coreRequest.AddMember("params", std::move(params), alloc);
|
||||
}
|
||||
|
||||
tl::expected< std::reference_wrapper< const Document >, Error > checkEnrolement(const Document & coreResponse, const Pam_t pam) const {
|
||||
using namespace std::string_view_literals;
|
||||
const auto & resp = coreResponse;
|
||||
using namespace memory::literals;
|
||||
memory::MonotonicStackResource< 1_kB > stackResource;
|
||||
RapidJSONPMRAlloc alloc{&stackResource};
|
||||
|
||||
/// TODO refactor this
|
||||
if(resp.HasMember("result") and resp["result"].IsObject() and resp["result"].HasMember("status")) {
|
||||
const auto & status = resp["result"]["status"].GetString();
|
||||
log(LogLevel::Warning, "Got enrolement message with stats %s", status);
|
||||
const auto * rublonStatus = JSONPointer{"/result/status", &alloc}.Get(coreResponse);
|
||||
const auto * rublonWebURI = JSONPointer{"/result/webURI", &alloc}.Get(coreResponse);
|
||||
|
||||
if(rublonStatus) {
|
||||
const auto & status = rublonStatus->GetString();
|
||||
if(status == "pending"sv) {
|
||||
if(resp["result"].HasMember("webURI")) {
|
||||
const auto & weburi = resp["result"]["webURI"].GetString();
|
||||
pam.print("Visit %s", weburi);
|
||||
if(rublonWebURI) {
|
||||
pam.print("Visit %s", rublonWebURI->GetString());
|
||||
}
|
||||
} else if(status == "waiting"sv) {
|
||||
log(LogLevel::Warning, "Got enrolement message with stats %s", status);
|
||||
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserWaiting}}};
|
||||
} else if(status == "denied"sv) {
|
||||
log(LogLevel::Warning, "Got enrolement message with stats %s", status);
|
||||
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserDenied}}};
|
||||
}
|
||||
}
|
||||
@ -89,7 +109,7 @@ class Init : public AuthenticationStep {
|
||||
const char * _name = "Initialization";
|
||||
|
||||
Init(Session & session) : base_t(session) {
|
||||
log(LogLevel::Debug, "Init");
|
||||
log(LogLevel::Debug, "Starting inicialization");
|
||||
}
|
||||
|
||||
template < typename Hander_t >
|
||||
|
||||
96
PAM/ssh/include/rublon/ip_utils.hpp
Normal file
96
PAM/ssh/include/rublon/ip_utils.hpp
Normal file
@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include "rublon/utils.hpp"
|
||||
#include <memory_resource>
|
||||
#include <rublon/error.hpp>
|
||||
#include <rublon/memory.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
|
||||
namespace rublon {
|
||||
|
||||
using socket_t = int;
|
||||
constexpr socket_t invalid_socket = -1;
|
||||
|
||||
class unique_fd {
|
||||
socket_t fd_ = invalid_socket;
|
||||
|
||||
public:
|
||||
unique_fd() = default;
|
||||
explicit unique_fd(socket_t fd) : fd_(fd) {}
|
||||
|
||||
~unique_fd() {
|
||||
close(fd_);
|
||||
}
|
||||
|
||||
unique_fd(const unique_fd &) = delete;
|
||||
unique_fd & operator=(const unique_fd &) = delete;
|
||||
|
||||
unique_fd(unique_fd && other) noexcept : fd_(std::exchange(other.fd_, invalid_socket)) {}
|
||||
unique_fd & operator=(unique_fd && other) noexcept {
|
||||
if(this != &other) {
|
||||
close(fd_);
|
||||
fd_ = std::exchange(other.fd_, invalid_socket);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
socket_t get() const noexcept {
|
||||
return fd_;
|
||||
}
|
||||
socket_t release() noexcept {
|
||||
return std::exchange(fd_, invalid_socket);
|
||||
}
|
||||
void reset(socket_t fd = invalid_socket) noexcept {
|
||||
if(fd_ != fd) {
|
||||
close(fd_);
|
||||
fd_ = fd;
|
||||
}
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return fd_ != invalid_socket;
|
||||
}
|
||||
};
|
||||
|
||||
// this function is a little hack just to fix RSUP-1413
|
||||
// When loging to rublon from withing the host itself, there is no IP delivered through any 'pam_item'
|
||||
// but we need to log from what place the request did came.
|
||||
tl::expected< std::pmr::string, Error > getDefaultRouteIp(std::pmr::memory_resource * mr) {
|
||||
log(LogLevel::Debug, "Reading default route IP");
|
||||
unique_fd sock(::socket(AF_INET, SOCK_DGRAM, 0));
|
||||
if(!sock){
|
||||
log(LogLevel::Warning, "Cant create socket");
|
||||
return tl::unexpected{Error{RublonRuntimeException::CantCreateSocket}};
|
||||
}
|
||||
|
||||
sockaddr_in remote{};
|
||||
remote.sin_family = AF_INET;
|
||||
remote.sin_port = htons(53);
|
||||
::inet_pton(AF_INET, "8.8.8.8", &remote.sin_addr);
|
||||
|
||||
// we use UDP so no actual connection is estamblished. Only trying to get an interface
|
||||
if(::connect(sock.get(), ( struct sockaddr * ) &remote, sizeof(remote)) < 0) {
|
||||
log(LogLevel::Warning, "Cant connect to socket");
|
||||
return tl::unexpected{Error{RublonRuntimeException::ConnectionFailed}};
|
||||
}
|
||||
|
||||
sockaddr_in local{};
|
||||
socklen_t len = sizeof(local);
|
||||
if(::getsockname(sock.get(), ( struct sockaddr * ) &local, &len) < 0) {
|
||||
log(LogLevel::Warning, "Cant get socket name");
|
||||
return tl::unexpected{Error{RublonRuntimeException::CantGetSocketName}};
|
||||
}
|
||||
|
||||
char ip[INET_ADDRSTRLEN];
|
||||
::inet_ntop(AF_INET, &local.sin_addr, ip, sizeof(ip));
|
||||
return std::pmr::string{ip, mr};
|
||||
}
|
||||
} // namespace rublon
|
||||
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "rapidjson/encodings.h"
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/istreamwrapper.h>
|
||||
#include <rapidjson/pointer.h>
|
||||
@ -121,8 +122,8 @@ struct FileWriter {
|
||||
|
||||
template < typename T >
|
||||
static void stringifyTo(const Document & body, T & to) {
|
||||
memory::Monotonic_1k_HeapResource tmpResource;
|
||||
RapidJSONPMRAlloc alloc{&tmpResource};
|
||||
memory::MonotonicStack_1k_Resource memoryResource;
|
||||
RapidJSONPMRAlloc alloc{&memoryResource};
|
||||
StringWriter< T > s{to};
|
||||
rapidjson::Writer< StringWriter< T >, rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc > writer{s, &alloc};
|
||||
body.Accept(writer);
|
||||
@ -137,7 +138,6 @@ inline auto begin(const rublon::Value & __ils) noexcept {
|
||||
inline ::rublon::Value::ConstValueIterator end(const rublon::Value & __ils) noexcept {
|
||||
return __ils.End();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::size_t size(const rublon::Value & __cont) {
|
||||
return __cont.Size();
|
||||
}
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory_resource>
|
||||
#include <rublon/stdlib.hpp>
|
||||
|
||||
namespace rublon {
|
||||
namespace memory {
|
||||
namespace literals {
|
||||
constexpr std::uint64_t operator""_kB(unsigned long long kilobytes) {
|
||||
return kilobytes * 1024ULL;
|
||||
}
|
||||
} // namespace literals
|
||||
|
||||
struct default_memory_resource {
|
||||
static inline std::pmr::memory_resource * _mr = std::pmr::get_default_resource();
|
||||
};
|
||||
@ -16,53 +23,48 @@ namespace memory {
|
||||
return default_memory_resource{}._mr;
|
||||
}
|
||||
|
||||
template < std::size_t N >
|
||||
class MonotonicStackResource : public std::pmr::monotonic_buffer_resource {
|
||||
char _buffer[N];
|
||||
|
||||
public:
|
||||
MonotonicStackResource() : std::pmr::monotonic_buffer_resource{_buffer, N, std::pmr::null_memory_resource()} {}
|
||||
};
|
||||
|
||||
class MonotonicHeapResourceBase {
|
||||
class MonotonicResourceBase {
|
||||
public:
|
||||
std::pmr::memory_resource * _upstream{};
|
||||
std::size_t _size{};
|
||||
void * _buffer{nullptr};
|
||||
|
||||
MonotonicHeapResourceBase(std::size_t size) : _upstream{default_resource()}, _size{size}, _buffer{_upstream->allocate(size)} {}
|
||||
MonotonicResourceBase(std::size_t size) : _upstream{default_resource()}, _size{size}, _buffer{_upstream->allocate(size)} {}
|
||||
|
||||
~MonotonicHeapResourceBase() {
|
||||
~MonotonicResourceBase() {
|
||||
if(_buffer)
|
||||
_upstream->deallocate(_buffer, _size);
|
||||
}
|
||||
};
|
||||
|
||||
template < std::size_t N >
|
||||
class MonotonicHeapResource : MonotonicHeapResourceBase, public std::pmr::monotonic_buffer_resource {
|
||||
class MonotonicResource : MonotonicResourceBase, public std::pmr::monotonic_buffer_resource {
|
||||
public:
|
||||
MonotonicHeapResource()
|
||||
: MonotonicHeapResourceBase{N}, std::pmr::monotonic_buffer_resource{this->_buffer, this->_size, default_resource()} {}
|
||||
MonotonicResource(std::pmr::memory_resource * mr = default_resource())
|
||||
: MonotonicResourceBase{N}, std::pmr::monotonic_buffer_resource{this->_buffer, this->_size, mr} {}
|
||||
};
|
||||
|
||||
template < std::size_t N >
|
||||
class StrictMonotonicHeapResource : MonotonicHeapResourceBase, public std::pmr::monotonic_buffer_resource {
|
||||
class MonotonicStackResourceBase {
|
||||
public:
|
||||
StrictMonotonicHeapResource()
|
||||
: MonotonicHeapResourceBase{N},
|
||||
std::pmr::monotonic_buffer_resource{this->_buffer, this->_size, std::pmr::null_memory_resource()} {}
|
||||
char _buffer[N];
|
||||
};
|
||||
|
||||
using StrictMonotonic_512_HeapResource = StrictMonotonicHeapResource< 512 >;
|
||||
using StrictMonotonic_1k_HeapResource = StrictMonotonicHeapResource< 1 * 1024 >;
|
||||
using StrictMonotonic_2k_HeapResource = StrictMonotonicHeapResource< 2 * 1024 >;
|
||||
using StrictMonotonic_4k_HeapResource = StrictMonotonicHeapResource< 4 * 1024 >;
|
||||
using StrictMonotonic_8k_HeapResource = StrictMonotonicHeapResource< 8 * 1024 >;
|
||||
template < std::size_t N >
|
||||
class MonotonicStackResource : MonotonicStackResourceBase< N >, public std::pmr::monotonic_buffer_resource {
|
||||
public:
|
||||
MonotonicStackResource()
|
||||
: MonotonicStackResourceBase< N >{}, std::pmr::monotonic_buffer_resource{this->_buffer, N, default_resource()} {}
|
||||
};
|
||||
|
||||
using Monotonic_1k_HeapResource = MonotonicHeapResource< 1 * 1024 >;
|
||||
using Monotonic_2k_HeapResource = MonotonicHeapResource< 2 * 1024 >;
|
||||
using Monotonic_4k_HeapResource = MonotonicHeapResource< 4 * 1024 >;
|
||||
using Monotonic_8k_HeapResource = MonotonicHeapResource< 8 * 1024 >;
|
||||
using MonotonicStack_1k_Resource = MonotonicStackResource< 1 * 1024 >;
|
||||
using MonotonicStack_2k_Resource = MonotonicStackResource< 2 * 1024 >;
|
||||
|
||||
|
||||
using Monotonic_1k_Resource = MonotonicResource< 1 * 1024 >;
|
||||
using Monotonic_2k_Resource = MonotonicResource< 2 * 1024 >;
|
||||
using Monotonic_4k_Resource = MonotonicResource< 4 * 1024 >;
|
||||
using Monotonic_8k_Resource = MonotonicResource< 8 * 1024 >;
|
||||
} // namespace memory
|
||||
|
||||
// class RublonMemory {
|
||||
|
||||
@ -13,7 +13,6 @@ class OTP : public PasscodeBasedAuth {
|
||||
public:
|
||||
OTP(Session & session, int prompts)
|
||||
: PasscodeBasedAuth(session,
|
||||
"",
|
||||
"Mobile Passcode",
|
||||
"Enter the passcode from the Authenticator app: ",
|
||||
6,
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "rublon/session.hpp"
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
#include <rublon/authentication_step_interface.hpp>
|
||||
|
||||
@ -12,7 +12,7 @@ namespace rublon::method {
|
||||
class SMS : public PasscodeBasedAuth {
|
||||
public:
|
||||
SMS(Session & session, int prompts)
|
||||
: PasscodeBasedAuth(session, "", "SMS", "Enter SMS passcode: ", 6, true, PasscodeBasedAuth::Endpoint::ConfirmCode, prompts) {}
|
||||
: PasscodeBasedAuth(session, "SMS", "Enter SMS passcode: ", 6, true, PasscodeBasedAuth::Endpoint::ConfirmCode, prompts) {}
|
||||
};
|
||||
|
||||
} // namespace rublon::method
|
||||
|
||||
@ -11,9 +11,8 @@ namespace rublon::method {
|
||||
|
||||
class YOTP : public PasscodeBasedAuth {
|
||||
public:
|
||||
YOTP(Session & session, std::string accessToken, int prompts)
|
||||
YOTP(Session & session, int prompts)
|
||||
: PasscodeBasedAuth(session,
|
||||
std::move(accessToken),
|
||||
"YubiKey OTP Security Key",
|
||||
"Insert and tap your YubiKey: ",
|
||||
44,
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
#pragma once
|
||||
#include "rublon/session.hpp"
|
||||
#include <algorithm>
|
||||
#include <memory_resource>
|
||||
#include <set>
|
||||
#include <string_view>
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
#include <rublon/bits.hpp>
|
||||
#include <rublon/error.hpp>
|
||||
#include <rublon/json.hpp>
|
||||
#include <rublon/memory.hpp>
|
||||
#include <rublon/pam_action.hpp>
|
||||
#include <rublon/session.hpp>
|
||||
#include <rublon/utils.hpp>
|
||||
|
||||
#include <rublon/method/EMAIL.hpp>
|
||||
#include <rublon/method/PUSH.hpp>
|
||||
@ -18,6 +24,9 @@
|
||||
|
||||
namespace rublon {
|
||||
|
||||
enum class MethodIds { OTP, SMS, PUSH, EMAIL, SmsLink, YOTP, PhoneCall };
|
||||
constexpr auto MethodNames = make_array< std::string_view >("totp", "sms", "push", "email", "smsLink", "yotp", "phoneCall");
|
||||
|
||||
class MethodProxy {
|
||||
public:
|
||||
template < typename Method_t >
|
||||
@ -43,49 +52,63 @@ class PostMethod : public AuthenticationStep {
|
||||
using base_t = AuthenticationStep;
|
||||
const char * uri = "/api/transaction/methodSSH";
|
||||
|
||||
std::string _method;
|
||||
MethodIds _method;
|
||||
int _prompts;
|
||||
bool _autopushPrompt;
|
||||
|
||||
tl::expected< MethodProxy, Error > createMethod(const Document & coreResponse) const {
|
||||
/// TODO vericodeLength
|
||||
const auto & rublonResponse = coreResponse["result"];
|
||||
// [[deprecated]] std::string tid = rublonResponse["tid"].GetString();
|
||||
tl::expected< void, Error > readAccessToken(const Document & coreResponse) const {
|
||||
log(LogLevel::Debug, "Readding access token");
|
||||
auto alloc = RapidJSONPMRStackAlloc< 256 >{};
|
||||
const auto * rublonAccessToken = JSONPointer{"/result/token", &alloc}.Get(coreResponse);
|
||||
if(rublonAccessToken)
|
||||
_session.updateAccessToken(rublonAccessToken);
|
||||
return {};
|
||||
}
|
||||
|
||||
if(_method == "totp") {
|
||||
tl::expected< MethodProxy, Error > createMethod() const {
|
||||
log(LogLevel::Debug, "Creating method");
|
||||
switch(_method) {
|
||||
case rublon::MethodIds::OTP:
|
||||
return MethodProxy{method::OTP{_session, _prompts}};
|
||||
} else if(_method == "sms") {
|
||||
|
||||
case rublon::MethodIds::SMS:
|
||||
return MethodProxy{method::SMS{_session, _prompts}};
|
||||
} else if(_method == "push") {
|
||||
|
||||
case rublon::MethodIds::PUSH:
|
||||
return MethodProxy{method::PUSH{_session, _autopushPrompt}};
|
||||
} else if(_method == "email") {
|
||||
|
||||
case rublon::MethodIds::EMAIL:
|
||||
return MethodProxy{method::EMAIL{_session}};
|
||||
} else if(_method == "smsLink") {
|
||||
|
||||
case rublon::MethodIds::SmsLink:
|
||||
return MethodProxy{method::SmsLink{_session}};
|
||||
} else if(_method == "yotp") {
|
||||
/// TODO assert has token
|
||||
std::string token = rublonResponse.HasMember("token") ? rublonResponse["token"].GetString() : "";
|
||||
return MethodProxy{method::YOTP{_session, std::move(token), _prompts}};
|
||||
} else if(_method == "phoneCall") {
|
||||
|
||||
case rublon::MethodIds::YOTP:
|
||||
return MethodProxy{method::YOTP{_session, _prompts}};
|
||||
|
||||
case rublon::MethodIds::PhoneCall:
|
||||
return MethodProxy{method::PhoneCall{_session}};
|
||||
} else
|
||||
|
||||
default:
|
||||
return tl::unexpected{MethodError{MethodError::BadMethod}};
|
||||
}
|
||||
}
|
||||
|
||||
void addParams(Document & body) const {
|
||||
auto & alloc = body.GetAllocator();
|
||||
body.AddMember("method", Value{_method.c_str(), alloc}, alloc);
|
||||
body.AddMember("method", Value{MethodNames[static_cast< int >(_method)].data(), alloc}, alloc);
|
||||
}
|
||||
|
||||
public:
|
||||
const char * _name = "Confirm Method";
|
||||
|
||||
PostMethod(Session & session, std::string method, int prompts, bool autopushPrompt)
|
||||
PostMethod(Session & session, MethodIds method, int prompts, bool autopushPrompt)
|
||||
: base_t(session), _method{method}, _prompts{prompts}, _autopushPrompt{autopushPrompt} {}
|
||||
|
||||
template < typename Hander_t >
|
||||
tl::expected< MethodProxy, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const {
|
||||
auto createMethod = [&](const auto & coreResponse) { return this->createMethod(coreResponse); };
|
||||
auto readAccessToken = [&](const auto & coreResponse) { return this->readAccessToken(coreResponse); };
|
||||
auto createMethod = [&]() { return this->createMethod(); };
|
||||
|
||||
RapidJSONPMRStackAlloc< 2 * 1024 > alloc{};
|
||||
Document body{rapidjson::kObjectType, &alloc};
|
||||
@ -96,6 +119,7 @@ class PostMethod : public AuthenticationStep {
|
||||
|
||||
return coreHandler
|
||||
.request(alloc, uri, body) //
|
||||
.and_then(readAccessToken)
|
||||
.and_then(createMethod);
|
||||
}
|
||||
};
|
||||
@ -106,35 +130,36 @@ class MethodSelect {
|
||||
int _prompts;
|
||||
bool _autopushPrompt;
|
||||
|
||||
std::vector< std::string > _methodsAvailable; // TODO pmr
|
||||
std::pmr::memory_resource * _mr;
|
||||
|
||||
// method name is really short, there is almost no chance that thos strings will allocate
|
||||
std::pmr::vector< std::pmr::string > _methodsAvailable;
|
||||
|
||||
public:
|
||||
template < typename Array_t >
|
||||
MethodSelect(Session & session, const Array_t & methodsAvailableForUser, int prompts, bool autopushPrompt)
|
||||
: _session{session}, _prompts{prompts}, _autopushPrompt{autopushPrompt} {
|
||||
MethodSelect(Session & session, const Array_t & methodsEnabledInCore, int prompts, bool autopushPrompt)
|
||||
: _session{session}, _prompts{prompts}, _autopushPrompt{autopushPrompt}, _mr{memory::default_resource()}, _methodsAvailable{_mr} {
|
||||
rublon::log(LogLevel::Debug, "Checking what methods from core are supported");
|
||||
using namespace std::string_view_literals;
|
||||
memory::MonotonicStackResource< 2024 > stackResource;
|
||||
std::pmr::vector< std::string_view > _methods;
|
||||
_methodsAvailable.reserve(std::size(methodsAvailableForUser));
|
||||
|
||||
std::pmr::set< std::string_view > methodsSupported{
|
||||
{"totp"sv, "email"sv, "yotp"sv, "sms"sv, "push"sv, "smsLink"sv, "phoneCall"sv}, &stackResource};
|
||||
_methodsAvailable.reserve(std::size(methodsEnabledInCore));
|
||||
|
||||
transform_if(
|
||||
std::begin(methodsAvailableForUser),
|
||||
std::end(methodsAvailableForUser),
|
||||
std::begin(methodsEnabledInCore),
|
||||
std::end(methodsEnabledInCore),
|
||||
std::back_inserter(_methodsAvailable),
|
||||
[&](const auto & method) { return method.GetString(); },
|
||||
[&](const auto & method) { return methodsSupported.find(method.GetString()) != methodsSupported.end(); });
|
||||
[&](const auto & method) { return std::find(MethodNames.begin(), MethodNames.end(), method.GetString()) != MethodNames.end(); });
|
||||
|
||||
rublon::log(LogLevel::Debug, "User has %d methods available", _methodsAvailable.size());
|
||||
}
|
||||
|
||||
tl::expected< PostMethod, Error > create(Pam_t & pam) const {
|
||||
rublon::log(LogLevel::Debug, "prompting user to select method");
|
||||
memory::StrictMonotonic_4k_HeapResource memoryResource;
|
||||
std::pmr::map< int, std::string > methods_id{&memoryResource}; /// TODO pmr
|
||||
memory::Monotonic_2k_Resource memoryResource;
|
||||
|
||||
std::pmr::map< int, MethodIds > methods_id{&memoryResource};
|
||||
std::pmr::map< int, std::pmr::string > methods_names{&memoryResource};
|
||||
|
||||
int prompts = _prompts;
|
||||
|
||||
if(_methodsAvailable.size() == 0) {
|
||||
@ -147,65 +172,95 @@ class MethodSelect {
|
||||
auto logMethodAvailable = [](auto & method) { //
|
||||
rublon::log(LogLevel::Debug, "Method %s found", method.c_str());
|
||||
};
|
||||
auto logMethodSkippedInteractive = [](auto & method) {
|
||||
rublon::log(LogLevel::Debug, "Method %s found, but skipped due non interactive mode", method.c_str());
|
||||
};
|
||||
auto logMethodNotAvailable = [](auto & method) {
|
||||
rublon::log(LogLevel::Debug, "Method %s found, but has no handler", method.c_str());
|
||||
};
|
||||
|
||||
auto printAvailableMethods = [&]() -> tl::expected< int, MethodError > {
|
||||
rublon::log(LogLevel::Trace, "Printing available methods");
|
||||
int i{};
|
||||
for(const auto & method : _methodsAvailable) {
|
||||
if(method == "totp") {
|
||||
// skipped in non interactive
|
||||
if(_session.inNonInteractiveMode()) {
|
||||
logMethodSkippedInteractive(method);
|
||||
continue;
|
||||
}
|
||||
logMethodAvailable(method);
|
||||
if(_session.inInteractiveMode())
|
||||
pam.print("%d: Passcode", i + 1);
|
||||
methods_id[++i] = method;
|
||||
methods_id[++i] = MethodIds::OTP;
|
||||
methods_names[i] = "Passcode";
|
||||
continue;
|
||||
}
|
||||
|
||||
if(method == "email") {
|
||||
// available in non interactive
|
||||
logMethodAvailable(method);
|
||||
if(_session.inInteractiveMode())
|
||||
pam.print("%d: Email Link", i + 1);
|
||||
methods_id[++i] = method;
|
||||
methods_id[++i] = MethodIds::EMAIL;
|
||||
methods_names[i] = "Email Link";
|
||||
continue;
|
||||
}
|
||||
|
||||
if(method == "yotp") {
|
||||
// skipped in non interactive
|
||||
if(_session.inNonInteractiveMode()) {
|
||||
logMethodSkippedInteractive(method);
|
||||
continue;
|
||||
}
|
||||
logMethodAvailable(method);
|
||||
if(_session.inInteractiveMode())
|
||||
pam.print("%d: YubiKey OTP Security Key", i + 1);
|
||||
methods_id[++i] = method;
|
||||
methods_id[++i] = MethodIds::YOTP;
|
||||
methods_names[i] = "YubiKey OTP Security Key";
|
||||
continue;
|
||||
}
|
||||
|
||||
if(method == "sms") {
|
||||
// skipped in non interactive
|
||||
if(_session.inNonInteractiveMode()) {
|
||||
logMethodSkippedInteractive(method);
|
||||
continue;
|
||||
}
|
||||
logMethodAvailable(method);
|
||||
if(_session.inInteractiveMode())
|
||||
pam.print("%d: SMS Passcode", i + 1);
|
||||
methods_id[++i] = method;
|
||||
methods_id[++i] = MethodIds::SMS;
|
||||
methods_names[i] = "SMS Passcode";
|
||||
continue;
|
||||
}
|
||||
|
||||
if(method == "push") {
|
||||
// available in non interactive
|
||||
logMethodAvailable(method);
|
||||
if(_session.inInteractiveMode())
|
||||
pam.print("%d: Mobile Push", i + 1);
|
||||
methods_id[++i] = method;
|
||||
methods_id[++i] = MethodIds::PUSH;
|
||||
methods_names[i] = "Mobile Push";
|
||||
continue;
|
||||
}
|
||||
|
||||
if(method == "smsLink") {
|
||||
// available in non interactive
|
||||
logMethodAvailable(method);
|
||||
if(_session.inInteractiveMode())
|
||||
pam.print("%d: SMS Link", i + 1);
|
||||
methods_id[++i] = method;
|
||||
methods_id[++i] = MethodIds::SmsLink;
|
||||
methods_names[i] = "SMS Link";
|
||||
continue;
|
||||
}
|
||||
|
||||
if(method == "phoneCall") {
|
||||
// available in non interactive
|
||||
logMethodAvailable(method);
|
||||
if(_session.inInteractiveMode())
|
||||
pam.print("%d: Phone Call", i + 1);
|
||||
methods_id[++i] = method;
|
||||
methods_id[++i] = MethodIds::PhoneCall;
|
||||
methods_names[i] = "Phone Call";
|
||||
continue;
|
||||
}
|
||||
@ -225,8 +280,8 @@ class MethodSelect {
|
||||
};
|
||||
|
||||
const auto createMethod = [&](std::uint32_t methodid) -> tl::expected< PostMethod, MethodError > {
|
||||
rublon::log(LogLevel::Trace, "Create method %d", methods_id);
|
||||
auto hasMethod = methods_id.find(methodid) != methods_id.end();
|
||||
// pam.print("\t selected: %s", hasMethod ? methods_id.at(methodid).c_str() : "unknown option");
|
||||
if(!hasMethod) {
|
||||
log(LogLevel::Error, "User selected option %d, which is not correct", methodid);
|
||||
return tl::unexpected{MethodError(MethodError::BadMethod)};
|
||||
@ -237,14 +292,36 @@ class MethodSelect {
|
||||
};
|
||||
|
||||
const auto askForMethod = [&](int methods_number) -> tl::expected< uint32_t, MethodError > {
|
||||
if(not _session.inInteractiveMode()) {
|
||||
log(LogLevel::Debug, "Non interactive method selection");
|
||||
/// TODO refactor, this is ugly
|
||||
auto nonInteractiveMethodsPriority =
|
||||
make_array< MethodIds >(MethodIds::PUSH, MethodIds::EMAIL, MethodIds::SmsLink, MethodIds::PhoneCall);
|
||||
|
||||
for(const auto methodid : nonInteractiveMethodsPriority) {
|
||||
for(auto [key, id] : methods_id) {
|
||||
if(id == methodid) {
|
||||
log(LogLevel::Debug,
|
||||
"Automatically selected authentication method: %s due to working in noninteractive mode",
|
||||
methods_names.at(key).c_str());
|
||||
|
||||
pam.print("Non-interctive mode enabled, choose method: %s", methods_names.at(key).c_str());
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tl::unexpected{MethodError(MethodError::BadMethod)};
|
||||
} else {
|
||||
if(methods_number == 1) {
|
||||
pam.print("Automatically selected the only available authentication method: %s", methods_id.at(1).c_str());
|
||||
pam.print("Automatically selected the only available authentication method: %s", methods_names.at(1).c_str());
|
||||
return 1;
|
||||
}
|
||||
return pam.scan(conv::to_uint32, "\nSelect method [1-%d]: ", methods_number).transform_error(toMethodError);
|
||||
}
|
||||
};
|
||||
|
||||
auto reducePromptCount = [&](int selected_method) -> tl::expected< uint32_t, MethodError > {
|
||||
rublon::log(LogLevel::Trace, "User has %d prompts available", prompts);
|
||||
prompts--;
|
||||
return selected_method;
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "rublon/memory.hpp"
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
#include <rublon/authentication_step_interface.hpp>
|
||||
@ -12,8 +13,8 @@ namespace rublon::method {
|
||||
|
||||
class PasscodeBasedAuth : public AuthenticationStep {
|
||||
protected:
|
||||
const char * uri;
|
||||
const char * confirmField;
|
||||
const char * _uri;
|
||||
const char * _confirmField;
|
||||
|
||||
static constexpr const char * confirmCodeEndpoint = "/api/transaction/confirmCode";
|
||||
static constexpr const char * confirmSecuritySSHEndpoint = "/api/transaction/confirmSecurityKeySSH";
|
||||
@ -23,13 +24,13 @@ class PasscodeBasedAuth : public AuthenticationStep {
|
||||
|
||||
static constexpr auto _bypassCodeLength = 9;
|
||||
|
||||
const char * userMessage{nullptr};
|
||||
const char * _userMessage{nullptr};
|
||||
|
||||
const uint_fast8_t vericodeLength;
|
||||
const bool onlyDigits;
|
||||
const uint_fast8_t _vericodeLength;
|
||||
const bool _onlyDigits;
|
||||
int _prompts;
|
||||
|
||||
constexpr static bool isdigit(char ch) {
|
||||
static bool isdigit(char ch) {
|
||||
return std::isdigit(static_cast< unsigned char >(ch));
|
||||
}
|
||||
|
||||
@ -38,17 +39,17 @@ class PasscodeBasedAuth : public AuthenticationStep {
|
||||
}
|
||||
|
||||
bool hasValidLength(std::string_view userInput) const {
|
||||
if(userInput.size() == vericodeLength || userInput.size() == _bypassCodeLength) {
|
||||
if(userInput.size() == _vericodeLength || userInput.size() == _bypassCodeLength) {
|
||||
log(LogLevel::Debug, "User input size %d is correct", userInput.size());
|
||||
return true;
|
||||
} else {
|
||||
log(LogLevel::Warning, "User input size %d is different than %d", userInput.size(), vericodeLength);
|
||||
log(LogLevel::Warning, "User input size %d is different than %d", userInput.size(), _vericodeLength);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool hasValidCharacters(std::string_view userInput) const {
|
||||
if(onlyDigits ? digitsOnly(userInput) : true) {
|
||||
if(_onlyDigits ? digitsOnly(userInput) : true) {
|
||||
log(LogLevel::Debug, "User input contains valid characters");
|
||||
return true;
|
||||
} else {
|
||||
@ -58,15 +59,17 @@ class PasscodeBasedAuth : public AuthenticationStep {
|
||||
}
|
||||
|
||||
tl::expected< std::reference_wrapper< Document >, Error > readPasscode(Document & body, const Pam_t & pam) const {
|
||||
/// TODO assert in interactive mode
|
||||
memory::MonotonicStackResource< 100 > memoryResource;
|
||||
auto & alloc = body.GetAllocator();
|
||||
auto vericode = pam.scan([](const char * userInput) { return std::string{userInput}; }, userMessage);
|
||||
auto vericode = pam.scan([&](const char * userInput) { return std::pmr::string{userInput, &memoryResource}; }, _userMessage);
|
||||
|
||||
if(hasValidLength(vericode) and hasValidCharacters(vericode)) {
|
||||
Value confirmFieldValue(confirmField, alloc);
|
||||
Value confirmFieldValue(_confirmField, alloc);
|
||||
body.AddMember(confirmFieldValue, Value{vericode.c_str(), alloc}, alloc);
|
||||
|
||||
if(token.size()) {
|
||||
this->addAccessToken(body, token);
|
||||
if(_session.hasAccessToken()) {
|
||||
this->addAccessToken(body, _session.accessToken());
|
||||
}
|
||||
return body;
|
||||
}
|
||||
@ -79,19 +82,6 @@ class PasscodeBasedAuth : public AuthenticationStep {
|
||||
return AuthenticationStatus{AuthenticationStatus::Action::Confirmed};
|
||||
}
|
||||
|
||||
template < typename Hander_t >
|
||||
tl::expected< AuthenticationStatus, Error > waitForCoreConfirmation(const CoreHandlerInterface< Hander_t > & eventListener) const {
|
||||
log(LogLevel::Info, "Listening to confirmation event in PasscodeBasedAuth");
|
||||
|
||||
auto event = eventListener.listen();
|
||||
if(event.status == transactionConfirmed ){
|
||||
log(LogLevel::Debug, " transaction confirmed jupi");
|
||||
return AuthenticationStatus{AuthenticationStatus::Action::Confirmed, std::string{event.accessToken.value().c_str()}};
|
||||
}
|
||||
log(LogLevel::Debug, " transaction denied");
|
||||
return AuthenticationStatus{AuthenticationStatus::Action::Denied};
|
||||
}
|
||||
|
||||
tl::expected< AuthenticationStatus, Error > errorHandler(Error error, const Pam_t & pam, int promptLeft) const {
|
||||
if(promptLeft && error.is< WerificationError >()) {
|
||||
switch(error.get< WerificationError >().errorClass) {
|
||||
@ -107,6 +97,9 @@ class PasscodeBasedAuth : public AuthenticationStep {
|
||||
case WerificationError::TooManyRequestsException:
|
||||
pam.print("Too Many Attempts. Try again after a minute");
|
||||
break;
|
||||
case WerificationError::SendPushException:
|
||||
// if there is a communication problem we can't do anything here
|
||||
break;
|
||||
}
|
||||
}
|
||||
return tl::unexpected{error};
|
||||
@ -114,13 +107,11 @@ class PasscodeBasedAuth : public AuthenticationStep {
|
||||
|
||||
public:
|
||||
const char * _name;
|
||||
std::string token; // TODO StaticString
|
||||
|
||||
enum class Endpoint { ConfirmCode, SecurityKeySSH };
|
||||
|
||||
PasscodeBasedAuth( //
|
||||
Session & session,
|
||||
std::string token,
|
||||
const char * _name,
|
||||
const char * userMessage,
|
||||
|
||||
@ -129,14 +120,13 @@ class PasscodeBasedAuth : public AuthenticationStep {
|
||||
Endpoint endpoint,
|
||||
int prompts)
|
||||
: AuthenticationStep(session),
|
||||
uri{(endpoint == Endpoint::ConfirmCode) ? confirmCodeEndpoint : confirmSecuritySSHEndpoint},
|
||||
confirmField{(endpoint == Endpoint::ConfirmCode) ? fieldVericode : fieldOtp},
|
||||
userMessage{userMessage},
|
||||
vericodeLength{length},
|
||||
onlyDigits{numbersOnly},
|
||||
_uri{(endpoint == Endpoint::ConfirmCode) ? confirmCodeEndpoint : confirmSecuritySSHEndpoint},
|
||||
_confirmField{(endpoint == Endpoint::ConfirmCode) ? fieldVericode : fieldOtp},
|
||||
_userMessage{userMessage},
|
||||
_vericodeLength{length},
|
||||
_onlyDigits{numbersOnly},
|
||||
_prompts{prompts},
|
||||
_name{_name},
|
||||
token{std::move(token)} {}
|
||||
_name{_name} {}
|
||||
|
||||
template < typename Hander_t >
|
||||
tl::expected< AuthenticationStatus, Error > verify(const CoreHandlerInterface< Hander_t > & coreHandler, const Pam_t & pam) const {
|
||||
@ -144,7 +134,7 @@ class PasscodeBasedAuth : public AuthenticationStep {
|
||||
Document body{rapidjson::kObjectType, &alloc};
|
||||
int prompts = _prompts;
|
||||
|
||||
const auto requestAuthorization = [&](const auto & body) { return coreHandler.request(alloc, uri, body); };
|
||||
const auto requestAuthorization = [&](const auto & body) { return coreHandler.request(alloc, _uri, body); };
|
||||
const auto checkCodeValidity = [&](const auto & coreResponse) { return this->checkAuthenticationStatus(coreResponse, pam); };
|
||||
const auto waitForCoreToConfirm = [&](const auto &) { return waitForCoreConfirmation(coreHandler); };
|
||||
const auto handleError = [&](const auto error) { return errorHandler(error, pam, prompts); };
|
||||
|
||||
@ -6,12 +6,25 @@
|
||||
#include <rublon/bits.hpp>
|
||||
#include <rublon/configuration.hpp>
|
||||
#include <rublon/pam_action.hpp>
|
||||
|
||||
#include <rublon/websockets.hpp>
|
||||
|
||||
namespace rublon::method {
|
||||
|
||||
class WebsocketBasedAuth : public AuthenticationStep {
|
||||
tl::expected< void, Error > prompt(const Pam_t & pam) const {
|
||||
if(not _session.inInteractiveMode()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if(not _autopushPrompt)
|
||||
pam.print("Autopush");
|
||||
else
|
||||
pam.scan([](const auto /*ignored userinput*/) { return ""; },
|
||||
"Rublon authentication initiated. Complete the authentication and press Enter to proceed");
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
public:
|
||||
const char * _name = "";
|
||||
const bool _autopushPrompt = true;
|
||||
@ -19,24 +32,13 @@ class WebsocketBasedAuth : public AuthenticationStep {
|
||||
WebsocketBasedAuth(Session & session, const char * name, bool autopushPrompt = true)
|
||||
: AuthenticationStep(session), _name{name}, _autopushPrompt{autopushPrompt} {}
|
||||
|
||||
/// TODO refactor this code
|
||||
template < typename Hander_t >
|
||||
tl::expected< AuthenticationStatus, Error > verify(const CoreHandlerInterface< Hander_t > & coreHandler, const Pam_t & pam) const {
|
||||
log(LogLevel::Info, "starting WS");
|
||||
if(not _autopushPrompt)
|
||||
pam.print("Autopush");
|
||||
else
|
||||
pam.scan([](const auto /*ignored userinput*/) { return ""; },
|
||||
"Rublon authentication initiated. Complete the authentication and press Enter to proceed");
|
||||
const auto promprUser = [&]() { return prompt(pam); };
|
||||
const auto waitForCoreToConfirm = [&]() { return waitForCoreConfirmation(coreHandler); };
|
||||
|
||||
RublonEventData event = coreHandler.listen();
|
||||
|
||||
if(event.status == transactionConfirmed ){
|
||||
log(LogLevel::Debug, " transaction confirmed jupi");
|
||||
return AuthenticationStatus{AuthenticationStatus::Action::Confirmed, std::string{event.accessToken.value().c_str()}};
|
||||
}
|
||||
log(LogLevel::Debug, " transaction denied");
|
||||
return AuthenticationStatus{AuthenticationStatus::Action::Denied};
|
||||
return promprUser() //
|
||||
.and_then(waitForCoreToConfirm);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -31,5 +31,8 @@ class NonOwningPtr {
|
||||
constexpr const T * operator->() const {
|
||||
return get();
|
||||
}
|
||||
constexpr operator bool() const {
|
||||
return object != nullptr;
|
||||
}
|
||||
};
|
||||
} // namespace rublon
|
||||
|
||||
@ -1,10 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __sun
|
||||
#include <security/pam_appl.h>
|
||||
#include <security/pam_client.h>
|
||||
#include <security/pam_ext.h>
|
||||
#include <security/pam_misc.h>
|
||||
#include <security/pam_modules.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int pam_vprompt_compat(pam_handle_t *pamh, int style, char **out,
|
||||
const char *fmt, va_list ap) {
|
||||
const struct pam_conv *conv = NULL;
|
||||
if (pam_get_item(pamh, PAM_CONV, (void **)&conv) != PAM_SUCCESS || !conv || !conv->conv)
|
||||
return PAM_SYSTEM_ERR;
|
||||
|
||||
char buf[1024];
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
|
||||
struct pam_message msg = { .msg_style = style, .msg = buf };
|
||||
struct pam_message *pmsg[1] = { &msg };
|
||||
struct pam_response *resp = NULL;
|
||||
|
||||
int r = conv->conv(1, pmsg, &resp, conv->appdata_ptr);
|
||||
if (r != PAM_SUCCESS) return r;
|
||||
|
||||
if (out) {
|
||||
if (resp && resp[0].resp) *out = resp[0].resp; else *out = NULL;
|
||||
}
|
||||
if (resp) free(resp); // NIE zwalniaj *out; zwolni to wywołujący
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
static int pam_prompt_compat(pam_handle_t *pamh, int style, char **out, const char *fmt, ...) {
|
||||
va_list ap; va_start(ap, fmt);
|
||||
int r = pam_vprompt_compat(pamh, style, out, fmt, ap);
|
||||
va_end(ap);
|
||||
return r;
|
||||
}
|
||||
|
||||
#define pam_prompt pam_prompt_compat
|
||||
|
||||
#else
|
||||
#include <security/pam_appl.h>
|
||||
#include <security/pam_ext.h>
|
||||
#endif
|
||||
|
||||
#include <rublon/non_owning_ptr.hpp>
|
||||
#include <rublon/utils.hpp>
|
||||
@ -13,35 +50,51 @@ namespace rublon {
|
||||
class LinuxPam {
|
||||
pam_handle_t * pamh;
|
||||
|
||||
bool _noninteractive{false};
|
||||
|
||||
public:
|
||||
LinuxPam(pam_handle_t * handler) : pamh{handler} {}
|
||||
|
||||
void enableNoninteractive(){
|
||||
_noninteractive = true;
|
||||
}
|
||||
|
||||
rublon::NonOwningPtr< const char > ip() const {
|
||||
#ifdef __sun
|
||||
void * ip = NULL;
|
||||
#else
|
||||
const void * ip = NULL;
|
||||
#endif
|
||||
pam_get_item(pamh, PAM_RHOST, &ip);
|
||||
if(ip == NULL) {
|
||||
rublon::log(rublon::LogLevel::Warning, "Cant read user from linux PAM");
|
||||
ip = "";
|
||||
rublon::log(rublon::LogLevel::Warning, "Cant read ip from linux PAM");
|
||||
return "";
|
||||
}
|
||||
return ( const char * ) ip;
|
||||
}
|
||||
|
||||
rublon::NonOwningPtr< const char > username() const {
|
||||
#ifdef __sun
|
||||
char * user = NULL;
|
||||
#else
|
||||
const char * user = NULL;
|
||||
#endif
|
||||
pam_get_user(pamh, &user, nullptr);
|
||||
if(user == NULL) {
|
||||
rublon::log(rublon::LogLevel::Warning, "Cant read user from linux PAM");
|
||||
user = "";
|
||||
return "";
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
template < typename... Ti >
|
||||
void print(const char * fmt, Ti... ti) const noexcept {
|
||||
if(_noninteractive){
|
||||
log(LogLevel::Info, "pam_print ommited due working in noninteractive mode");
|
||||
return;
|
||||
}
|
||||
char buf[256] = {};
|
||||
sprintf(buf, fmt, std::forward< Ti >(ti)...);
|
||||
log(LogLevel::Debug, "pam_print: '%s'", buf);
|
||||
|
||||
if(auto r = pam_prompt(pamh, PAM_TEXT_INFO, nullptr, fmt, std::forward< Ti >(ti)...); r != PAM_SUCCESS) {
|
||||
log(LogLevel::Error, "pam_print returned with error code %d", r);
|
||||
}
|
||||
@ -49,6 +102,7 @@ class LinuxPam {
|
||||
|
||||
template < typename Fun, typename... Ti >
|
||||
auto scan(Fun && f, const char * fmt, Ti... ti) const noexcept {
|
||||
assert(_noninteractive == false);
|
||||
char * response = nullptr;
|
||||
pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &response, fmt, std::forward< Ti >(ti)...);
|
||||
if(response) {
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <rublon/stdlib.hpp>
|
||||
#include <rublon/static_string.hpp>
|
||||
|
||||
namespace rublon {
|
||||
|
||||
class AuthenticationStatus {
|
||||
public:
|
||||
using Token_t = std::optional< StaticString< 64 > >;
|
||||
enum class Action { Denied, Confirmed, Bypass };
|
||||
|
||||
AuthenticationStatus(Action action, std::string authenticationToken = "")
|
||||
: _action{action}, _authenticationToken{std::move(authenticationToken)} {}
|
||||
AuthenticationStatus(Action action, const char * token = nullptr)
|
||||
: _action{action}, _authenticationToken{token == nullptr ? Token_t{std::nullopt} : Token_t{token}} {}
|
||||
|
||||
constexpr bool userAuthorized() const {
|
||||
return _action == Action::Confirmed;
|
||||
@ -20,12 +22,14 @@ class AuthenticationStatus {
|
||||
}
|
||||
|
||||
std::string_view accessToken() const {
|
||||
return _authenticationToken;
|
||||
if(not _authenticationToken)
|
||||
return "";
|
||||
return {_authenticationToken->c_str(), _authenticationToken->size()};
|
||||
}
|
||||
|
||||
private:
|
||||
Action _action;
|
||||
std::string _authenticationToken; /// TODO dynamic mem
|
||||
Token_t _authenticationToken;
|
||||
};
|
||||
|
||||
} // namespace rublon
|
||||
|
||||
@ -42,5 +42,9 @@ class PamStub {
|
||||
}
|
||||
return std::result_of_t< Fun(char *) >();
|
||||
}
|
||||
|
||||
void enableNoninteractive(){
|
||||
|
||||
}
|
||||
};
|
||||
} // namespace rublon
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "rublon/memory.hpp"
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
#include <rublon/bits.hpp>
|
||||
@ -11,17 +12,28 @@ namespace rublon {
|
||||
|
||||
class RublonFactory {
|
||||
public:
|
||||
tl::expected< Session, Error > startSession(const Pam_t & pam) {
|
||||
details::initLog();
|
||||
tl::expected< void, Error > initializeSession(Session & session, [[maybe_unused]]int flags, int argc, const char ** argv) {
|
||||
log(LogLevel::Debug, "Configuration read start");
|
||||
memory::MonotonicStack_2k_Resource memory_resource;
|
||||
ConfigurationReader reader{&memory_resource, "/etc/rublon.config", argc, argv};
|
||||
|
||||
auto config = ConfigurationFactory{}.systemConfig();
|
||||
|
||||
if(not config.has_value()) {
|
||||
pam.print("The configuration file does not exist or contains incorrect values");
|
||||
if(auto ok = reader.applyTo(session.config()); not ok) {
|
||||
log(LogLevel::Warning, "Configuration contains errors");
|
||||
session.pam().print("The configuration file does not exist or contains incorrect values");
|
||||
return tl::unexpected{ConfigurationError{}};
|
||||
}
|
||||
|
||||
return Session{pam, config.value()};
|
||||
log(LogLevel::Debug, "Configuration read success");
|
||||
|
||||
// if(flags & PAM_SILENT) {
|
||||
// log(LogLevel::Debug, "nonInteractiveMode enabled (PAM_SILENT flag is set)");
|
||||
// session.switchToNonInteractiveMode();
|
||||
// }
|
||||
|
||||
if(session.inNonInteractiveMode())
|
||||
session.updateInteractiveFlag();
|
||||
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,41 +1,122 @@
|
||||
#pragma once
|
||||
|
||||
#include "rublon/utils.hpp"
|
||||
#include <rapidjson/document.h>
|
||||
#include <rublon/bits.hpp>
|
||||
#include <rublon/configuration.hpp>
|
||||
#include <rublon/json.hpp>
|
||||
#include <string_view>
|
||||
#include <rublon/memory.hpp>
|
||||
#include <rublon/utils.hpp>
|
||||
|
||||
namespace rublon {
|
||||
|
||||
class Session {
|
||||
const Pam_t & _pam;
|
||||
const Configuration _config;
|
||||
// class LoggingMemoryResource : public std::pmr::memory_resource {
|
||||
// public:
|
||||
// LoggingMemoryResource(std::pmr::memory_resource* upstream = std::pmr::get_default_resource())
|
||||
// : upstream_(upstream), allocated_bytes_(0)
|
||||
// {
|
||||
// pid_t pid = getpid();
|
||||
// std::ostringstream filename;
|
||||
// filename << "/tmp/memory" << pid << ".log";
|
||||
// log_file_.open(filename.str(), std::ios::out | std::ios::trunc);
|
||||
|
||||
std::pmr::string _tid;
|
||||
// if (!log_file_) {
|
||||
// throw std::runtime_error("Failed to open log file");
|
||||
// }
|
||||
// log("Memory logging started.");
|
||||
// }
|
||||
|
||||
CoreHandler_t _coreHandler;
|
||||
/// TODO log
|
||||
/// TODO momory resource
|
||||
// ~LoggingMemoryResource() override {
|
||||
// log("Memory logging ended.");
|
||||
// log_file_.close();
|
||||
// }
|
||||
|
||||
// protected:
|
||||
// void* do_allocate(std::size_t bytes, std::size_t alignment) override {
|
||||
// std::lock_guard<std::mutex> lock(mutex_);
|
||||
// void* ptr = upstream_->allocate(bytes, alignment);
|
||||
// allocated_bytes_ += bytes;
|
||||
// active_allocations_[ptr] = bytes;
|
||||
|
||||
// log("ALLOC", ptr, bytes, alignment);
|
||||
// return ptr;
|
||||
// }
|
||||
|
||||
// void do_deallocate(void* ptr, std::size_t bytes, std::size_t alignment) override {
|
||||
// std::lock_guard<std::mutex> lock(mutex_);
|
||||
// auto it = active_allocations_.find(ptr);
|
||||
// if (it != active_allocations_.end()) {
|
||||
// allocated_bytes_ -= it->second;
|
||||
// active_allocations_.erase(it);
|
||||
// }
|
||||
|
||||
// log("FREE", ptr, bytes, alignment);
|
||||
// upstream_->deallocate(ptr, bytes, alignment);
|
||||
// }
|
||||
|
||||
// bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
|
||||
// return this == &other;
|
||||
// }
|
||||
|
||||
// private:
|
||||
// std::pmr::memory_resource* upstream_;
|
||||
// std::ofstream log_file_;
|
||||
// std::mutex mutex_;
|
||||
// std::size_t allocated_bytes_;
|
||||
// std::map<void*, std::size_t> active_allocations_;
|
||||
|
||||
// void log(const std::string& action, void* ptr = nullptr, std::size_t bytes = 0, std::size_t alignment = 0) {
|
||||
// auto now = std::chrono::system_clock::now();
|
||||
// auto now_time = std::chrono::system_clock::to_time_t(now);
|
||||
// log_file_ << std::put_time(std::localtime(&now_time), "%F %T")
|
||||
// << " | " << std::setw(6) << action;
|
||||
|
||||
// if (ptr) {
|
||||
// log_file_ << " | ptr=" << ptr << " bytes=" << bytes << " align=" << alignment;
|
||||
// }
|
||||
|
||||
// log_file_ << " | total=" << allocated_bytes_ << " bytes\n";
|
||||
// log_file_.flush();
|
||||
// }
|
||||
// };
|
||||
|
||||
// std::pmr::unsynchronized_pool_resource resource{};
|
||||
// LoggingMemoryResource mr{&resource};
|
||||
|
||||
class DefaultResource {
|
||||
public:
|
||||
Session(const Pam_t & pam, const Configuration & config) : _pam{pam}, _config{config}, _coreHandler{_config} {
|
||||
log(LogLevel::Debug, __PRETTY_FUNCTION__);
|
||||
DefaultResource() {
|
||||
// memory::set_default_resource(&mr);
|
||||
}
|
||||
};
|
||||
|
||||
class Session : public DefaultResource {
|
||||
std::pmr::memory_resource * mr;
|
||||
Pam_t & _pam;
|
||||
Configuration _config;
|
||||
std::pmr::string _tid;
|
||||
std::pmr::string _accessToken;
|
||||
|
||||
public:
|
||||
Session(Pam_t & pam) : DefaultResource{}, mr{memory::default_resource()}, _pam{pam}, _config{mr}, _tid{mr}, _accessToken{mr} {
|
||||
details::initLog();
|
||||
}
|
||||
|
||||
Session(Session &&) noexcept = default;
|
||||
Session(Session &&) noexcept = delete;
|
||||
Session(const Session &) = delete;
|
||||
|
||||
Session& operator=(Session &&) noexcept = default;
|
||||
Session & operator=(Session &&) noexcept = delete;
|
||||
Session & operator=(const Session &) = delete;
|
||||
|
||||
const auto & coreHandler() const {
|
||||
return _coreHandler;
|
||||
}
|
||||
const auto & pam() const {
|
||||
constexpr const auto & pam() const {
|
||||
return _pam;
|
||||
}
|
||||
const auto & config() const {
|
||||
constexpr auto & pam() {
|
||||
return _pam;
|
||||
}
|
||||
constexpr auto & config() {
|
||||
return _config;
|
||||
}
|
||||
constexpr const auto & config() const {
|
||||
return _config;
|
||||
}
|
||||
|
||||
@ -46,13 +127,46 @@ class Session {
|
||||
return systemToken().data();
|
||||
}
|
||||
|
||||
/// TODO validate tid
|
||||
void switchToNonInteractiveMode(){
|
||||
if(inInteractiveMode()) {
|
||||
log(LogLevel::Debug, "Switched to non interactive mode");
|
||||
_config.nonInteractiveMode = true;
|
||||
_pam.enableNoninteractive();
|
||||
}
|
||||
}
|
||||
|
||||
// updates PAM configuration based on nonInteractiveMode flag in configuration
|
||||
void updateInteractiveFlag(){
|
||||
if(_config.nonInteractiveMode){
|
||||
_pam.enableNoninteractive();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool inNonInteractiveMode() const {
|
||||
return not inInteractiveMode();
|
||||
}
|
||||
|
||||
constexpr bool inInteractiveMode() const {
|
||||
return _config.nonInteractiveMode == false;
|
||||
}
|
||||
|
||||
void updateTransactionId(const Value * tid) {
|
||||
if(tid == nullptr) {
|
||||
log(LogLevel::Warning, "Transaction ID is not defined");
|
||||
} else {
|
||||
log(LogLevel::Debug, "Set transaction ID %s", tid->GetString());
|
||||
_tid = tid->GetString();
|
||||
}
|
||||
|
||||
void updateAccessToken(const Value * accessToken) {
|
||||
log(LogLevel::Debug, "AccessToken %s", accessToken->GetString());
|
||||
_accessToken = accessToken->GetString();
|
||||
}
|
||||
bool hasAccessToken() const {
|
||||
return not _accessToken.empty();
|
||||
}
|
||||
std::string_view accessToken() const {
|
||||
return _accessToken;
|
||||
}
|
||||
const char * caccessToken() const {
|
||||
return _accessToken.c_str();
|
||||
}
|
||||
|
||||
std::string_view transactionID() const {
|
||||
@ -64,12 +178,7 @@ class Session {
|
||||
}
|
||||
}
|
||||
const char * ctransactionID() const {
|
||||
if(_tid.empty()) {
|
||||
log(LogLevel::Warning, "Transaction ID is not defined, but requested");
|
||||
return "";
|
||||
} else {
|
||||
return _tid.data();
|
||||
}
|
||||
return transactionID().data();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/sha.h>
|
||||
@ -8,40 +9,37 @@
|
||||
|
||||
namespace rublon {
|
||||
|
||||
struct EVP_MD_CTX_deleter{
|
||||
void operator()(EVP_MD_CTX *ctx)const{
|
||||
EVP_MD_CTX_free(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
inline StaticString< SHA256_DIGEST_LENGTH * 2 > fileSHA256(const char * const path) {
|
||||
std::string fileContent;
|
||||
readFile(path, fileContent);
|
||||
|
||||
StaticString< SHA256_DIGEST_LENGTH * 2 > xRublon{};
|
||||
std::array< unsigned char, SHA256_DIGEST_LENGTH + 1 > hash{};
|
||||
int ret{};
|
||||
|
||||
EVP_MD_CTX * ctx;
|
||||
ctx = EVP_MD_CTX_new();
|
||||
auto ctx = std::unique_ptr<EVP_MD_CTX, EVP_MD_CTX_deleter>{EVP_MD_CTX_new()};
|
||||
|
||||
return 0;
|
||||
if(ctx == NULL)
|
||||
if(not ctx)
|
||||
goto out;
|
||||
|
||||
// EVP_X methods return 1 on success, so does this function
|
||||
// Any values other than 1 denote error
|
||||
ret = EVP_DigestInit(ctx, EVP_sha256());
|
||||
if(!ret)
|
||||
if(not EVP_DigestInit(ctx.get(), EVP_sha256()))
|
||||
goto out;
|
||||
|
||||
ret = EVP_DigestUpdate(ctx, fileContent.data(), fileContent.size());
|
||||
if(!ret)
|
||||
if(not EVP_DigestUpdate(ctx.get(), fileContent.data(), fileContent.size()))
|
||||
goto out;
|
||||
|
||||
// Provide uint* instead of NULL to get nBytes written, 32 for SHA256
|
||||
ret = EVP_DigestFinal(ctx, hash.data(), NULL);
|
||||
if(!ret)
|
||||
if(not EVP_DigestFinal(ctx.get(), hash.data(), NULL))
|
||||
goto out;
|
||||
|
||||
out:
|
||||
if(ctx != NULL)
|
||||
EVP_MD_CTX_free(ctx);
|
||||
|
||||
for(unsigned int i = 0; i < SHA256_DIGEST_LENGTH; i++)
|
||||
sprintf(&xRublon[i * 2], "%02x", ( unsigned int ) hash[i]);
|
||||
|
||||
@ -51,7 +49,7 @@ out:
|
||||
// +1 for \0
|
||||
inline StaticString< SHA256_DIGEST_LENGTH * 2 > signData(std::string_view data, std::string_view secretKey) {
|
||||
StaticString< SHA256_DIGEST_LENGTH * 2 > xRublon;
|
||||
std::array< unsigned char, EVP_MAX_MD_SIZE > md;
|
||||
std::array< unsigned char, EVP_MAX_MD_SIZE > md{};
|
||||
unsigned int md_len{};
|
||||
|
||||
HMAC(EVP_sha256(), secretKey.data(), secretKey.size(), ( unsigned const char * ) data.data(), data.size(), md.data(), &md_len);
|
||||
|
||||
@ -31,19 +31,22 @@ template < size_t N >
|
||||
class StaticString : public details::StaticStringBase {
|
||||
public:
|
||||
constexpr StaticString() = default;
|
||||
constexpr StaticString(const char (&chars)[N]) : m_str(toStdArray(chars)) {}
|
||||
constexpr StaticString(const char (&chars)[N]) : m_str(toStdArray(chars)), _size{N} {}
|
||||
|
||||
constexpr StaticString(std::array< const char, N > chars) : m_str(std::move(chars)) {}
|
||||
constexpr StaticString(std::array< const char, N > chars) : m_str(std::move(chars)), _size{N} {}
|
||||
constexpr StaticString(const char * str) {
|
||||
std::strncpy(m_str.data(), str, N);
|
||||
_size = std::min(strlen(str), N);
|
||||
std::memcpy(m_str.data(), str, _size);
|
||||
}
|
||||
|
||||
void operator=(const char * str) {
|
||||
std::strncpy(m_str.data(), str, N);
|
||||
_size = std::min(strlen(str), N);
|
||||
std::memcpy(m_str.data(), str, _size);
|
||||
}
|
||||
|
||||
void operator=(std::string_view str) {
|
||||
std::strncpy(m_str.data(), str.data(), N);
|
||||
_size = std::min(str.size(), N);
|
||||
std::memcpy(m_str.data(), str.data(), _size);
|
||||
}
|
||||
|
||||
const char * c_str() const noexcept {
|
||||
@ -67,16 +70,23 @@ class StaticString : public details::StaticStringBase {
|
||||
}
|
||||
|
||||
std::size_t size() const {
|
||||
return strlen(m_str.data());
|
||||
return _size;
|
||||
}
|
||||
|
||||
constexpr std::size_t capacity() const noexcept {
|
||||
return N - 1;
|
||||
}
|
||||
|
||||
template < std::size_t M >
|
||||
constexpr StaticString< N + M - 1 > operator+(const StaticString< M > & rhs) const {
|
||||
return join(resize< N - 1 >(m_str), rhs.m_str);
|
||||
StaticString< N > & operator+=(const char * rhs) {
|
||||
auto remaining = capacity() - _size;
|
||||
auto rhs_len = std::strlen(rhs);
|
||||
auto copy_len = std::min(rhs_len, remaining);
|
||||
|
||||
std::strncpy(data() + _size, rhs, copy_len);
|
||||
_size += copy_len;
|
||||
data()[_size] = '\0'; // null
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template < std::size_t M >
|
||||
@ -88,5 +98,12 @@ class StaticString : public details::StaticStringBase {
|
||||
|
||||
private:
|
||||
std::array< char, N + 1 > m_str{};
|
||||
std::size_t _size;
|
||||
};
|
||||
|
||||
template < size_t N >
|
||||
bool operator==(const StaticString< N > & lhs, const char * rhs) {
|
||||
return strcmp(lhs.c_str(), rhs) == 0;
|
||||
}
|
||||
|
||||
} // namespace rublon
|
||||
|
||||
@ -1,14 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <rublon/memory.hpp>
|
||||
#include <rublon/static_string.hpp>
|
||||
#include <rublon/stdlib.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tl/expected.hpp>
|
||||
#include <utility>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
|
||||
#include <security/pam_appl.h>
|
||||
#include <security/pam_modules.h>
|
||||
@ -33,7 +40,7 @@ inline bool readFile(const std::filesystem::path & path, T & destination) {
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class LogLevel { Debug, Info, Warning, Error };
|
||||
enum class LogLevel { Trace, Debug, Info, Warning, Error };
|
||||
|
||||
inline StaticString< 32 > dateStr() {
|
||||
StaticString< 32 > date;
|
||||
@ -43,45 +50,12 @@ inline StaticString< 32 > dateStr() {
|
||||
return date;
|
||||
}
|
||||
|
||||
constexpr const char * LogLevelNames[]{"Debug", "Info", "Warning", "Error"};
|
||||
constexpr const char * LogLevelNames[]{"Trace", "Debug", "Info", "Warning", "Error"};
|
||||
LogLevel g_level = LogLevel::Debug;
|
||||
constexpr bool syncLogFile = true;
|
||||
static const char * application = "";
|
||||
|
||||
namespace details {
|
||||
std::pmr::string osName(std::pmr::memory_resource * mr) {
|
||||
memory::MonotonicStackResource< 8 * 1024 > stackResource;
|
||||
|
||||
std::ifstream file(std::filesystem::path{"/etc/os-release"});
|
||||
if(not file.good())
|
||||
return {"unknown", mr};
|
||||
|
||||
std::pmr::string line{&stackResource};
|
||||
line.reserve(100);
|
||||
|
||||
while(std::getline(file, line)) {
|
||||
std::pmr::string _key{&stackResource};
|
||||
std::pmr::string _value{&stackResource};
|
||||
|
||||
if(!line.length())
|
||||
continue;
|
||||
|
||||
if(line[0] == '#' || line[0] == ';')
|
||||
continue;
|
||||
|
||||
auto posEqual = line.find('=');
|
||||
_key = line.substr(0, posEqual);
|
||||
_value = line.substr(posEqual + 1);
|
||||
|
||||
if(_key == "PRETTY_NAME") {
|
||||
_value.erase(std::remove_if(_value.begin(), _value.end(), [](auto ch) { return ch == '"'; }), _value.end());
|
||||
return {_value, mr};
|
||||
}
|
||||
}
|
||||
|
||||
return {"unknown", mr};
|
||||
}
|
||||
|
||||
constexpr const char * logPath() {
|
||||
constexpr auto path = "/var/log/rublon-ssh.log";
|
||||
return path;
|
||||
@ -112,12 +86,18 @@ namespace details {
|
||||
return logPath();
|
||||
}
|
||||
|
||||
inline void doLog(LogLevel level, const char * line) noexcept {
|
||||
inline void doLog(LogLevel level, std::string_view line) noexcept {
|
||||
auto fp = std::unique_ptr< FILE, int (*)(FILE *) >(fopen(initLog(), "a+"), fclose);
|
||||
if(fp) {
|
||||
auto newl = line.back() == '\n' ? "" : "\n";
|
||||
/// TODO add transaction ID
|
||||
fprintf(
|
||||
fp.get(), "%s %s[%s] %s\n", dateStr().c_str(), application == nullptr ? "" : application, LogLevelNames[( int ) level], line);
|
||||
fprintf(fp.get(),
|
||||
"%s %s[%s] %s%s",
|
||||
dateStr().c_str(),
|
||||
application == nullptr ? "" : application,
|
||||
LogLevelNames[( int ) level],
|
||||
line.data(),
|
||||
newl);
|
||||
if(syncLogFile)
|
||||
sync();
|
||||
}
|
||||
@ -139,33 +119,11 @@ void log(LogLevel level, const char * fmt, Ti &&... ti) noexcept {
|
||||
return;
|
||||
constexpr auto maxEntryLength = 1000;
|
||||
std::array< char, maxEntryLength > line;
|
||||
snprintf(line.data(), maxEntryLength, fmt, std::forward< Ti >(ti)...);
|
||||
details::doLog(level, line.data());
|
||||
auto len = snprintf(line.data(), maxEntryLength, fmt, std::forward< Ti >(ti)...);
|
||||
if(len>0)
|
||||
details::doLog(level, {line.data(), static_cast< std::size_t >(len)});
|
||||
}
|
||||
|
||||
class PrintUser {
|
||||
public:
|
||||
template < typename Printer >
|
||||
PrintUser(Printer & p) : _impl{std::make_unique< ModelImpl >(p)} {}
|
||||
|
||||
private:
|
||||
struct model {
|
||||
virtual ~model();
|
||||
virtual void print(std::string_view line) const = 0;
|
||||
};
|
||||
|
||||
template < typename Printer_T >
|
||||
struct ModelImpl : public model {
|
||||
ModelImpl(Printer_T & printer) : _printer{printer} {}
|
||||
void print(std::string_view line) const override {
|
||||
_printer.print(line);
|
||||
}
|
||||
Printer_T & _printer;
|
||||
};
|
||||
|
||||
std::unique_ptr< model > _impl;
|
||||
};
|
||||
|
||||
namespace conv {
|
||||
enum class Error { OutOfRange, NotANumber };
|
||||
|
||||
@ -181,14 +139,32 @@ namespace conv {
|
||||
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 {
|
||||
constexpr auto max = std::numeric_limits< uint32_t >::digits10 + 1;
|
||||
if(userinput.empty() || userinput.size() >= max)
|
||||
return std::nullopt; // Avoid large or empty inputs
|
||||
|
||||
char buffer[max] = {0};
|
||||
std::memcpy(buffer, userinput.data(), userinput.size());
|
||||
buffer[userinput.size()] = '\0'; // Ensure null termination
|
||||
|
||||
char * endptr = nullptr;
|
||||
errno = 0;
|
||||
|
||||
long result = std::strtol(buffer, &endptr, 10);
|
||||
|
||||
if(errno == ERANGE || endptr != buffer + userinput.size() || result < 0 || result > std::numeric_limits< uint32_t >::max()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return static_cast< std::uint32_t >(result);
|
||||
}
|
||||
|
||||
inline tl::expected< std::uint32_t, Error > to_uint32(std::string_view userinput) noexcept {
|
||||
auto val = to_uint32opt(userinput);
|
||||
if(val)
|
||||
return *val;
|
||||
return tl::unexpected{Error::NotANumber};
|
||||
}
|
||||
} // namespace conv
|
||||
|
||||
@ -209,15 +185,37 @@ namespace details {
|
||||
return ltrim(rtrim(s));
|
||||
}
|
||||
|
||||
template < typename StrT >
|
||||
void trimInPlace(StrT & s) {
|
||||
// Remove leading whitespace
|
||||
size_t start = 0;
|
||||
while(start < s.size() && isspace(s[start]))
|
||||
++start;
|
||||
|
||||
// Remove trailing whitespace
|
||||
size_t end = s.size();
|
||||
while(end > start && isspace(s[end - 1]))
|
||||
--end;
|
||||
|
||||
if(start > 0 || end < s.size()) {
|
||||
s = s.substr(start, end - start);
|
||||
}
|
||||
}
|
||||
|
||||
template < typename Headers >
|
||||
inline void headers(std::string_view data, Headers & headers) {
|
||||
memory::StrictMonotonic_4k_HeapResource stackResource;
|
||||
memory::Monotonic_4k_Resource stackResource;
|
||||
std::pmr::string tmp{&stackResource};
|
||||
tmp.reserve(300);
|
||||
|
||||
std::istringstream resp{};
|
||||
resp.rdbuf()->pubsetbuf(const_cast< char * >(data.data()), data.size());
|
||||
|
||||
while(std::getline(resp, tmp) && !(trim(tmp).empty())) {
|
||||
while(std::getline(resp, tmp)) {
|
||||
if(tmp == "\r")
|
||||
continue;
|
||||
if(trim(tmp).empty())
|
||||
break;
|
||||
auto line = std::string_view(tmp);
|
||||
auto index = tmp.find(':', 0);
|
||||
if(index != std::string::npos) {
|
||||
@ -228,6 +226,48 @@ namespace details {
|
||||
}
|
||||
}
|
||||
|
||||
std::pmr::string hostname(std::pmr::memory_resource * mr) {
|
||||
// longest hostname on linux is 253 characters
|
||||
char host[255]{};
|
||||
if(gethostname(host, sizeof(host)) != 0) {
|
||||
log(LogLevel::Warning, "Hostname is not available");
|
||||
return "";
|
||||
}
|
||||
return {host, mr};
|
||||
}
|
||||
|
||||
std::pmr::string osName(std::pmr::memory_resource * mr) {
|
||||
memory::Monotonic_1k_Resource memoryResource;
|
||||
|
||||
std::ifstream file(std::filesystem::path{"/etc/os-release"});
|
||||
if(not file.good())
|
||||
return {"unknown", mr};
|
||||
|
||||
std::pmr::string line{&memoryResource};
|
||||
std::pmr::string _key{&memoryResource};
|
||||
std::pmr::string _value{&memoryResource};
|
||||
line.reserve(100);
|
||||
|
||||
while(std::getline(file, line)) {
|
||||
if(!line.length())
|
||||
continue;
|
||||
|
||||
if(line[0] == '#' || line[0] == ';')
|
||||
continue;
|
||||
|
||||
auto posEqual = line.find('=');
|
||||
_key = line.substr(0, posEqual);
|
||||
_value = line.substr(posEqual + 1);
|
||||
|
||||
if(_key == "PRETTY_NAME") {
|
||||
_value.erase(std::remove_if(_value.begin(), _value.end(), [](auto ch) { return ch == '"'; }), _value.end());
|
||||
return {_value, mr};
|
||||
}
|
||||
}
|
||||
|
||||
return {"unknown", mr};
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
|
||||
template < class InputIterator, class OutputIterator, class UnaryOperator, class Pred >
|
||||
@ -250,4 +290,36 @@ struct ci_less {
|
||||
}
|
||||
};
|
||||
|
||||
template < typename Out, typename... Types >
|
||||
constexpr std::array< Out, sizeof...(Types) > make_array(Types... names) {
|
||||
return {std::forward< Types >(names)...};
|
||||
}
|
||||
|
||||
template < typename T >
|
||||
std::size_t size_buffer(const T & item) {
|
||||
using U = std::decay_t< T >;
|
||||
if constexpr(std::is_same_v< U, const char * >) {
|
||||
return strlen(item);
|
||||
} else if constexpr(std::is_same_v< U, std::pmr::string > || std::is_same_v< U, std::string >) {
|
||||
return item.size();
|
||||
} else if constexpr(std::is_integral_v< U > || std::is_floating_point_v< U >) {
|
||||
return std::numeric_limits< U >::digits;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template < typename T >
|
||||
std::size_t size_buffer(const std::optional< T > & item) {
|
||||
if(item.has_value())
|
||||
return size_buffer(*item);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// min + 10%
|
||||
template < typename... Args >
|
||||
std::size_t conservative_estimate(const Args &... args) {
|
||||
auto min = (size_buffer(args) + ...);
|
||||
return min + min * 10 / 100;
|
||||
}
|
||||
|
||||
} // namespace rublon
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "rublon/json.hpp"
|
||||
#include "rublon/memory.hpp"
|
||||
#include "rublon/static_string.hpp"
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <rublon/error.hpp>
|
||||
#include <rublon/pam_action.hpp>
|
||||
#include <rublon/utils.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <rublon/configuration.hpp>
|
||||
#include <rublon/error.hpp>
|
||||
#include <rublon/json.hpp>
|
||||
#include <rublon/memory.hpp>
|
||||
#include <rublon/pam_action.hpp>
|
||||
#include <rublon/static_string.hpp>
|
||||
#include <rublon/utils.hpp>
|
||||
|
||||
#include <libwebsockets.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@ -28,7 +29,7 @@ struct RublonEventData {
|
||||
};
|
||||
|
||||
class WebSocket {
|
||||
std::string url; /// TODO pmr
|
||||
std::reference_wrapper< const Configuration > _config;
|
||||
std::string_view urlv;
|
||||
|
||||
bool event_received = false;
|
||||
@ -40,9 +41,12 @@ class WebSocket {
|
||||
lws_client_connect_info ccinfo{};
|
||||
|
||||
RublonEventData * currentEvent{nullptr};
|
||||
std::pmr::string proxyUrl{};
|
||||
|
||||
public:
|
||||
WebSocket(std::string_view uri) : url{uri.data()}, urlv{url} {
|
||||
WebSocket(const Configuration & config) : _config{config}, urlv{_config.get().apiServer}, proxyUrl{_config.get().apiServer.get_allocator()} {
|
||||
const auto & cfg = _config.get(); // only a alias to not use _config.get() all the time
|
||||
|
||||
auto lws_log_emit = [](int level, const char * line) {
|
||||
LogLevel rlevel{};
|
||||
if(level == LLL_ERR)
|
||||
@ -59,7 +63,11 @@ class WebSocket {
|
||||
log(rlevel, "libwesockets: %s", line);
|
||||
};
|
||||
|
||||
if(_config.get().logging) {
|
||||
lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG, lws_log_emit);
|
||||
} else {
|
||||
lws_set_log_level(LLL_ERR | LLL_WARN, lws_log_emit);
|
||||
}
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
memset(&ccinfo, 0, sizeof(ccinfo));
|
||||
@ -68,10 +76,29 @@ class WebSocket {
|
||||
info.protocols = protocols;
|
||||
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
|
||||
|
||||
if(cfg.proxyEnabled && (cfg.proxyType == "http" || cfg.proxyType == "https")) {
|
||||
assert(cfg.proxyType.has_value());
|
||||
assert(cfg.proxyHost.has_value());
|
||||
log(LogLevel::Debug, "WebSocket using proxy");
|
||||
|
||||
// "username:password\@server:port"
|
||||
if(cfg.proxyAuthRequired) {
|
||||
proxyUrl.reserve(conservative_estimate(cfg.proxyUsername, cfg.proxyPass, cfg.proxyHost) + 10);
|
||||
proxyUrl += *cfg.proxyUsername;
|
||||
proxyUrl += ":";
|
||||
proxyUrl += *cfg.proxyPass;
|
||||
proxyUrl += "@";
|
||||
}
|
||||
|
||||
proxyUrl += *cfg.proxyHost;
|
||||
log(LogLevel::Debug, "WebSocket proxy %s", proxyUrl.c_str());
|
||||
info.http_proxy_address = proxyUrl.c_str();
|
||||
info.http_proxy_port = config.proxyPort.value_or(8080);
|
||||
}
|
||||
|
||||
context = lws_create_context(&info);
|
||||
|
||||
const std::string_view prefix = "https://";
|
||||
|
||||
if(urlv.substr(0, prefix.size()) == prefix)
|
||||
urlv.remove_prefix(prefix.size());
|
||||
|
||||
@ -85,7 +112,7 @@ class WebSocket {
|
||||
ccinfo.pwsi = &wsi;
|
||||
ccinfo.userdata = this;
|
||||
|
||||
log(LogLevel::Debug, "Created WS from %s", urlv.data());
|
||||
log(LogLevel::Debug, "WebSocket Created connection to %s", urlv.data());
|
||||
}
|
||||
|
||||
WebSocket(WebSocket && rhs) noexcept = default;
|
||||
@ -100,28 +127,29 @@ class WebSocket {
|
||||
}
|
||||
|
||||
bool attachToTransactionConfirmationChannel(std::string_view transaction_id) {
|
||||
/// TODO change to StaticString and +=
|
||||
std::array< char, 128 > subscribe_message{0};
|
||||
std::array< char, 128 > buf{0};
|
||||
StaticString< 128 > subscribe_message{};
|
||||
unsigned char buf[128 + LWS_PRE] = {};
|
||||
|
||||
sprintf(subscribe_message.data(), R"msg(42["subscribe",{"channel":"transactionConfirmation.%s"}])msg", transaction_id.data());
|
||||
memcpy(&buf[LWS_PRE], subscribe_message.data(), strlen(subscribe_message.data()));
|
||||
subscribe_message += R"msg(42["subscribe",{"channel":"transactionConfirmation.)msg";
|
||||
subscribe_message += transaction_id.data();
|
||||
subscribe_message += R"("}])";
|
||||
|
||||
log(LogLevel::Debug, "WS send message: %s", subscribe_message.data());
|
||||
int bytes_sent = lws_write(wsi, ( unsigned char * ) &buf[LWS_PRE], strlen(subscribe_message.data()), LWS_WRITE_TEXT);
|
||||
memcpy(buf + LWS_PRE, subscribe_message.data(), subscribe_message.size());
|
||||
|
||||
log(LogLevel::Debug, "WS send: %d bytes", bytes_sent);
|
||||
log(LogLevel::Debug, "WebSocket send message: %s", subscribe_message.c_str());
|
||||
int bytes_sent = lws_write(wsi, buf + LWS_PRE, subscribe_message.size(), LWS_WRITE_TEXT);
|
||||
|
||||
if(bytes_sent < ( int ) strlen(subscribe_message.data())) {
|
||||
log(LogLevel::Error, "Failed to send subscribe message");
|
||||
log(LogLevel::Debug, "WebSocket send: %d bytes", bytes_sent);
|
||||
|
||||
if(bytes_sent < ( int ) subscribe_message.size()) {
|
||||
log(LogLevel::Error, "WebSocket failed to send subscribe message");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AttachToCore(std::string_view tid) {
|
||||
log(LogLevel::Debug, "connecting to %s", ccinfo.address);
|
||||
/// needed here only for rublon core api URL, so 1k fine
|
||||
log(LogLevel::Debug, "WebSocket attaching to rublon api at %s", ccinfo.address);
|
||||
lws_client_connect_via_info(&ccinfo);
|
||||
|
||||
const int seconds = 10;
|
||||
@ -156,7 +184,7 @@ class WebSocket {
|
||||
const int seconds = 60;
|
||||
auto endtime = std::chrono::steady_clock::now() + std::chrono::seconds{seconds};
|
||||
|
||||
log(LogLevel::Debug, "waiting for events for %d seconds", seconds);
|
||||
log(LogLevel::Debug, "WebSocket waiting for events for %d seconds", seconds);
|
||||
while(!event_received && std::chrono::steady_clock::now() < endtime) {
|
||||
lws_service(context, 1000);
|
||||
}
|
||||
@ -179,10 +207,10 @@ class WebSocket {
|
||||
case LWS_CALLBACK_CLIENT_WRITEABLE: {
|
||||
// Perform the Socket.IO 4.x handshake (send `40` message)
|
||||
const std::string_view handshake = "40";
|
||||
unsigned char buf[64];
|
||||
unsigned char buf[64] = {};
|
||||
memcpy(&buf[LWS_PRE], handshake.data(), handshake.size());
|
||||
lws_write(wsi, &buf[LWS_PRE], handshake.size(), LWS_WRITE_TEXT);
|
||||
log(LogLevel::Debug, "Sent Socket.IO handshake");
|
||||
log(LogLevel::Debug, "WebSocket Sent Socket.IO handshake");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -201,8 +229,10 @@ class WebSocket {
|
||||
if(input.substr(0, 2) == "42") {
|
||||
/// TODO assert _this
|
||||
/// TODO assert currentEvent
|
||||
/// TODO refactor to separate class
|
||||
if(_this->currentEvent == nullptr)
|
||||
return -1;
|
||||
log(LogLevel::Debug, "WebSocket got %s", input.data());
|
||||
size_t startPos = input.find("[\"") + 2;
|
||||
size_t endPos = input.find("\",", startPos);
|
||||
auto & event = *_this->currentEvent;
|
||||
@ -217,12 +247,13 @@ class WebSocket {
|
||||
startPos = endPos + 2;
|
||||
auto jsonString = input.substr(startPos, input.length() - startPos - 1);
|
||||
|
||||
memory::Monotonic_1k_HeapResource mr;
|
||||
memory::Monotonic_1k_Resource mr;
|
||||
RapidJSONPMRAlloc alloc{&mr};
|
||||
|
||||
Document dataJson{&alloc};
|
||||
dataJson.Parse(jsonString.data(), jsonString.size());
|
||||
const auto * data = JSONPointer{"/data", &alloc}.Get(dataJson);
|
||||
const auto * redirectUrl = JSONPointer{"/redirectUrl", &alloc}.Get(dataJson);
|
||||
|
||||
if(data != nullptr) {
|
||||
Document tokenJson{&alloc};
|
||||
@ -232,18 +263,19 @@ class WebSocket {
|
||||
if(token != nullptr) {
|
||||
event.accessToken = token->GetString();
|
||||
} else {
|
||||
log(LogLevel::Debug, "got broken data");
|
||||
log(LogLevel::Error, "WebSocket response does not contain access token");
|
||||
}
|
||||
|
||||
log(LogLevel::Debug, "mark event received");
|
||||
_this->event_received = true;
|
||||
} else if(redirectUrl != nullptr) {
|
||||
log(LogLevel::Info, "WebSocket received deny message");
|
||||
_this->event_received = true;
|
||||
} else {
|
||||
log(LogLevel::Error, "event data incorrect");
|
||||
log(LogLevel::Error, "WebSocket event data incorrect");
|
||||
return -1;
|
||||
}
|
||||
|
||||
} else {
|
||||
log(LogLevel::Debug, "Not an confirmation event");
|
||||
log(LogLevel::Debug, "WebSocket Not an confirmation event");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1,10 +1,3 @@
|
||||
#include <security/pam_appl.h>
|
||||
#include <security/pam_client.h>
|
||||
#include <security/pam_ext.h>
|
||||
#include <security/pam_misc.h>
|
||||
#include <security/pam_modules.h>
|
||||
#include <syslog.h>
|
||||
|
||||
#include <rublon/check_application.hpp>
|
||||
#include <rublon/error.hpp>
|
||||
#include <rublon/error_handler.hpp>
|
||||
@ -29,22 +22,26 @@ DLL_PUBLIC int pam_sm_acct_mgmt([[maybe_unused]] pam_handle_t * pamh,
|
||||
}
|
||||
|
||||
DLL_PUBLIC int
|
||||
pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) {
|
||||
pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, const char ** argv) {
|
||||
using namespace rublon;
|
||||
details::initLog();
|
||||
LinuxPam pam{pamh};
|
||||
|
||||
log(LogLevel::Info, "user '%s' authentication attempt", pam.username().get());
|
||||
auto printAuthMessageAndExit = [&](const AuthenticationStatus status) {
|
||||
switch(status.action()) {
|
||||
case AuthenticationStatus::Action::Bypass:
|
||||
log(LogLevel::Info, "user '%s' authentication BYPASSED", pam.username().get());
|
||||
pam.print("RUBLON authentication BYPASSED");
|
||||
return PAM_SUCCESS;
|
||||
|
||||
case AuthenticationStatus::Action::Denied:
|
||||
log(LogLevel::Info, "user '%s' authentication FAILED", pam.username().get());
|
||||
pam.print("RUBLON authentication FAILED");
|
||||
return PAM_MAXTRIES;
|
||||
|
||||
case AuthenticationStatus::Action::Confirmed:
|
||||
log(LogLevel::Info, "user '%s' authentication SUCCEEDED", pam.username().get());
|
||||
pam.print("RUBLON authentication SUCCEEDED");
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
@ -53,16 +50,17 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
|
||||
return PAM_MAXTRIES;
|
||||
};
|
||||
|
||||
auto session = rublon::RublonFactory{}.startSession(pam);
|
||||
if(not session.has_value()) {
|
||||
Session session{pam};
|
||||
auto ok = rublon::RublonFactory{}.initializeSession(session, flags, argc, argv);
|
||||
if(not ok.has_value()) {
|
||||
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
|
||||
}
|
||||
|
||||
if(!session->config().logging) {
|
||||
if(!session.config().logging) {
|
||||
g_level = LogLevel::Warning;
|
||||
}
|
||||
|
||||
auto & CH = session.value().coreHandler();
|
||||
CoreHandler_t CH{session.config()};
|
||||
|
||||
auto selectMethod = [&](const MethodSelect & selector) { //
|
||||
return selector.create(pam);
|
||||
@ -73,14 +71,12 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
|
||||
};
|
||||
|
||||
auto confirmCode = [&](const MethodProxy & method) mutable { //
|
||||
return method.fire(session.value(), CH, pam);
|
||||
return method.fire(session, CH, pam);
|
||||
};
|
||||
|
||||
auto finalizeTransaction = [&](const AuthenticationStatus & status) mutable -> tl::expected< AuthenticationStatus, Error > {
|
||||
if(status.userAuthorized()) {
|
||||
auto tok = std::string{status.accessToken().data()};
|
||||
Finish finish{session.value(), std::move(tok)};
|
||||
finish.handle(CH);
|
||||
Finish{session, status.accessToken()}.handle(CH);
|
||||
}
|
||||
return status;
|
||||
};
|
||||
@ -90,20 +86,19 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
|
||||
};
|
||||
|
||||
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;
|
||||
auto ret =
|
||||
ca.call(CH, {session.value().config().systemToken.data(), session.value().config().systemToken.size()}).or_else(mapError);
|
||||
CheckApplication ca{session};
|
||||
const auto ret = ca.call(CH, session.config().systemToken).or_else(mapError);
|
||||
if(not ret.has_value()) {
|
||||
log(LogLevel::Error, "Check Application step failed, check configration");
|
||||
return PAM_MAXTRIES;
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = Init{session.value()}
|
||||
auto ret = Init{session}
|
||||
.handle(CH, pam) //
|
||||
.and_then(selectMethod)
|
||||
.and_then(confirmMethod)
|
||||
|
||||
64
PAM/ssh/patches/rapidjson.patch
Normal file
64
PAM/ssh/patches/rapidjson.patch
Normal file
@ -0,0 +1,64 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index ceda71b..0128f99 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -9,6 +9,7 @@ endif()
|
||||
|
||||
SET(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules)
|
||||
|
||||
+cmake_policy(SET CMP0048 NEW)
|
||||
PROJECT(RapidJSON CXX)
|
||||
|
||||
set(LIB_MAJOR_VERSION "1")
|
||||
|
||||
diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h
|
||||
index e3e20dfb..592c5678 100644
|
||||
--- a/include/rapidjson/document.h
|
||||
+++ b/include/rapidjson/document.h
|
||||
@@ -97,17 +97,20 @@ struct GenericMember {
|
||||
|
||||
\see GenericMember, GenericValue::MemberIterator, GenericValue::ConstMemberIterator
|
||||
*/
|
||||
+#pragma GCC diagnostic push
|
||||
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
template <bool Const, typename Encoding, typename Allocator>
|
||||
class GenericMemberIterator
|
||||
: public std::iterator<std::random_access_iterator_tag
|
||||
, typename internal::MaybeAddConst<Const,GenericMember<Encoding,Allocator> >::Type> {
|
||||
-
|
||||
+
|
||||
friend class GenericValue<Encoding,Allocator>;
|
||||
template <bool, typename, typename> friend class GenericMemberIterator;
|
||||
|
||||
typedef GenericMember<Encoding,Allocator> PlainType;
|
||||
typedef typename internal::MaybeAddConst<Const,PlainType>::Type ValueType;
|
||||
typedef std::iterator<std::random_access_iterator_tag,ValueType> BaseType;
|
||||
+#pragma GCC diagnostic pop
|
||||
|
||||
public:
|
||||
//! Iterator type itself
|
||||
@@ -1936,7 +1939,10 @@ private:
|
||||
if (count) {
|
||||
GenericValue* e = static_cast<GenericValue*>(allocator.Malloc(count * sizeof(GenericValue)));
|
||||
SetElementsPointer(e);
|
||||
+#pragma GCC diagnostic push
|
||||
+#pragma GCC diagnostic ignored "-Wclass-memaccess"
|
||||
std::memcpy(e, values, count * sizeof(GenericValue));
|
||||
+#pragma GCC diagnostic pop
|
||||
}
|
||||
else
|
||||
SetElementsPointer(0);
|
||||
@@ -1949,7 +1955,10 @@ private:
|
||||
if (count) {
|
||||
Member* m = static_cast<Member*>(allocator.Malloc(count * sizeof(Member)));
|
||||
SetMembersPointer(m);
|
||||
+#pragma GCC diagnostic push
|
||||
+#pragma GCC diagnostic ignored "-Wclass-memaccess"
|
||||
std::memcpy(m, members, count * sizeof(Member));
|
||||
+#pragma GCC diagnostic pop
|
||||
}
|
||||
else
|
||||
SetMembersPointer(0);
|
||||
diff --git a/package.json b/package.json
|
||||
index 843463d7..cc6087a5 100644
|
||||
Binary files a/package.json and b/package.json differ
|
||||
11
build.sh
Executable file
11
build.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
source_dir=$1
|
||||
build_dir=$2
|
||||
tag=#3
|
||||
|
||||
cmake --build $build_dir -- -j
|
||||
cmake --build $build_dir --target package -- -j
|
||||
|
||||
cp $build_dir/*rpm $source_dir/_tmp/packages 2>/dev/null || cp $build_dir/*deb $source_dir/_tmp/packages 2>/dev/null
|
||||
|
||||
136
build_all.py
Normal file
136
build_all.py
Normal file
@ -0,0 +1,136 @@
|
||||
import os
|
||||
import subprocess
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from pydoc import doc
|
||||
|
||||
# Configuration
|
||||
ROOT_DIR = "./os"
|
||||
MAX_WORKERS = 8 # Maximum parallel jobs for build docker image and configure project
|
||||
MAX_WORKERS_COMPILE = 2 # number of compilation jobs t run
|
||||
|
||||
def find_dockerfiles(root_dir):
|
||||
"""Recursively find all Dockerfiles in the given directory."""
|
||||
dockerfiles = []
|
||||
for dirpath, _, filenames in os.walk(root_dir):
|
||||
for filename in filenames:
|
||||
if filename == "Dockerfile":
|
||||
dockerfiles.append(os.path.join(dirpath, filename))
|
||||
return dockerfiles
|
||||
|
||||
|
||||
def tags_from_path(dockerfile):
|
||||
dir_path = os.path.dirname(dockerfile)
|
||||
tag = dir_path.replace(ROOT_DIR + "/", "").replace(os.path.sep, "_")
|
||||
return dir_path, tag
|
||||
|
||||
|
||||
def build_docker_image(dockerfile):
|
||||
"""Build a Docker image for the given Dockerfile."""
|
||||
dir_path, tag = tags_from_path(dockerfile)
|
||||
print(f"Starting build image step for {tag} (directory: {dir_path})...")
|
||||
try:
|
||||
log_file = f"_tmp/logs/{tag}/build_docker_image.log"
|
||||
os.makedirs(os.path.dirname(log_file), exist_ok=True)
|
||||
with open(log_file, "w") as log:
|
||||
result = subprocess.run(
|
||||
["docker", "build", "-t", tag, dir_path],
|
||||
stdout=log,
|
||||
stderr=subprocess.STDOUT,
|
||||
check=True
|
||||
)
|
||||
return f"Successfully built {tag}"
|
||||
except subprocess.CalledProcessError:
|
||||
print(f"Failed to build {tag}. Check {log_file} for details.")
|
||||
return f"Failed to build {tag}"
|
||||
|
||||
|
||||
in_image_source_dir = "/home/rublon-ssh"
|
||||
in_image_build_dir = "/home/build"
|
||||
|
||||
def configure_rublon(dockerfile):
|
||||
"""Build a Docker image for the given Dockerfile."""
|
||||
dir_path, tag = tags_from_path(dockerfile)
|
||||
print(f"Starting configure step for {tag} (directory: {dir_path})...")
|
||||
try:
|
||||
log_file = f"_tmp/logs/{tag}/configure_rublon_image.log"
|
||||
build_dir = f"_tmp/builds/{tag}"
|
||||
os.makedirs(os.path.dirname(log_file), exist_ok=True)
|
||||
os.makedirs(os.path.dirname(build_dir), exist_ok=True)
|
||||
with open(log_file, "w") as log:
|
||||
result = subprocess.run(
|
||||
# $tag /bin/bash /home/rublon-ssh/configure.sh $tag
|
||||
["docker", "run",
|
||||
"-v", f"./:{in_image_source_dir}",
|
||||
"-v", f"./{build_dir}:{in_image_build_dir}",
|
||||
tag,
|
||||
f"{in_image_source_dir}/configure.sh", in_image_source_dir, in_image_build_dir
|
||||
],
|
||||
stdout=log,
|
||||
stderr=subprocess.STDOUT,
|
||||
check=True
|
||||
)
|
||||
return f"Successfully built {tag}"
|
||||
except subprocess.CalledProcessError:
|
||||
print(f"Failed to build {tag}. Check {log_file} for details.")
|
||||
return f"Failed to build {tag}"
|
||||
|
||||
|
||||
def build_rublon(dockerfile):
|
||||
"""Build a Docker image for the given Dockerfile."""
|
||||
dir_path, tag = tags_from_path(dockerfile)
|
||||
print(f"Starting build for {tag} (directory: {dir_path})...")
|
||||
try:
|
||||
log_file = f"_tmp/logs/{tag}/compile_rublon_image.log"
|
||||
build_dir = f"_tmp/builds/{tag}"
|
||||
packages_dir = "_tmp/packages/"
|
||||
os.makedirs(os.path.dirname(log_file), exist_ok=True)
|
||||
os.makedirs(os.path.dirname(build_dir), exist_ok=True)
|
||||
os.makedirs(os.path.dirname(packages_dir), exist_ok=True)
|
||||
with open(log_file, "w") as log:
|
||||
result = subprocess.run(
|
||||
# $tag /bin/bash /home/rublon-ssh/configure.sh $tag
|
||||
["docker", "run",
|
||||
"-v", f"./:{in_image_source_dir}",
|
||||
"-v", f"./{build_dir}:{in_image_build_dir}",
|
||||
tag,
|
||||
f"{in_image_source_dir}/build.sh", in_image_source_dir, in_image_build_dir, tag
|
||||
],
|
||||
stdout=log,
|
||||
stderr=subprocess.STDOUT,
|
||||
check=True
|
||||
)
|
||||
return f"Successfully built {tag}"
|
||||
except subprocess.CalledProcessError:
|
||||
print(f"Failed to build {tag}. Check {log_file} for details.")
|
||||
return f"Failed to build {tag}"
|
||||
|
||||
|
||||
def run_parralel(num, job, name, dockerfiles):
|
||||
# Use ThreadPoolExecutor for parallel builds
|
||||
with ThreadPoolExecutor(max_workers=num) as executor:
|
||||
future_to_dockerfile = {executor.submit(job, df): df for df in dockerfiles}
|
||||
for future in as_completed(future_to_dockerfile):
|
||||
dockerfile = future_to_dockerfile[future]
|
||||
try:
|
||||
result = future.result()
|
||||
print(result)
|
||||
except Exception as e:
|
||||
print(f"Job {name} failed for {dockerfile}: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
# Find all Dockerfiles
|
||||
dockerfiles = find_dockerfiles(ROOT_DIR)
|
||||
if not dockerfiles:
|
||||
print("No Dockerfiles found.")
|
||||
return
|
||||
|
||||
run_parralel(MAX_WORKERS, build_docker_image, "Docker Image Build", dockerfiles)
|
||||
run_parralel(MAX_WORKERS, configure_rublon, "Configuring project", dockerfiles)
|
||||
run_parralel(MAX_WORKERS_COMPILE, build_rublon, "Building project", dockerfiles)
|
||||
|
||||
|
||||
print("All Docker builds completed.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
home_dir=$PWD
|
||||
echo $home_dir
|
||||
find . -type f -name Dockerfile | while read -r file_path; do
|
||||
dir_path=$(dirname "$file_path")
|
||||
file_name=$(echo $dir_path | cut -d'/' -f3- | sed 's|/|_|g')
|
||||
docker build -t "$file_name" $dir_path
|
||||
docker run -v `pwd`:/home/rublon-ssh/ $file_name /bin/bash /home/rublon-ssh/build_project.sh $file_name
|
||||
done
|
||||
@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
cmake -B rublon_ssh-build /home/rublon-ssh/
|
||||
cmake --build rublon_ssh-build/
|
||||
cmake --build rublon_ssh-build/ --target package -- -j
|
||||
if [ ! -d /home/rublon-ssh/$1_packages ]; then
|
||||
mkdir /home/rublon-ssh/$1_packages
|
||||
fi
|
||||
cp rublon_ssh-build/*rpm /home/rublon-ssh/$1_packages 2>/dev/null || cp rublon_ssh-build/*deb /home/rublon-ssh/$1_packages 2>/dev/null
|
||||
|
||||
7
configure.sh
Executable file
7
configure.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
source_dir=$1
|
||||
build_dir=$2
|
||||
|
||||
rm -Rf $build_dir || true
|
||||
cmake -B $build_dir -DCMAKE_BUILD_TYPE=Release $source_dir
|
||||
31
helpers/centos-base.repo
Normal file
31
helpers/centos-base.repo
Normal file
@ -0,0 +1,31 @@
|
||||
#####################################################################
|
||||
# CentOS-Base.repo
|
||||
#
|
||||
# The mirror system uses the connecting IP address of the client and the
|
||||
# update status of each mirror to pick mirrors that are updated to and
|
||||
# geographically close to the client. You should use this for CentOS updates
|
||||
# unless you are manually picking other mirrors.
|
||||
#
|
||||
# If the mirrorlist= does not work for you, as a fall back you can try the
|
||||
# remarked out baseurl= line instead.
|
||||
#
|
||||
#
|
||||
[centosApp]
|
||||
name=CentOSApp-9
|
||||
baseurl=https://yum.oracle.com/repo/OracleLinux/OL8/appstream/x86_64/
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
gpgkey=http://mirror.centos.org/centos/8/os/x86_64/RPM-GPG-KEY-CentOS-8
|
||||
[centosApp2]
|
||||
name=CentOSApp-8
|
||||
baseurl=https://yum.oracle.com/repo/OracleLinux/OL8/baseos/latest/x86_64/
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
gpgkey=http://mirror.centos.org/centos/8/os/x86_64/RPM-GPG-KEY-CentOS-8
|
||||
#[centosBase]
|
||||
#name=CentOSBase-9
|
||||
#baseurl=https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/os/
|
||||
#enabled=1
|
||||
#gpgcheck=0
|
||||
#gpgkey=http://mirror.centos.org/centos/8/os/x86_64/RPM-GPG-KEY-CentOS-8
|
||||
#####################################################################
|
||||
31
helpers/centos-base_9.repo
Normal file
31
helpers/centos-base_9.repo
Normal file
@ -0,0 +1,31 @@
|
||||
#####################################################################
|
||||
# CentOS-Base.repo
|
||||
#
|
||||
# The mirror system uses the connecting IP address of the client and the
|
||||
# update status of each mirror to pick mirrors that are updated to and
|
||||
# geographically close to the client. You should use this for CentOS updates
|
||||
# unless you are manually picking other mirrors.
|
||||
#
|
||||
# If the mirrorlist= does not work for you, as a fall back you can try the
|
||||
# remarked out baseurl= line instead.
|
||||
#
|
||||
#
|
||||
[centosApp]
|
||||
name=CentOSApp-9
|
||||
baseurl=https://mirror.stream.centos.org/9-stream/AppStream/x86_64/os/
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
gpgkey=http://mirror.centos.org/centos/8/os/x86_64/RPM-GPG-KEY-CentOS-8
|
||||
[centosApp2]
|
||||
name=CentOSAppBase-9
|
||||
baseurl=https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/os/
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
gpgkey=http://mirror.centos.org/centos/8/os/x86_64/RPM-GPG-KEY-CentOS-8
|
||||
#[centosBase]
|
||||
#name=CentOSBase-9
|
||||
#baseurl=https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/os/
|
||||
#enabled=1
|
||||
#gpgcheck=0
|
||||
#gpgkey=http://mirror.centos.org/centos/8/os/x86_64/RPM-GPG-KEY-CentOS-8
|
||||
#####################################################################
|
||||
34
os/alma/8/Vagrantfile
vendored
34
os/alma/8/Vagrantfile
vendored
@ -23,10 +23,9 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux"
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = 8024
|
||||
|
||||
# Display the VirtualBox GUI when booting the machine
|
||||
vb.gui = false
|
||||
vb.memory = 2048
|
||||
vb.cpus = 2
|
||||
|
||||
# Fix for 'SSH auth method: Private key' stuck
|
||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||
@ -38,29 +37,22 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
export BASE=/home/vagrant/Rublon-Linux
|
||||
export DISTRO=alma08
|
||||
export BUILDDIR=${BASE}/${DISTRO}
|
||||
export BUILDDIR=/home/vagrant/build
|
||||
|
||||
yum update
|
||||
yum install -y --nogpgcheck gcc wget openssl-devel openssh-server libcurl-devel pam-devel git cmake policycoreutils-devel checkpolicy gcc-c++ rpm-build libubsan libasan redhat-lsb-core
|
||||
yum install -y --nogpgcheck gcc gcc-c++ openssl-devel openssh-server libcurl-devel pam-devel cmake policycoreutils-devel checkpolicy rpm-build redhat-lsb-core
|
||||
|
||||
# get dependencies
|
||||
mkdir ${BUILDDIR} -p; cd ${BUILDDIR}
|
||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
||||
mkdir socket.io-client-cpp/build_alma; cd socket.io-client-cpp/build_alma
|
||||
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release ..
|
||||
cmake --build . --target install
|
||||
# remove old build if exists
|
||||
rm ${BUILDDIR} -Rf || true
|
||||
|
||||
# build package
|
||||
cmake -B ${BUILDDIR} ${BASE}
|
||||
cmake --build ${BUILDDIR} -j$(nproc)
|
||||
cmake --build ${BUILDDIR} --target package -j$(nproc)
|
||||
|
||||
#handle semodule
|
||||
wget https://repo.almalinux.org/almalinux/8/devel/x86_64/os/Packages/rapidjson-devel-1.1.0-3.module_el8.8.0%2B3567%2B56a616e4.noarch.rpm
|
||||
rpm -i rapidjson*
|
||||
# Build project
|
||||
cd ${BUILDDIR}
|
||||
cmake -B rublon_ssh-build ..
|
||||
cmake --build rublon_ssh-build/ -- -j
|
||||
cmake --build rublon_ssh-build/ --target package -- -j
|
||||
useradd -s /bin/bash -m bwi
|
||||
echo "bwi:bwi"|chpasswd
|
||||
cd ./rublon_ssh-build
|
||||
yum install -y rublon*.rpm
|
||||
yum install -y ${BUILDDIR}/rublon*.rpm
|
||||
SHELL
|
||||
end
|
||||
|
||||
|
||||
38
os/alma/9/Vagrantfile
vendored
38
os/alma/9/Vagrantfile
vendored
@ -23,10 +23,9 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux"
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = 8024
|
||||
vb.cpus = 4
|
||||
# Display the VirtualBox GUI when booting the machine
|
||||
vb.gui = false
|
||||
vb.memory = 2048
|
||||
vb.cpus = 2
|
||||
|
||||
# Fix for 'SSH auth method: Private key' stuck
|
||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||
@ -38,26 +37,21 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
export BASE=/home/vagrant/Rublon-Linux
|
||||
export DISTRO=alma09
|
||||
export BUILDDIR=${BASE}/${DISTRO}
|
||||
yum update
|
||||
yum install -y --nogpgcheck gcc wget openssl-devel openssh-server libcurl-devel pam-devel git cmake policycoreutils-devel checkpolicy gcc-c++ rpm-build*
|
||||
cp ${BASE}/helpers/centos-base_9.repo /etc/yum.repos.d/centos-base_9.repo
|
||||
# get dependencies
|
||||
mkdir ${BUILDDIR} -p; cd ${BUILDDIR}
|
||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
||||
mkdir socket.io-client-cpp/build_alma; cd socket.io-client-cpp/build_alma
|
||||
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release ..
|
||||
cmake --build . --target install
|
||||
export BUILDDIR=/home/vagrant/build
|
||||
|
||||
#handle semodule
|
||||
wget https://repo.almalinux.org/almalinux/9/devel/x86_64/os/Packages/rapidjson-devel-1.1.0-19.el9.x86_64.rpm
|
||||
rpm -i ./rapid*
|
||||
# Build project
|
||||
cd ${BUILDDIR}
|
||||
cmake -B rublon_ssh-build ..
|
||||
cmake --build rublon_ssh-build/ -- -j
|
||||
cmake --build rublon_ssh-build/ --target package -- -j
|
||||
yum install -y ./rublon_ssh-build/rublon*
|
||||
yum update
|
||||
yum install -y --nogpgcheck gcc gcc-c++ openssl-devel openssh-server libcurl-devel pam-devel cmake policycoreutils-devel checkpolicy rpm-build*
|
||||
cp ${BASE}/helpers/centos-base_9.repo /etc/yum.repos.d/centos-base_9.repo
|
||||
|
||||
# remove old build if exists
|
||||
rm ${BUILDDIR} -Rf || true
|
||||
|
||||
# build package
|
||||
cmake -B ${BUILDDIR} ${BASE}
|
||||
cmake --build ${BUILDDIR} -j$(nproc)
|
||||
cmake --build ${BUILDDIR} --target package -j$(nproc)
|
||||
|
||||
yum install -y ${BUILDDIR}/rublon*
|
||||
useradd -s /bin/bash -m bwi
|
||||
echo "bwi:bwi"|chpasswd
|
||||
SHELL
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
FROM carterjones/centos-stream9
|
||||
|
||||
RUN yum update && yum install -y gcc \
|
||||
RUN yum -y update && yum install -y \
|
||||
gcc gcc-c++ \
|
||||
openssl-devel \
|
||||
libcurl-devel \
|
||||
pam-devel \
|
||||
@ -8,8 +9,8 @@ RUN yum update && yum install -y gcc \
|
||||
policycoreutils-devel \
|
||||
checkpolicy \
|
||||
rpm-build \
|
||||
openssh \
|
||||
gcc-c++
|
||||
openssh
|
||||
|
||||
WORKDIR /home/Rublon-Linux/
|
||||
|
||||
|
||||
|
||||
25
os/centos/stream9/Vagrantfile
vendored
25
os/centos/stream9/Vagrantfile
vendored
@ -37,25 +37,22 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
export BASE=/home/vagrant/Rublon-Linux
|
||||
export DISTRO=centos09
|
||||
export BUILDDIR=${BASE}/${DISTRO}
|
||||
export BUILDDIR=/home/vagrant/${DISTRO}
|
||||
|
||||
yum update
|
||||
yum install -y gcc openssl-devel libcurl-devel pam-devel git rapidjson-devel cmake policycoreutils-devel checkpolicy rpm-build openssh
|
||||
yum install -y gcc gcc-c++ openssl-devel libcurl-devel pam-devel cmake policycoreutils-devel checkpolicy rpm-build openssh
|
||||
|
||||
# get dependencies
|
||||
mkdir ${BUILDDIR} -p; cd ${BUILDDIR}
|
||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
||||
mkdir socket.io-client-cpp/build; cd socket.io-client-cpp/build
|
||||
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release ..
|
||||
cmake --build . --target install
|
||||
# remove old build if exists
|
||||
rm ${BUILDDIR} -Rf || true
|
||||
|
||||
# build package
|
||||
cmake -B ${BUILDDIR} ${BASE}
|
||||
cmake --build ${BUILDDIR} -j$(nproc)y
|
||||
cmake --build ${BUILDDIR} --target package -j$(nproc)
|
||||
|
||||
# Build project
|
||||
cd ${BUILDDIR}
|
||||
cmake -B rublon_ssh-build ..
|
||||
cmake --build rublon_ssh-build -- -j
|
||||
cmake --build rublon_ssh-build --target package -- -j
|
||||
useradd -s /bin/bash -m bwi
|
||||
echo "bwi:bwi"|chpasswd
|
||||
sudo yum install -y ./rublon_ssh-build/rublon*.rpm
|
||||
sudo yum install -y ${BUILDDIR}/rublon*.rpm
|
||||
SHELL
|
||||
end
|
||||
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
FROM debian:11
|
||||
ENV DEBIAN_FRONTEND=noniteracactive
|
||||
RUN apt update && apt install -y gcc \
|
||||
g++ \
|
||||
RUN apt update && apt install -y \
|
||||
build-essential \
|
||||
openssh-server \
|
||||
libcurl4-openssl-dev \
|
||||
libpam0g-dev \
|
||||
libssl-dev \
|
||||
cmake \
|
||||
git \
|
||||
file
|
||||
WORKDIR /home/Rublon-Linux/
|
||||
|
||||
|
||||
31
os/debian/11/Vagrantfile
vendored
31
os/debian/11/Vagrantfile
vendored
@ -23,10 +23,9 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux"
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = 4048
|
||||
vb.cpus = 4
|
||||
# Display the VirtualBox GUI when booting the machine
|
||||
vb.gui = false
|
||||
vb.memory = 2048
|
||||
vb.cpus = 2
|
||||
|
||||
# Fix for 'SSH auth method: Private key' stuck
|
||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||
@ -38,34 +37,28 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
export BASE=/home/vagrant/Rublon-Linux
|
||||
export DISTRO=debian11
|
||||
export BUILDDIR=${BASE}/${DISTRO}
|
||||
export BUILDDIR=/home/vagrant/build
|
||||
|
||||
DEBIAN_FRONTEND=noniteracactive \
|
||||
apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
build-essential \
|
||||
openssh-server \
|
||||
libcurl4-openssl-dev \
|
||||
libpam0g-dev \
|
||||
libssl-dev \
|
||||
git \
|
||||
rapidjson-dev \
|
||||
cmake
|
||||
|
||||
# get dependencies
|
||||
mkdir ${BUILDDIR} -p; cd ${BUILDDIR}
|
||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
||||
cmake -B socket.io-build -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release socket.io-client-cpp
|
||||
cmake --build socket.io-build --target install -- -j
|
||||
# remove old build if exists
|
||||
rm ${BUILDDIR} -Rf || true
|
||||
|
||||
# build package
|
||||
cmake -B ${BUILDDIR} ${BASE}
|
||||
cmake --build ${BUILDDIR} -j$(nproc)
|
||||
cmake --build ${BUILDDIR} --target package -j$(nproc)
|
||||
|
||||
# Build project
|
||||
cd ${BUILDDIR}
|
||||
cmake -B rublon_ssh-build ..
|
||||
cmake --build rublon_ssh-build -- -j
|
||||
cmake --build rublon_ssh-build --target package -- -j
|
||||
# Register Rublon pam
|
||||
|
||||
useradd -s /bin/bash -m bwi
|
||||
echo "bwi:bwi"|chpasswd
|
||||
sudo dpkg -i ./rublon_ssh-build/rublon*
|
||||
sudo dpkg -i ${BUILDDIR}/rublon*.deb
|
||||
SHELL
|
||||
end
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
FROM debian:12
|
||||
ENV DEBIAN_FRONTEND=noniteracactive
|
||||
RUN apt update && apt install -y gcc \
|
||||
g++ \
|
||||
RUN apt update && apt install -y \
|
||||
build-essential \
|
||||
openssh-server \
|
||||
libcurl4-openssl-dev \
|
||||
libpam0g-dev \
|
||||
libssl-dev \
|
||||
cmake \
|
||||
git \
|
||||
file
|
||||
WORKDIR /home/Rublon-Linux/
|
||||
|
||||
|
||||
31
os/debian/12/Vagrantfile
vendored
31
os/debian/12/Vagrantfile
vendored
@ -23,10 +23,9 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux"
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
# Display the VirtualBox GUI when booting the machine
|
||||
vb.gui = false
|
||||
vb.memory = 8044
|
||||
vb.cpus = 4
|
||||
vb.memory = 2048
|
||||
vb.cpus = 2
|
||||
|
||||
# Fix for 'SSH auth method: Private key' stuck
|
||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||
@ -38,34 +37,28 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
export BASE=/home/vagrant/Rublon-Linux
|
||||
export DISTRO=debian12
|
||||
export BUILDDIR=${BASE}/${DISTRO}
|
||||
export BUILDDIR=/home/vagrant/build
|
||||
|
||||
DEBIAN_FRONTEND=noniteracactive \
|
||||
apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
build-essential \
|
||||
openssh-server \
|
||||
libcurl4-openssl-dev \
|
||||
libpam0g-dev \
|
||||
libssl-dev \
|
||||
git \
|
||||
rapidjson-dev \
|
||||
cmake
|
||||
|
||||
# get dependencies
|
||||
mkdir ${BUILDDIR} -p; cd ${BUILDDIR}
|
||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
||||
cmake -B socket.io-build -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release socket.io-client-cpp
|
||||
cmake --build socket.io-build --target install -- -j
|
||||
# remove old build if exists
|
||||
rm ${BUILDDIR} -Rf || true
|
||||
|
||||
# build package
|
||||
cmake -B ${BUILDDIR} ${BASE}
|
||||
cmake --build ${BUILDDIR} -j$(nproc)
|
||||
cmake --build ${BUILDDIR} --target package -j$(nproc)
|
||||
|
||||
# Build project
|
||||
cd ${BUILDDIR}
|
||||
cmake -B rublon_ssh-build ..
|
||||
cmake --build rublon_ssh-build -- -j
|
||||
cmake --build rublon_ssh-build --target package -- -j
|
||||
# Register Rublon pam
|
||||
|
||||
useradd -s /bin/bash -m bwi
|
||||
echo "bwi:bwi"|chpasswd
|
||||
sudo dpkg -i ${BUILDDIR}/rublon_ssh-build/rublon*
|
||||
sudo dpkg -i ${BUILDDIR}/rublon*.deb
|
||||
SHELL
|
||||
end
|
||||
|
||||
24
os/opensuse/15sp3/Vagrantfile
vendored
24
os/opensuse/15sp3/Vagrantfile
vendored
@ -37,35 +37,29 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
export BASE=/home/vagrant/Rublon-Linux
|
||||
export DISTRO=opensuse15
|
||||
export BUILDDIR=${BASE}/${DISTRO}
|
||||
export BUILDDIR=/home/vagrant/${DISTRO}
|
||||
zypper --non-interactive install -y \
|
||||
gcc \
|
||||
gcc-c++ \
|
||||
openssh-server \
|
||||
libcurl-devel \
|
||||
libpamtest-devel \
|
||||
git \
|
||||
rapidjson-devel \
|
||||
cmake \
|
||||
pam-devel \
|
||||
openssl-devel \
|
||||
rpm-build
|
||||
|
||||
# get dependencies
|
||||
mkdir ${BUILDDIR} -p;
|
||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
||||
cmake -B socket.io-build -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release socket.io-client-cpp
|
||||
cmake --build socket.io-build --target install -- -j
|
||||
# remove old build if exists
|
||||
rm ${BUILDDIR} -Rf || true
|
||||
|
||||
# build package
|
||||
cmake -B ${BUILDDIR} ${BASE}
|
||||
cmake --build ${BUILDDIR} -j$(nproc)
|
||||
cmake --build ${BUILDDIR} --target package -j$(nproc)
|
||||
|
||||
# Build project
|
||||
cd ${BUILDDIR}
|
||||
cmake -B rublon_ssh-build ..
|
||||
cmake --build rublon_ssh-build -- -j
|
||||
cmake --build rublon_ssh-build --target package -- -j
|
||||
# Register Rublon pam
|
||||
|
||||
useradd -s /bin/bash -m bwi
|
||||
echo "bwi:bwi"|chpasswd
|
||||
sudo zypper --no-gpg-checks install -y ${BUILDDIR}/rublon_ssh-build/rublon*
|
||||
sudo zypper --no-gpg-checks install -y ${BUILDDIR}/rublon*
|
||||
SHELL
|
||||
end
|
||||
|
||||
@ -8,8 +8,8 @@ RUN yum update -y && yum install -y gcc \
|
||||
rpm-build \
|
||||
openssh-server \
|
||||
gcc-c++ \
|
||||
wget
|
||||
RUN wget https://yum.oracle.com/repo/OracleLinux/OL8/baseos/latest/x86_64/getPackage/pam-1.3.1-34.0.1.el8_10.x86_64.rpm \
|
||||
https://yum.oracle.com/repo/OracleLinux/OL8/baseos/latest/x86_64/getPackage/pam-devel-1.3.1-34.0.1.el8_10.x86_64.rpm
|
||||
wget\
|
||||
pam
|
||||
RUN wget ftp://ftp.icm.edu.pl/vol/rzm5/linux-rocky/8.10/BaseOS/x86_64/os/Packages/p/pam-devel-1.3.1-36.el8_10.x86_64.rpm
|
||||
RUN rpm -Uvh pam*
|
||||
|
||||
|
||||
35
os/rhel/8/Vagrantfile
vendored
35
os/rhel/8/Vagrantfile
vendored
@ -24,9 +24,9 @@ Vagrant.configure("2") do |config|
|
||||
# config.vm.synced_folder "../../../../socket.io", "/home/vagrant/socket.io"
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = 8024
|
||||
# Display the VirtualBox GUI when booting the machine
|
||||
vb.gui = false
|
||||
vb.memory = 2048
|
||||
vb.cpus = 2
|
||||
|
||||
# Fix for 'SSH auth method: Private key' stuck
|
||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||
@ -38,24 +38,25 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
export BASE=/home/vagrant/Rublon-Linux
|
||||
export DISTRO=rhel8
|
||||
export BUILDDIR=${BASE}/${DISTRO}
|
||||
export BUILDDIR=/home/vagrant/build
|
||||
|
||||
cp ${BASE}/helpers/centos-base.repo /etc/yum.repos.d/
|
||||
yum install -y openssl-devel libcurl systemd-pam git rapidjson-devel cmake pam-devel libcurl-devel libasan libubsan rpm-build redhat-lsb-core
|
||||
# get dependencies
|
||||
mkdir ${BUILDDIR} -p; cd ${BUILDDIR}
|
||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
||||
cmake -B socket.io-build -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release socket.io-client-cpp
|
||||
cmake --build socket.io-build --target install -- -j
|
||||
yum update
|
||||
yum install -y openssl-devel libcurl systemd-pam cmake pam-devel libcurl-devel rpm-build redhat-lsb-core
|
||||
|
||||
# remove old build if exists
|
||||
rm ${BUILDDIR} -Rf || true
|
||||
|
||||
# build package
|
||||
cmake -B ${BUILDDIR} ${BASE}
|
||||
cmake --build ${BUILDDIR} -j$(nproc)
|
||||
cmake --build ${BUILDDIR} --target package -j$(nproc)
|
||||
|
||||
# Register Rublon pam
|
||||
yum -y install ${BUILDDIR}/rublon-ssh*.el8.rpm
|
||||
|
||||
useradd -s /bin/bash -m bwi
|
||||
echo "bwi:bwi"|chpasswd
|
||||
# Build project
|
||||
cd ${BUILDDIR}
|
||||
cmake -B rublon_ssh-build ..
|
||||
cmake --build rublon_ssh-build -- -j
|
||||
cmake --build rublon_ssh-build --target package -- -j
|
||||
# Register Rublon pam
|
||||
yum -y install ./rublon_ssh-build/rublon-ssh*.el8.rpm
|
||||
|
||||
SHELL
|
||||
|
||||
end
|
||||
|
||||
35
os/rhel/9/Vagrantfile
vendored
35
os/rhel/9/Vagrantfile
vendored
@ -23,10 +23,9 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux"
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = 6024
|
||||
|
||||
# Display the VirtualBox GUI when booting the machine
|
||||
vb.gui = false
|
||||
vb.memory = 2048
|
||||
vb.cpus = 2
|
||||
|
||||
# Fix for 'SSH auth method: Private key' stuck
|
||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||
@ -38,26 +37,24 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
export BASE=/home/vagrant/Rublon-Linux
|
||||
export DISTRO=rhel9
|
||||
export BUILDDIR=${BASE}/${DISTRO}
|
||||
export BUILDDIR=/home/vagrant/build
|
||||
|
||||
cp ${BASE}/helpers/centos-base_9.repo /etc/yum.repos.d/centos-base_9.repo
|
||||
yum update
|
||||
yum install -y gcc openssl-devel openssh libcurl systemd-pam git rapidjson-devel cmake rpm-build lsb-release pam-devel libcurl-devel
|
||||
yum install -y gcc openssl-devel openssh libcurl systemd-pam cmake rpm-build lsb-release pam-devel libcurl-devel
|
||||
|
||||
# remove old build if exists
|
||||
rm ${BUILDDIR} -Rf || true
|
||||
|
||||
# build package
|
||||
cmake -B ${BUILDDIR} ${BASE}
|
||||
cmake --build ${BUILDDIR} -j$(nproc)
|
||||
cmake --build ${BUILDDIR} --target package -j$(nproc)
|
||||
|
||||
# Register Rublon pam
|
||||
useradd -s /bin/bash -m bwi
|
||||
echo "bwi:bwi"|chpasswd
|
||||
# get dependencies
|
||||
mkdir ${BUILDDIR} -p; cd ${BUILDDIR}
|
||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
||||
cmake -B socket.io-build -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release socket.io-client-cpp
|
||||
cmake --build socket.io-build --target install -- -j
|
||||
|
||||
# Build project
|
||||
cd ${BUILDDIR}
|
||||
cmake -B rublon_ssh-build ..
|
||||
cmake --build rublon_ssh-build -- -j
|
||||
cmake --build rublon_ssh-build --target package -- -j
|
||||
# Register Rublon pam
|
||||
yum -y install ./rublon_ssh-build/rublon*.rpm
|
||||
|
||||
yum -y install ${BUILDDIR}/rublon*.rpm
|
||||
SHELL
|
||||
|
||||
end
|
||||
|
||||
31
os/rocky_linux/Vagrantfile
vendored
31
os/rocky_linux/Vagrantfile
vendored
@ -23,9 +23,10 @@ Vagrant.configure("2") do |config|
|
||||
# argument is a set of non-required options.
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = 8024
|
||||
# Display the VirtualBox GUI when booting the machine
|
||||
vb.gui = false
|
||||
vb.memory = 2048
|
||||
vb.cpus = 2
|
||||
|
||||
# Fix for 'SSH auth method: Private key' stuck
|
||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||
end
|
||||
@ -36,24 +37,22 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
export BASE=/home/vagrant/Rublon-Linux
|
||||
export DISTRO=rocky9
|
||||
export BUILDDIR=${BASE}/${DISTRO}
|
||||
yum install -y epel-release
|
||||
yum install -y gcc openssl-devel systemd-pam git cmake gcc-c++ rapidjson-devel wget pam-devel rpm-build libcurl-devel policycoreutils-python-utils
|
||||
# get dependencies
|
||||
mkdir ${BUILDDIR} -p; cd ${BUILDDIR}
|
||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
||||
cmake -B socket.io-build -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release socket.io-client-cpp
|
||||
cmake --build socket.io-build --target install -- -j
|
||||
export BUILDDIR=/home/vagrant/build
|
||||
|
||||
yum install -y epel-release
|
||||
yum install -y gcc gcc-c++ openssl-devel systemd-pam cmake pam-devel rpm-build libcurl-devel policycoreutils-python-utils
|
||||
|
||||
# remove old build if exists
|
||||
rm ${BUILDDIR} -Rf || true
|
||||
|
||||
# build package
|
||||
cmake -B ${BUILDDIR} ${BASE}
|
||||
cmake --build ${BUILDDIR} -j$(nproc)
|
||||
cmake --build ${BUILDDIR} --target package -j$(nproc)
|
||||
|
||||
# Build project
|
||||
cd ${BUILDDIR}
|
||||
cmake -B rublon_ssh-build ..
|
||||
cmake --build rublon_ssh-build -- -j
|
||||
cmake --build rublon_ssh-build --target package -- -j
|
||||
useradd -s /bin/bash -m bwi
|
||||
echo "bwi:bwi"|chpasswd
|
||||
sudo yum install -y ./rublon_ssh-build/rublon*.rpm
|
||||
SHELL
|
||||
|
||||
end
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
FROM ubuntu:20.04
|
||||
ENV DEBIAN_FRONTEND=noniteracactive
|
||||
RUN apt update && apt install -y gcc \
|
||||
g++ \
|
||||
RUN apt update && apt install -y \
|
||||
build-essential \
|
||||
openssh-server \
|
||||
libcurl4-openssl-dev \
|
||||
|
||||
32
os/ubuntu/20.04/Vagrantfile
vendored
32
os/ubuntu/20.04/Vagrantfile
vendored
@ -23,9 +23,9 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux"
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = 8060
|
||||
# Display the VirtualBox GUI when booting the machine
|
||||
vb.gui = false
|
||||
vb.memory = 2048
|
||||
vb.cpus = 2
|
||||
|
||||
# Fix for 'SSH auth method: Private key' stuck
|
||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||
@ -37,36 +37,28 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
export BASE=/home/vagrant/Rublon-Linux
|
||||
export DISTRO=ubuntu2004
|
||||
export BUILDDIR=${BASE}/${DISTRO}
|
||||
export BUILDDIR=/home/vagrant/build
|
||||
|
||||
DEBIAN_FRONTEND=noniteracactive \
|
||||
apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
g++ \
|
||||
build-essential \
|
||||
openssh-server \
|
||||
libcurl4-openssl-dev \
|
||||
libpam0g-dev \
|
||||
libssl-dev \
|
||||
git \
|
||||
rapidjson-dev \
|
||||
cmake
|
||||
|
||||
# get dependencies
|
||||
mkdir ${BUILDDIR} -p; cd ${BUILDDIR}
|
||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
||||
cmake -B socket.io-build -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release socket.io-client-cpp
|
||||
cmake --build socket.io-build --target install -- -j
|
||||
ln -s /usr/bin/make /usr/bin/gmake
|
||||
# Build project
|
||||
cd ${BUILDDIR}
|
||||
cmake -B rublon_ssh-build ..
|
||||
cmake --build rublon_ssh-build -- -j
|
||||
cmake --build rublon_ssh-build --target package -- -j
|
||||
# Register Rublon pam
|
||||
# remove old build if exists
|
||||
rm ${BUILDDIR} -Rf || true
|
||||
|
||||
# build package
|
||||
cmake -B ${BUILDDIR} ${BASE}
|
||||
cmake --build ${BUILDDIR} -j$(nproc)
|
||||
cmake --build ${BUILDDIR} --target package -j$(nproc)
|
||||
|
||||
# Register Rublon pam
|
||||
useradd -s /bin/bash -m bwi
|
||||
echo "bwi:bwi"|chpasswd
|
||||
sudo dpkg -i ${BUILDDIR}/rublon_ssh-build/rublon*.deb
|
||||
sudo dpkg -i ${BUILDDIR}/rublon*.deb
|
||||
SHELL
|
||||
end
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
FROM ubuntu:22.04
|
||||
ENV DEBIAN_FRONTEND=noniteracactive
|
||||
RUN apt update && apt install -y gcc \
|
||||
g++ \
|
||||
RUN apt update && apt install -y \
|
||||
build-essential \
|
||||
openssh-server \
|
||||
libcurl4-openssl-dev \
|
||||
|
||||
27
os/ubuntu/22.04/Vagrantfile
vendored
27
os/ubuntu/22.04/Vagrantfile
vendored
@ -23,9 +23,9 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux"
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = 4000
|
||||
# Display the VirtualBox GUI when booting the machine
|
||||
vb.gui = false
|
||||
vb.memory = 2048
|
||||
vb.cpus = 2
|
||||
|
||||
# Fix for 'SSH auth method: Private key' stuck
|
||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||
@ -37,29 +37,28 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
export BASE=/home/vagrant/Rublon-Linux
|
||||
export DISTRO=ubuntu2204
|
||||
export BUILDDIR=${BASE}/${DISTRO}
|
||||
export BUILDDIR=/home/vagrant/build
|
||||
|
||||
DEBIAN_FRONTEND=noniteracactive \
|
||||
apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
build-essential \
|
||||
openssh-server \
|
||||
libcurl4-openssl-dev \
|
||||
libpam0g-dev \
|
||||
libssl-dev \
|
||||
git \
|
||||
rapidjson-dev \
|
||||
libwebsockets-dev \
|
||||
cmake
|
||||
|
||||
# get dependencies
|
||||
mkdir ${BUILDDIR} -p; cd ${BUILDDIR}
|
||||
cmake -B rublon_ssh-build ..
|
||||
cmake --build rublon_ssh-build -- -j
|
||||
cmake --build rublon_ssh-build --target package -- -j
|
||||
# Register Rublon pam
|
||||
# remove old build if exists
|
||||
rm ${BUILDDIR} -Rf || true
|
||||
|
||||
# build package
|
||||
cmake -B ${BUILDDIR} ${BASE}
|
||||
cmake --build ${BUILDDIR} -j$(nproc)
|
||||
cmake --build ${BUILDDIR} --target package -j$(nproc)
|
||||
|
||||
# Register Rublon pam
|
||||
useradd -s /bin/bash -m bwi
|
||||
echo "bwi:bwi"|chpasswd
|
||||
sudo dpkg -i ${BUILDDIR}/rublon_ssh-build/rublon*.deb
|
||||
sudo dpkg -i ${BUILDDIR}/rublon*.deb
|
||||
SHELL
|
||||
end
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
FROM ubuntu:24.04
|
||||
ENV DEBIAN_FRONTEND=noniteracactive
|
||||
RUN apt update && apt install -y gcc \
|
||||
g++ \
|
||||
RUN apt update && apt install -y \
|
||||
build-essential \
|
||||
openssh-server \
|
||||
libcurl4-openssl-dev \
|
||||
|
||||
32
os/ubuntu/24.04/Vagrantfile
vendored
32
os/ubuntu/24.04/Vagrantfile
vendored
@ -22,9 +22,9 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux"
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = 4000
|
||||
# Display the VirtualBox GUI when booting the machine
|
||||
vb.gui = false
|
||||
vb.memory = 2048
|
||||
vb.cpus = 2
|
||||
|
||||
# Fix for 'SSH auth method: Private key' stuck
|
||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||
@ -36,34 +36,28 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
export BASE=/home/vagrant/Rublon-Linux
|
||||
export DISTRO=ubuntu2404
|
||||
export BUILDDIR=${BASE}/${DISTRO}
|
||||
export BUILDDIR=/home/vagrant/build
|
||||
|
||||
DEBIAN_FRONTEND=noniteracactive \
|
||||
apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
build-essential \
|
||||
openssh-server \
|
||||
libcurl4-openssl-dev \
|
||||
libpam0g-dev \
|
||||
libssl-dev \
|
||||
git \
|
||||
rapidjson-dev \
|
||||
cmake
|
||||
|
||||
# get dependencies
|
||||
mkdir ${BUILDDIR} -p; cd ${BUILDDIR}
|
||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
||||
cmake -B socket.io-build -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release socket.io-client-cpp
|
||||
cmake --build socket.io-build --target install -- -j
|
||||
ln -s /usr/bin/make /usr/bin/gmake
|
||||
# Build project
|
||||
cd ${BUILDDIR}
|
||||
cmake -B rublon_ssh-build ..
|
||||
cmake --build rublon_ssh-build -- -j
|
||||
cmake --build rublon_ssh-build --target package -- -j
|
||||
# Register Rublon pam
|
||||
# remove old build if exists
|
||||
rm ${BUILDDIR} -Rf || true
|
||||
|
||||
# build package
|
||||
cmake -B ${BUILDDIR} ${BASE}
|
||||
cmake --build ${BUILDDIR} -j$(nproc)
|
||||
cmake --build ${BUILDDIR} --target package -j$(nproc)
|
||||
|
||||
# Register Rublon pam
|
||||
useradd -s /bin/bash -m bwi
|
||||
echo "bwi:bwi"|chpasswd
|
||||
sudo dpkg -i ${BUILDDIR}/rublon_ssh-build/rublon*.deb
|
||||
sudo dpkg -i ${BUILDDIR}/rublon*.deb
|
||||
SHELL
|
||||
end
|
||||
|
||||
@ -29,7 +29,12 @@ if ( ${outOS} MATCHES "ubuntu" OR ${outOS} MATCHES "debian" )
|
||||
set(CPACK_DEBIAN_PAM-DEV_PACKAGE_NAME, YES)
|
||||
set(CPACK_DEB_COMPONENT_INSTALL YES)
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS YES)
|
||||
|
||||
set(CPACK_SOURCE_IGNORE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/service/login_rublon.mod" )
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/service/login_rublon.pp")
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/service/login_rublon.te")
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/service/pam_service.txt")
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/service/rublon_veritas")
|
||||
set(CPACK_SOURCE_IGNORE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/service/inst_pubkey_rhel9.sh")
|
||||
execute_process (
|
||||
COMMAND bash -c "awk -F= '/^VERSION_ID=/{print $2}' /etc/os-release |tr -d '\n' | tr -d '\"'"
|
||||
OUTPUT_VARIABLE VERSION_ID
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
UsePAM yes
|
||||
ChallengeResponseAuthentication yes
|
||||
LoginGraceTime 15m
|
||||
ChallengeResponseAuthentication yes
|
||||
AuthenticationMethods publickey,keyboard-interactive
|
||||
MaxAuthTries 3
|
||||
PubkeyAuthentication yes
|
||||
|
||||
@ -9,14 +9,14 @@ if [ ! -f $RUBLON_CONFIG ]
|
||||
then
|
||||
cp -a /usr/share/rublon/rublon.config.defaults $RUBLON_CONFIG
|
||||
chown root:root $RUBLON_CONFIG
|
||||
chmod 640 $RUBLON_CONFIG
|
||||
chmod 644 $RUBLON_CONFIG
|
||||
fi
|
||||
|
||||
if [ ! -f $RUBLON_SSH_CONFIG ]
|
||||
then
|
||||
cp -a /usr/share/rublon/01-rublon-ssh.conf.default $RUBLON_SSH_CONFIG
|
||||
chown root:root $RUBLON_SSH_CONFIG
|
||||
chmod 640 $RUBLON_SSH_CONFIG
|
||||
chmod 644 $RUBLON_SSH_CONFIG
|
||||
fi
|
||||
|
||||
if [ -f /etc/os-release ]
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
SSHD_CONF=/etc/ssh/sshd_config
|
||||
SSHD_PAM_CONF=/etc/pam.d/sshd
|
||||
RUBLON_SSH_CONFIG=/etc/ssh/sshd_config.d/01-rublon-ssh.conf
|
||||
|
||||
cp -a /usr/share/rublon/01-rublon-ssh_pubkey.conf.default $RUBLON_SSH_CONFIG
|
||||
chown root:root $RUBLON_SSH_CONFIG
|
||||
chmod 640 $RUBLON_SSH_CONFIG
|
||||
|
||||
if [ -f /etc/os-release ]
|
||||
then
|
||||
. /etc/os-release
|
||||
fi
|
||||
grep -qe 'auth requisite pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth requisite pam_rublon.so' $SSHD_PAM_CONF
|
||||
|
||||
if [ ${OS} == "Ubuntu"]
|
||||
then
|
||||
grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aaccount required pam_rublon.so' $SSHD_PAM_CONF
|
||||
fi
|
||||
|
||||
grep -qe '@include common-auth' $SSHD_PAM_CONF || sed -i 's/@include common-auth/#@include common-auth/' $SSHD_PAM_CONF
|
||||
|
||||
systemctrl restart sshd
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
RUBLON_CONFIG=/etc/rublon.config
|
||||
RUBLON_SSH_CONFIG=/etc/ssh/sshd_config.d/01-rublon-ssh.conf
|
||||
SSHD_PAM_CONF=/etc/pam.d/sshd
|
||||
VERITAS_PATH=/usr/openv/netbackup/sec/at/bin/
|
||||
|
||||
if [ -f $RUBLON_CONFIG ]
|
||||
then
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
SSHD_CONF=/etc/ssh/sshd_config
|
||||
SSHD_PAM_CONF=/etc/pam.d/sshd
|
||||
RUBLON_SSH_CONFIG=/etc/ssh/sshd_config.d/01-rublon-ssh.conf
|
||||
|
||||
cp -a /usr/share/rublon/01-rublon-ssh_pubkey.conf.default $RUBLON_SSH_CONFIG
|
||||
chown root:root $RUBLON_SSH_CONFIG
|
||||
chmod 640 $RUBLON_SSH_CONFIG
|
||||
|
||||
if [ -f /etc/os-release ]
|
||||
then
|
||||
. /etc/os-release
|
||||
fi
|
||||
sed -i '/auth required pam_rublon.so/d' $SSHD_PAM_CONF
|
||||
|
||||
if [[ $ID == "rhel" || $ID=="alma" || $ID=="rocky" || $ID=="Centos" ]]
|
||||
then
|
||||
grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth required pam_rublon.so' $SSHD_PAM_CONF
|
||||
grep -qe '#auth substack password-auth' $SSHD_PAM_CONF || sed -i -e 's/auth substack password-auth/#auth substack password-auth/g' $SSHD_PAM_CONF
|
||||
|
||||
elif [ $ID = "Debian" ]
|
||||
then
|
||||
grep -qe 'auth requisite pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth requisite pam_rublon.so' $SSHD_PAM_CONF
|
||||
else
|
||||
grep -qe 'auth requisite pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth requisite pam_rublon.so' $SSHD_PAM_CONF
|
||||
grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aaccount required pam_rublon.so' $SSHD_PAM_CONF
|
||||
fi
|
||||
|
||||
grep -qe '#@include common-auth' $SSHD_PAM_CONF || sed -i 's/@include common-auth/#@include common-auth/' $SSHD_PAM_CONF
|
||||
if [[ $ID == "rhel" || $ID=="alma" || $ID=="rocky" || $ID=="Centos" ]]
|
||||
then
|
||||
systemctl restart sshd
|
||||
else
|
||||
deb-systemd-invoke restart ssh.service
|
||||
fi
|
||||
|
||||
17
service/inst_pubkey.sh
Normal file
17
service/inst_pubkey.sh
Normal file
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
SSHD_CONF=/etc/ssh/sshd_config
|
||||
SSHD_PAM_CONF=/etc/pam.d/sshd
|
||||
RUBLON_SSH_CONFIG=/etc/ssh/sshd_config.d/01-rublon-ssh.conf
|
||||
|
||||
cp -a /usr/share/rublon/01-rublon-ssh_pubkey.conf.default $RUBLON_SSH_CONFIG
|
||||
chown root:root $RUBLON_SSH_CONFIG
|
||||
chmod 640 $RUBLON_SSH_CONFIG
|
||||
|
||||
grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth required pam_rublon.so' $SSHD_PAM_CONF
|
||||
grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aaccount required pam_rublon.so' $SSHD_PAM_CONF
|
||||
grep -qe '#@include \+common-auth' $SSHD_PAM_CONF || sed -i 's/@include \+common-auth/#@include common-auth/' $SSHD_PAM_CONF
|
||||
|
||||
deb-systemd-invoke restart ssh.service
|
||||
|
||||
|
||||
15
service/inst_pubkey_rhel_8.sh
Normal file
15
service/inst_pubkey_rhel_8.sh
Normal file
@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
SSHD_CONF=/etc/ssh/sshd_config
|
||||
SSHD_PAM_CONF=/etc/pam.d/sshd
|
||||
RUBLON_SSH_CONFIG=/etc/ssh/01-rublon-ssh.conf
|
||||
|
||||
cp -a /usr/share/rublon/01-rublon-ssh_pubkey.conf.default $RUBLON_SSH_CONFIG
|
||||
chown root:root $RUBLON_SSH_CONFIG
|
||||
chmod 640 $RUBLON_SSH_CONFIG
|
||||
|
||||
grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aaccount required pam_rublon.so' $SSHD_PAM_CONF
|
||||
grep -qe '#auth \+substack \+password-auth' $SSHD_PAM_CONF || sed -i 's/auth \+substack \+password-auth/#auth substack password-auth/' $SSHD_PAM_CONF
|
||||
|
||||
systemctl restart sshd
|
||||
|
||||
17
service/inst_pubkey_rhel_9.sh
Normal file
17
service/inst_pubkey_rhel_9.sh
Normal file
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
SSHD_CONF=/etc/ssh/sshd_config
|
||||
SSHD_PAM_CONF=/etc/pam.d/sshd
|
||||
RUBLON_SSH_CONFIG=/etc/ssh/sshd_config.d/01-rublon-ssh.conf
|
||||
|
||||
cp -a /usr/share/rublon/01-rublon-ssh_pubkey.conf.default $RUBLON_SSH_CONFIG
|
||||
chown root:root $RUBLON_SSH_CONFIG
|
||||
chmod 640 $RUBLON_SSH_CONFIG
|
||||
|
||||
grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth required pam_rublon.so' $SSHD_PAM_CONF
|
||||
grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aaccount required pam_rublon.so' $SSHD_PAM_CONF
|
||||
grep -qe '#auth \+substack \+password-auth' $SSHD_PAM_CONF || sed -i 's/auth \+substack \+password-auth/#auth substack password-auth/' $SSHD_PAM_CONF
|
||||
|
||||
systemctl restart sshd
|
||||
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
SSHD_CONF=/etc/ssh/sshd_config
|
||||
SSHD_PAM_CONF=/etc/pam.d/sshd
|
||||
RUBLON_CONFIG=/etc/rublon.config
|
||||
RUBLON_SSH_CONFIG=/etc/ssh/sshd_config.d/01-rublon-ssh.conf
|
||||
|
||||
if [ ! -f $RUBLON_CONFIG ]
|
||||
then
|
||||
cp -a /usr/share/rublon/rublon.config.defaults $RUBLON_CONFIG
|
||||
chown root:root $RUBLON_CONFIG
|
||||
chmod 640 $RUBLON_CONFIG
|
||||
fi
|
||||
cp -a /usr/share/rublon/service/01-rublon-ssh_pubkey.conf.default $RUBLON_SSH_CONFIG
|
||||
chown root:root $RUBLON_SSH_CONFIG
|
||||
chmod 640 $RUBLON_SSH_CONFIG
|
||||
|
||||
if [ -f /etc/os-release ]
|
||||
then
|
||||
. /etc/os-release
|
||||
fi
|
||||
|
||||
#if [[ $ID == "rhel" || $ID=="alma" || $ID=="rocky" ]]
|
||||
#then
|
||||
# cd /usr/share/rublon/service
|
||||
# checkmodule -M -m -o login_rublon.mod login_rublon.te
|
||||
# semodule_package -o login_rublon.pp -m login_rublon.mod
|
||||
# semodule -i login_rublon.pp
|
||||
#fi
|
||||
|
||||
grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth required pam_rublon.so' $SSHD_PAM_CONF
|
||||
grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aaccount required pam_rublon.so' $SSHD_PAM_CONF
|
||||
grep -qe '@include common-auth' $SSHD_PAM_CONF || sed -i 's/@include common-auth/#@include common-auth/' $SSHD_PAM_CONF
|
||||
|
||||
if [[ $ID == "rhel" || $ID=="alma" || $ID=="rocky" ]]
|
||||
then
|
||||
systemctl restart sshd
|
||||
else
|
||||
deb-systemd-invoke restart ssh.service
|
||||
fi
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
SSHD_CONF=/etc/ssh/sshd_config
|
||||
SSHD_PAM_CONF=/etc/pam.d/sshd
|
||||
RUBLON_SSH_CONFIG=/etc/ssh/sshd_config.d/01-rublon-ssh.conf
|
||||
|
||||
cp -a /usr/share/rublon/01-rublon-ssh_pubkey.conf.default $RUBLON_SSH_CONFIG
|
||||
chown root:root $RUBLON_SSH_CONFIG
|
||||
chmod 640 $RUBLON_SSH_CONFIG
|
||||
|
||||
if [ -f /etc/os-release ]
|
||||
then
|
||||
. /etc/os-release
|
||||
fi
|
||||
sed -i '/auth required pam_rublon.so/d' $SSHD_PAM_CONF
|
||||
|
||||
if [[ $ID == "rhel" || $ID=="alma" || $ID=="rocky" || $ID=="Centos" ]]
|
||||
then
|
||||
grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth required pam_rublon.so' $SSHD_PAM_CONF
|
||||
grep -qe '#auth substack password-auth' $SSHD_PAM_CONF || sed -i -e 's/auth substack password-auth/#auth substack password-auth/g' $SSHD_PAM_CONF
|
||||
|
||||
elif [ $ID = "Debian" ]
|
||||
then
|
||||
grep -qe 'auth requisite pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth requisite pam_rublon.so' $SSHD_PAM_CONF
|
||||
else
|
||||
grep -qe 'auth requisite pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth requisite pam_rublon.so' $SSHD_PAM_CONF
|
||||
grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aaccount required pam_rublon.so' $SSHD_PAM_CONF
|
||||
fi
|
||||
|
||||
grep -qe '#@include common-auth' $SSHD_PAM_CONF || sed -i 's/@include common-auth/#@include common-auth/' $SSHD_PAM_CONF
|
||||
if [[ $ID == "rhel" || $ID=="alma" || $ID=="rocky" || $ID=="Centos" ]]
|
||||
then
|
||||
systemctl restart sshd
|
||||
else
|
||||
deb-systemd-invoke restart ssh.service
|
||||
fi
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
auth required pam_env.so
|
||||
auth requisite pam_rublon.so
|
||||
auth requisite pam_unix.so
|
||||
auth sufficient pam_rublon.so
|
||||
auth required pam_deny.so
|
||||
Loading…
Reference in New Issue
Block a user