Bwi/bugfix round2 (#9)
* Fix log file access, refactor configuration reading class * Remove bypass option in favor of failmode * fix loging, print enrolment info * Add EMAIL method * Add yubi authentication method * Add support for verification message * Add verification * Made changes in Vagrant's files to run different OSs * Switch off tests and packages demands to run PAM on Debian 11 * Add authentication totp * Changes in utils * Remove unnessesary interface * Changed vagrant files and postinstal script for Ubuntu 20 and 22 * Moved adding PasswordAuth to vagrant file from posinst * Added ubuntu 24.04 * Set version * Poprawki UI * WebSocket implementation * Add totp authentication method * fixup changes in utils * Remove unnessesary interface and simplify code * Remove "default" message handler from WebSocket class * Change display names of known authentication methods * Cleanup code in 'main' file * Add CheckApplication * Remove unused function * Changed vagrant files and postinstal script for Ubuntu 20 and 22 * Moved adding PasswordAuth to vagrant file from posinst * Added ubuntu 24.04 * Set version to 2.0.2 * Proper handle for missing configuration * Fixup use value of optional object * Add more vCPU/RAM to vagrant VM's + fix translations * Minor WS fixes, translations * Proper handler for Werification error * Make use of prompt parameter * Add max number of prompts * remove unused code, fir includes * Add Waiting status * Add check application status check --------- Co-authored-by: Madzik <m.w@linux.pl>
This commit is contained in:
parent
8ffa20fffa
commit
9415174eba
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
8
CMakeLists.txt
Normal file → Executable file
8
CMakeLists.txt
Normal file → Executable file
@ -5,9 +5,9 @@ project(rublon-ssh LANGUAGES CXX)
|
|||||||
include(CTest)
|
include(CTest)
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
set(PROJECT_VERSION_MAJOR 0)
|
set(PROJECT_VERSION_MAJOR 2)
|
||||||
set(PROJECT_VERSION_MINOR 1)
|
set(PROJECT_VERSION_MINOR 0)
|
||||||
set(PROJECT_VERSION_PATCH 0)
|
set(PROJECT_VERSION_PATCH 1)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
||||||
@ -15,7 +15,7 @@ set(CMAKE_CXX_EXTENSIONS NO)
|
|||||||
|
|
||||||
add_compile_options(-Wall -Wextra -Wpedantic -Wno-format-security)
|
add_compile_options(-Wall -Wextra -Wpedantic -Wno-format-security)
|
||||||
|
|
||||||
option(ENABLE_TESTS "Enable tests" ON)
|
option(ENABLE_TESTS "Enable tests" OFF)
|
||||||
|
|
||||||
add_custom_target(CONFIG_IDE SOURCES ${CMAKE_CURRENT_LIST_DIR}/rsc/rublon.config.defaults)
|
add_custom_target(CONFIG_IDE SOURCES ${CMAKE_CURRENT_LIST_DIR}/rsc/rublon.config.defaults)
|
||||||
add_custom_target(INSTSCRIPTS_IDE SUORCES ${CMAKE_CURRENT_LIST_DIR}/service/postinst)
|
add_custom_target(INSTSCRIPTS_IDE SUORCES ${CMAKE_CURRENT_LIST_DIR}/service/postinst)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ set(INC
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler_interface.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/curl.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/error.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/init.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/json.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/json.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/method_select.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/method_select.hpp
|
||||||
@ -46,7 +47,8 @@ install(
|
|||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(lib)
|
add_subdirectory(lib)
|
||||||
|
# add_subdirectory(bin)
|
||||||
|
|
||||||
if(${ENABLE_TESTS})
|
# if(${ENABLE_TESTS})
|
||||||
add_subdirectory(tests)
|
# add_subdirectory(tests)
|
||||||
endif()
|
# endif()
|
||||||
|
|||||||
30
PAM/ssh/bin/CMakeLists.txt
Normal file
30
PAM/ssh/bin/CMakeLists.txt
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
add_executable(rublon_check_application
|
||||||
|
rublon_check_application.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_options(rublon_check_application
|
||||||
|
PUBLIC
|
||||||
|
-fpic
|
||||||
|
-s
|
||||||
|
-Wl,--gc-sections
|
||||||
|
-flto
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(rublon_check_application
|
||||||
|
PUBLIC
|
||||||
|
rublon-ssh
|
||||||
|
-lcurl
|
||||||
|
-lssl
|
||||||
|
-lcrypto
|
||||||
|
)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
install(
|
||||||
|
TARGETS
|
||||||
|
rublon_check_application
|
||||||
|
DESTINATION
|
||||||
|
${CMAKE_INSTALL_SBINDIR}
|
||||||
|
COMPONENT
|
||||||
|
PAM
|
||||||
|
)
|
||||||
23
PAM/ssh/bin/rublon_check_application.cpp
Normal file
23
PAM/ssh/bin/rublon_check_application.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include <rublon/utils.hpp>
|
||||||
|
|
||||||
|
#include <rublon/core_handler.hpp>
|
||||||
|
#include <rublon/curl.hpp>
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
/// read system config
|
||||||
|
/// if valid send info
|
||||||
|
using namespace rublon;
|
||||||
|
details::initLog("CheckApplication");
|
||||||
|
auto config = ConfigurationFactory{}.systemConfig();
|
||||||
|
|
||||||
|
if(not config){
|
||||||
|
std::cout << "No valid rublon configuration available, check " << details::logPath() << " for more details";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
else{
|
||||||
|
// run some tests on
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreHandler CH{*config};
|
||||||
|
}
|
||||||
0
PAM/ssh/extern/tl/expected.hpp
vendored
Normal file → Executable file
0
PAM/ssh/extern/tl/expected.hpp
vendored
Normal file → Executable file
22
PAM/ssh/include/rublon/authentication_step_interface.hpp
Normal file → Executable file
22
PAM/ssh/include/rublon/authentication_step_interface.hpp
Normal file → Executable file
@ -1,13 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <iterator>
|
||||||
#include <rublon/core_handler_interface.hpp>
|
#include <rublon/core_handler_interface.hpp>
|
||||||
#include <rublon/pam.hpp>
|
#include <rublon/pam.hpp>
|
||||||
#include <rublon/pam_action.hpp>
|
#include <rublon/pam_action.hpp>
|
||||||
#include <rublon/utils.hpp>
|
#include <rublon/utils.hpp>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace rublon {
|
namespace rublon {
|
||||||
|
|
||||||
template < typename Impl >
|
|
||||||
class AuthenticationStep {
|
class AuthenticationStep {
|
||||||
protected:
|
protected:
|
||||||
std::string _systemToken;
|
std::string _systemToken;
|
||||||
@ -16,19 +18,6 @@ class AuthenticationStep {
|
|||||||
public:
|
public:
|
||||||
AuthenticationStep() = default;
|
AuthenticationStep() = default;
|
||||||
AuthenticationStep(std::string systemToken, std::string tid) : _systemToken{std::move(systemToken)}, _tid{std::move(tid)} {}
|
AuthenticationStep(std::string systemToken, std::string tid) : _systemToken{std::move(systemToken)}, _tid{std::move(tid)} {}
|
||||||
|
|
||||||
template < typename Handler_t >
|
|
||||||
auto fire(const CoreHandlerInterface< Handler_t > & coreHandler) const {
|
|
||||||
log(LogLevel::Info, "Starting %s step", static_cast< const Impl * >(this)->name);
|
|
||||||
return static_cast< const Impl * >(this)->handle(coreHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
template < typename Handler_t, typename PamInfo_t = LinuxPam >
|
|
||||||
auto fire(const CoreHandlerInterface< Handler_t > & coreHandler, const PamInfo_t & pam) const {
|
|
||||||
log(LogLevel::Info, "Starting %s step", static_cast< const Impl * >(this)->name);
|
|
||||||
return static_cast< const Impl * >(this)->handle(coreHandler, pam);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void addSystemToken(Document & body) const {
|
void addSystemToken(Document & body) const {
|
||||||
auto & alloc = body.GetAllocator();
|
auto & alloc = body.GetAllocator();
|
||||||
@ -39,6 +28,11 @@ class AuthenticationStep {
|
|||||||
auto & alloc = body.GetAllocator();
|
auto & alloc = body.GetAllocator();
|
||||||
body.AddMember("tid", Value{this->_tid.c_str(), alloc}, alloc);
|
body.AddMember("tid", Value{this->_tid.c_str(), alloc}, alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addAccessToken(Document & body, std::string_view token) const {
|
||||||
|
auto & alloc = body.GetAllocator();
|
||||||
|
body.AddMember("accessToken", Value{token.data(), static_cast< unsigned >(token.length()), alloc}, alloc);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rublon
|
} // namespace rublon
|
||||||
|
|||||||
114
PAM/ssh/include/rublon/check_application.hpp
Normal file
114
PAM/ssh/include/rublon/check_application.hpp
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <rapidjson/prettywriter.h>
|
||||||
|
#include <rapidjson/rapidjson.h>
|
||||||
|
|
||||||
|
#include <rublon/core_handler.hpp>
|
||||||
|
#include <rublon/curl.hpp>
|
||||||
|
#include <rublon/json.hpp>
|
||||||
|
#include <rublon/utils.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace rublon {
|
||||||
|
|
||||||
|
class Status {
|
||||||
|
std::string_view _statusDirPath = "/var/lib/rublon";
|
||||||
|
std::string_view _statusFilePath = "/var/lib/rublon/install.json";
|
||||||
|
|
||||||
|
RapidJSONPMRStackAlloc< 4 * 1024 > _alloc;
|
||||||
|
Document _data;
|
||||||
|
bool _statusUpdated;
|
||||||
|
|
||||||
|
std::string_view _appVersionKey = "/appVer";
|
||||||
|
std::string_view _appTypeKey = "/type";
|
||||||
|
std::string_view _paramSystemName = "/params/os";
|
||||||
|
std::string_view _paramSSHDUsePamName = "/params/sshd_config/usePam";
|
||||||
|
|
||||||
|
public:
|
||||||
|
Status() : _data{&_alloc} {
|
||||||
|
if(not details::exists(_statusFilePath.data())) {
|
||||||
|
log(LogLevel::Info, "application first run, creating status file at %s", _statusFilePath.data());
|
||||||
|
details::mkdir(_statusDirPath.data());
|
||||||
|
details::touch(_statusFilePath.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream ifs{_statusFilePath.data()};
|
||||||
|
if(!ifs.is_open()) {
|
||||||
|
/// TODO handle no file error
|
||||||
|
}
|
||||||
|
|
||||||
|
rapidjson::IStreamWrapper isw{ifs};
|
||||||
|
_data.ParseStream(isw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateAppVersion(std::string_view newVersion) {
|
||||||
|
RapidJSONPMRStackAlloc< 128 > stackAlloc;
|
||||||
|
auto version = JSONPointer{_appVersionKey.data(), &stackAlloc}.Get(_data);
|
||||||
|
if(not version || version->GetString() != newVersion) {
|
||||||
|
_statusUpdated = true;
|
||||||
|
auto version = Value{newVersion.data(), _data.GetAllocator()};
|
||||||
|
JSONPointer{_appVersionKey.data(), &stackAlloc}.Set(_data, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateSystemVersion(std::string_view system) {
|
||||||
|
RapidJSONPMRStackAlloc< 128 > stackAlloc;
|
||||||
|
auto version = JSONPointer{_paramSystemName.data(), &stackAlloc}.Get(_data);
|
||||||
|
if(not version || version->GetString() != system) {
|
||||||
|
_statusUpdated = true;
|
||||||
|
auto version = Value{system.data(), _data.GetAllocator()};
|
||||||
|
JSONPointer{_paramSystemName.data(), &stackAlloc}.Set(_data, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool updated() const {
|
||||||
|
return _statusUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
void save() {
|
||||||
|
if(updated()) {
|
||||||
|
memory::Monotonic_1k_HeapResource tmpResource;
|
||||||
|
RapidJSONPMRAlloc alloc{&tmpResource};
|
||||||
|
FileWriter s{_statusFilePath};
|
||||||
|
rapidjson::PrettyWriter< FileWriter, rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc > writer{s, &alloc};
|
||||||
|
writer.SetIndent(' ', 2);
|
||||||
|
_data.Accept(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Document & data() {
|
||||||
|
return _data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CheckApplication {
|
||||||
|
tl::expected< bool, Error > persistStatus(Status & status) const {
|
||||||
|
status.data().RemoveMember("systemToken");
|
||||||
|
status.save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
template < typename Hander_t >
|
||||||
|
tl::expected< int, Error > call(const CoreHandlerInterface< Hander_t > & coreHandler, std::string_view systemToken) const {
|
||||||
|
memory::Monotonic_1k_HeapResource mr;
|
||||||
|
RapidJSONPMRStackAlloc< 2048 > alloc{};
|
||||||
|
constexpr std::string_view api = "/api/app/init";
|
||||||
|
Status status;
|
||||||
|
|
||||||
|
const auto persist = [&](const auto /*ok*/) { return this->persistStatus(status); };
|
||||||
|
|
||||||
|
status.updateAppVersion("2.0.2");
|
||||||
|
status.updateSystemVersion(details::osName(&mr));
|
||||||
|
|
||||||
|
if(status.updated()) {
|
||||||
|
auto & alloc = status.data().GetAllocator();
|
||||||
|
status.data().AddMember("systemToken", Value{systemToken.data(), alloc}, alloc);
|
||||||
|
return coreHandler.request(alloc, api, status.data()).and_then(persist);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rublon
|
||||||
99
PAM/ssh/include/rublon/configuration.hpp
Normal file → Executable file
99
PAM/ssh/include/rublon/configuration.hpp
Normal file → Executable file
@ -7,20 +7,27 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "utils.hpp"
|
#include <rublon/utils.hpp>
|
||||||
|
#include <rublon/error.hpp>
|
||||||
|
#include <rublon/static_string.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
template < class T >
|
||||||
|
struct is_std_array : std::is_array< T > {};
|
||||||
|
template < class T, std::size_t N >
|
||||||
|
struct is_std_array< std::array< T, N > > : std::true_type {};
|
||||||
|
|
||||||
|
template < typename T >
|
||||||
|
constexpr bool is_std_array_v = is_std_array< T >::value;
|
||||||
|
|
||||||
namespace rublon {
|
namespace rublon {
|
||||||
class ConfigurationFactory;
|
class ConfigurationFactory;
|
||||||
|
|
||||||
class ConfigurationError {
|
enum class FailMode { bypass, deny };
|
||||||
public:
|
|
||||||
enum class Cause { NoDefaultValue };
|
|
||||||
const char * parameterName;
|
|
||||||
const char * cause;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Configuration {
|
class Configuration {
|
||||||
public:
|
public:
|
||||||
|
// change to StaticString
|
||||||
std::array< char, 33 > systemToken{};
|
std::array< char, 33 > systemToken{};
|
||||||
std::array< char, 33 > secretKey{};
|
std::array< char, 33 > secretKey{};
|
||||||
std::array< char, 300 > apiServer{};
|
std::array< char, 300 > apiServer{};
|
||||||
@ -28,8 +35,7 @@ class Configuration {
|
|||||||
bool enablePasswdEmail{};
|
bool enablePasswdEmail{};
|
||||||
bool logging{};
|
bool logging{};
|
||||||
bool autopushPrompt{};
|
bool autopushPrompt{};
|
||||||
bool bypass{};
|
FailMode failMode{};
|
||||||
bool offlineBypas{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -39,19 +45,10 @@ namespace {
|
|||||||
template < typename T >
|
template < typename T >
|
||||||
tl::expected< T, ConfigurationError > to(std::string_view);
|
tl::expected< T, ConfigurationError > to(std::string_view);
|
||||||
|
|
||||||
template <>
|
template < class T >
|
||||||
auto to(std::string_view arg) -> tl::expected< std::array<char, 33>, ConfigurationError > {
|
auto to_array(std::string_view arg) -> tl::expected< T, ConfigurationError > {
|
||||||
assert(arg.size()<=(33-1));
|
T value{};
|
||||||
|
assert(arg.size() <= (value.size() - 1));
|
||||||
std::array<char, 33> value{};
|
|
||||||
std::memcpy(value.data(), arg.data(), arg.size());
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
template <>
|
|
||||||
auto to(std::string_view arg) -> tl::expected< std::array<char, 300>, ConfigurationError > {
|
|
||||||
assert(arg.size()<=(300-1));
|
|
||||||
|
|
||||||
std::array<char, 300> value{};
|
|
||||||
std::memcpy(value.data(), arg.data(), arg.size());
|
std::memcpy(value.data(), arg.data(), arg.size());
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -65,6 +62,28 @@ namespace {
|
|||||||
return conv::to_uint32(arg).value_or(0);
|
return conv::to_uint32(arg).value_or(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
auto to(std::string_view arg) -> tl::expected< FailMode, ConfigurationError > {
|
||||||
|
if(arg == "safe" || "bypass")
|
||||||
|
return FailMode::bypass;
|
||||||
|
if(arg == "secure" || 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_std_array_v< T >) {
|
||||||
|
return to_array< T >(arg);
|
||||||
|
} else {
|
||||||
|
return to< T >(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
struct Entry {
|
struct Entry {
|
||||||
enum class Source { UserInput, DefaultValue };
|
enum class Source { UserInput, DefaultValue };
|
||||||
@ -72,12 +91,20 @@ struct Entry {
|
|||||||
static constexpr auto make_read_function() {
|
static constexpr auto make_read_function() {
|
||||||
using pType = decltype(member_ptr_t(member));
|
using pType = decltype(member_ptr_t(member));
|
||||||
|
|
||||||
return [](const Entry * _this,
|
return
|
||||||
Configuration * configuration,
|
[](const Entry * _this, Configuration * configuration, std::string_view userInput) -> tl::expected< Source, ConfigurationError > {
|
||||||
std::string_view userInput) -> tl::expected< Source, ConfigurationError > {
|
const auto setDefaultValue = [&](const ConfigurationError & error) -> tl::expected< Source, ConfigurationError > {
|
||||||
const auto setDefaultValue = [&](const auto & value) -> tl::expected< Source, ConfigurationError > {
|
log(LogLevel::Warning, "applying user provided value for %s parameter, faild with %s", _this->name, error.what());
|
||||||
configuration->*member = value;
|
if(_this->defaultValue != nullptr) {
|
||||||
|
configuration->*member = parse< pType >(_this->defaultValue).value();
|
||||||
return Source::DefaultValue;
|
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 > {
|
const auto saveValue = [&](const auto & value) -> tl::expected< Source, ConfigurationError > {
|
||||||
@ -85,19 +112,7 @@ struct Entry {
|
|||||||
return Source::UserInput;
|
return Source::UserInput;
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto returnBadInput = [&](const auto & /*error*/) -> tl::expected< Source, ConfigurationError > {
|
return parse< pType >(userInput).and_then(saveValue).or_else(setDefaultValue);
|
||||||
return tl::unexpected{ConfigurationError{"", ""}};
|
|
||||||
};
|
|
||||||
|
|
||||||
if(userInput.empty()) {
|
|
||||||
if(_this->defaultValue != nullptr) {
|
|
||||||
return to< pType >(_this->defaultValue).and_then(setDefaultValue).or_else(returnBadInput);
|
|
||||||
} else {
|
|
||||||
return tl::unexpected{ConfigurationError{_this->name, "No default value"}};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return to< pType >(userInput).and_then(saveValue).or_else(returnBadInput);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +133,7 @@ struct Entry {
|
|||||||
|
|
||||||
const auto logError = [&](const auto & error) -> tl::expected< Source, ConfigurationError > {
|
const auto logError = [&](const auto & error) -> tl::expected< Source, ConfigurationError > {
|
||||||
rublon::log(LogLevel::Error,
|
rublon::log(LogLevel::Error,
|
||||||
"Configuration parameter '%s' is has no default value and is not provided in user configuraion, aborting",
|
"Configuration parameter '%s' has no default value and is not provided in user configuraion, aborting",
|
||||||
this->name);
|
this->name);
|
||||||
return tl::unexpected{error};
|
return tl::unexpected{error};
|
||||||
};
|
};
|
||||||
@ -140,7 +155,7 @@ constexpr static inline std::array< Entry, 8 > configurationVariables = { //
|
|||||||
make_entry< &Configuration::prompt >("prompt", "1"),
|
make_entry< &Configuration::prompt >("prompt", "1"),
|
||||||
make_entry< &Configuration::enablePasswdEmail >("enablePasswdEmail", "true"),
|
make_entry< &Configuration::enablePasswdEmail >("enablePasswdEmail", "true"),
|
||||||
make_entry< &Configuration::autopushPrompt >("autopushPrompt", "false"),
|
make_entry< &Configuration::autopushPrompt >("autopushPrompt", "false"),
|
||||||
make_entry< &Configuration::offlineBypas >("failMode", "bypas")};
|
make_entry< &Configuration::failMode >("failMode", "deny")};
|
||||||
|
|
||||||
class ConfigurationFactory {
|
class ConfigurationFactory {
|
||||||
public:
|
public:
|
||||||
|
|||||||
113
PAM/ssh/include/rublon/core_handler.hpp
Normal file → Executable file
113
PAM/ssh/include/rublon/core_handler.hpp
Normal file → Executable file
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <memory_resource>
|
#include <memory_resource>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@ -7,61 +8,87 @@
|
|||||||
#include <rublon/core_handler_interface.hpp>
|
#include <rublon/core_handler_interface.hpp>
|
||||||
#include <rublon/curl.hpp>
|
#include <rublon/curl.hpp>
|
||||||
#include <rublon/json.hpp>
|
#include <rublon/json.hpp>
|
||||||
|
#include <rublon/pam_action.hpp>
|
||||||
#include <rublon/sign.hpp>
|
#include <rublon/sign.hpp>
|
||||||
|
#include <rublon/utils.hpp>
|
||||||
#include <rublon/websockets.hpp>
|
#include <rublon/websockets.hpp>
|
||||||
|
|
||||||
#include <rublon/pam_action.hpp>
|
|
||||||
|
|
||||||
#include <tl/expected.hpp>
|
#include <tl/expected.hpp>
|
||||||
|
|
||||||
#include <rublon/core_handler_interface.hpp>
|
|
||||||
|
|
||||||
namespace rublon {
|
namespace rublon {
|
||||||
|
|
||||||
template < typename HttpHandler = CURL >
|
template < typename HttpHandler = CURL >
|
||||||
class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
||||||
std::string secretKey;
|
std::string secretKey;
|
||||||
std::string url;
|
std::string rublonCore;
|
||||||
bool bypass;
|
|
||||||
|
mutable std::unique_ptr< WebSocket > _ws;
|
||||||
|
|
||||||
void signRequest(Request & request) const {
|
void signRequest(Request & request) const {
|
||||||
request.headers["X-Rublon-Signature"] = std::pmr::string{signData(request.body, secretKey).data(), request.headers.get_allocator()};
|
request.headers["X-Rublon-Signature"] = std::pmr::string{signData(request.body, secretKey).data(), request.headers.get_allocator()};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool responseSigned(const Response & response) const {
|
bool hasSignature(const Response & response) const {
|
||||||
|
if(not response.headers.count("x-rublon-signature")) {
|
||||||
|
log(LogLevel::Error, "No x-rublon-signature");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bool hasRateLimit(const Response & response)const {
|
||||||
|
// if(not response.headers.count("x-ratelimit-remaining")) {
|
||||||
|
// log(LogLevel::Error, "No x-ratelimit-remaining");
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// bool rateIsAcceptable(const Response &response)const{
|
||||||
|
//// todo validate rate type and number
|
||||||
|
// }
|
||||||
|
|
||||||
|
bool signatureIsNatValid(const Response & response) const {
|
||||||
const auto & xRubResp = response.headers.at("x-rublon-signature");
|
const auto & xRubResp = response.headers.at("x-rublon-signature");
|
||||||
const auto & sign = signData(response.body, secretKey);
|
const auto & sign = signData(response.body, secretKey);
|
||||||
const bool signatureMatch = xRubResp == sign.data();
|
const bool signatureMatch = xRubResp == sign.data();
|
||||||
if(not signatureMatch)
|
if(not signatureMatch)
|
||||||
log(LogLevel::Error, "Signature mismatch %s != %s ", xRubResp.c_str(), sign.data());
|
log(LogLevel::Error, "Signature mismatch %s != %s ", xRubResp.c_str(), sign.data());
|
||||||
return signatureMatch;
|
return not signatureMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool containsException(const Document & coreResponse) const {
|
bool hasException(const Document & coreResponse) const {
|
||||||
using namespace std::string_view_literals;
|
using namespace std::string_view_literals;
|
||||||
return coreResponse.HasMember("status") and coreResponse["status"].GetString() == "ERROR"sv;
|
return coreResponse.HasMember("status") and coreResponse["status"].GetString() == "ERROR"sv;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isUnHealthy(const Document & coreResponse) const {
|
bool isUnHealthy(const Document & coreResponse) const {
|
||||||
return coreResponse.HasParseError() or not coreResponse.HasMember("result");
|
return coreResponse.HasParseError();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
HttpHandler http{};
|
HttpHandler http{};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CoreHandler(const Configuration & config)
|
CoreHandler(const Configuration & config) : secretKey{config.secretKey.data()}, rublonCore{config.apiServer.data()}, http{} {}
|
||||||
: secretKey{config.secretKey.data()}, url{config.apiServer.data()}, bypass{config.bypass}, http{} {}
|
|
||||||
|
|
||||||
tl::expected< std::reference_wrapper< const Response >, Error > validateSignature(const Response & response) const {
|
tl::expected< std::reference_wrapper< const Response >, Error > validateSignature(const Response & response) const {
|
||||||
if(not responseSigned(response)) {
|
if(hasSignature(response) and signatureIsNatValid(response)) {
|
||||||
log(LogLevel::Error, "rublon core response is not signed");
|
log(LogLevel::Error, "rublon core response is not signed properly");
|
||||||
return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}};
|
return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}};
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tl::expected< std::reference_wrapper< const Response >, Error > validateRate(const Response & response) const {
|
||||||
|
// if(hasSignature(response) and signatureIsNatValid(response)) {
|
||||||
|
// log(LogLevel::Error, "rublon core response is not signed properly");
|
||||||
|
// return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}};
|
||||||
|
// }
|
||||||
|
// return response;
|
||||||
|
// }
|
||||||
|
|
||||||
tl::expected< Document, Error > validateResponse(RapidJSONPMRAlloc & alloc, const Response & response) const {
|
tl::expected< Document, Error > validateResponse(RapidJSONPMRAlloc & alloc, const Response & response) const {
|
||||||
Document resp{&alloc};
|
Document resp{&alloc};
|
||||||
resp.Parse(response.body.c_str());
|
resp.Parse(response.body.c_str());
|
||||||
@ -71,40 +98,40 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
|||||||
if(isUnHealthy(resp)) {
|
if(isUnHealthy(resp)) {
|
||||||
log(LogLevel::Error, "Rublon Core responded with broken data");
|
log(LogLevel::Error, "Rublon Core responded with broken data");
|
||||||
return tl::unexpected{CoreHandlerError{CoreHandlerError::BrokenData}};
|
return tl::unexpected{CoreHandlerError{CoreHandlerError::BrokenData}};
|
||||||
}
|
} else if(hasException(resp)) {
|
||||||
|
const auto * exception = JSONPointer{"/result/exception", &alloc}.Get(resp);
|
||||||
else if(containsException(resp)) {
|
log(LogLevel::Error, "Rublon Core responded with '%s' exception", exception->GetString());
|
||||||
const auto* exception = JSONPointer{"/result/exception", &alloc}.Get(resp);
|
|
||||||
log(LogLevel::Error, "Rublon Core responded with '%s' exception", exception);
|
|
||||||
return handleCoreException(exception->GetString());
|
return handleCoreException(exception->GetString());
|
||||||
|
} else if(not hasSignature(response)) {
|
||||||
|
// additional check for mallformed responses (A invalid response, without any x-rublon-signature will stop at this check)
|
||||||
|
return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}};
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
tl::unexpected< Error > handleCoreException(std::string_view exceptionString) const {
|
tl::unexpected< Error > handleCoreException(std::string_view exceptionString) const {
|
||||||
if(exceptionString == "UserBypassedException" or exceptionString == "UserNotFoundException") {
|
// can happen only dyring check application step
|
||||||
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserBaypass}}};
|
if(auto error = RublonCheckApplicationException::fromString(exceptionString); error.has_value())
|
||||||
} else {
|
return tl::unexpected{Error{error.value()}};
|
||||||
|
|
||||||
|
// auth problems likely bad configuration in rublon admin console
|
||||||
|
if(auto error = RublonAuthenticationInterrupt::fromString(exceptionString); error.has_value())
|
||||||
|
return tl::unexpected{Error{error.value()}};
|
||||||
|
|
||||||
|
// verification error wrong passcode etc.
|
||||||
|
if(auto error = WerificationError::fromString(exceptionString); error.has_value())
|
||||||
|
return tl::unexpected{Error{error.value()}};
|
||||||
|
|
||||||
|
// other exceptions, just "throw"
|
||||||
return tl::unexpected{
|
return tl::unexpected{
|
||||||
Error{CoreHandlerError{CoreHandlerError::RublonCoreException, std::string{exceptionString.data(), exceptionString.size()}}}};
|
Error{CoreHandlerError{CoreHandlerError::RublonCoreException, std::string{exceptionString.data(), exceptionString.size()}}}};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
tl::expected< Document, Error > handleError(const Error & error) const {
|
tl::expected< Document, Error > handleError(const Error & error) const {
|
||||||
return tl::unexpected{Error{error}};
|
return tl::unexpected{Error{error}};
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename T >
|
|
||||||
static void stringifyTo(const Document & body, T & to) {
|
|
||||||
memory::Monotonic_2k_HeapResource tmpResource;
|
|
||||||
RapidJSONPMRAlloc alloc{&tmpResource};
|
|
||||||
StringBuffer jsonStr{&alloc};
|
|
||||||
Writer writer{jsonStr, &alloc};
|
|
||||||
body.Accept(writer);
|
|
||||||
to = jsonStr.GetString();
|
|
||||||
}
|
|
||||||
|
|
||||||
tl::expected< Document, Error > request(RapidJSONPMRAlloc & mr, std::string_view path, const Document & body) const {
|
tl::expected< Document, Error > request(RapidJSONPMRAlloc & mr, std::string_view path, const Document & body) const {
|
||||||
memory::StrictMonotonic_8k_HeapResource memoryResource;
|
memory::StrictMonotonic_8k_HeapResource memoryResource;
|
||||||
|
|
||||||
@ -118,9 +145,10 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
|||||||
|
|
||||||
request.headers["Content-Type"] = pmrs("application/json");
|
request.headers["Content-Type"] = pmrs("application/json");
|
||||||
request.headers["Accept"] = pmrs("application/json");
|
request.headers["Accept"] = pmrs("application/json");
|
||||||
|
|
||||||
stringifyTo(body, request.body);
|
stringifyTo(body, request.body);
|
||||||
signRequest(request);
|
signRequest(request);
|
||||||
std::pmr::string uri{url + path.data(), &memoryResource};
|
std::pmr::string uri{rublonCore + path.data(), &memoryResource};
|
||||||
|
|
||||||
return http
|
return http
|
||||||
.request(uri, request, response) //
|
.request(uri, request, response) //
|
||||||
@ -129,17 +157,16 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
|||||||
.or_else(handleError);
|
.or_else(handleError);
|
||||||
}
|
}
|
||||||
|
|
||||||
///TODO use WS_Handler type to allow mocking / changind implementation of WS
|
bool createWSConnection(std::string_view tid) const {
|
||||||
tl::expected< AuthenticationStatus, Error > waitForConfirmation(std::string_view tid) const {
|
if(not _ws) {
|
||||||
bool aproved{};
|
_ws = std::make_unique< WebSocket >();
|
||||||
|
}
|
||||||
|
|
||||||
WebSocket ws;
|
return _ws->AttachToCore(rublonCore, tid);
|
||||||
aproved = ws.connect(url, tid);
|
}
|
||||||
|
|
||||||
log(LogLevel::Info, "websocket :s", aproved ? "approved" : "denied");
|
auto listen() const {
|
||||||
|
return _ws->listen();
|
||||||
return aproved ? AuthenticationStatus{AuthenticationStatus::Action::Confirmed} :
|
|
||||||
AuthenticationStatus{AuthenticationStatus::Action::Denied};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
13
PAM/ssh/include/rublon/core_handler_interface.hpp
Normal file → Executable file
13
PAM/ssh/include/rublon/core_handler_interface.hpp
Normal file → Executable file
@ -4,22 +4,27 @@
|
|||||||
|
|
||||||
#include <rublon/error.hpp>
|
#include <rublon/error.hpp>
|
||||||
#include <rublon/json.hpp>
|
#include <rublon/json.hpp>
|
||||||
#include <rublon/utils.hpp>
|
|
||||||
#include <rublon/pam_action.hpp>
|
#include <rublon/pam_action.hpp>
|
||||||
|
#include <rublon/utils.hpp>
|
||||||
|
|
||||||
namespace rublon {
|
namespace rublon {
|
||||||
|
|
||||||
template < typename Impl >
|
template < typename Impl >
|
||||||
class CoreHandlerInterface {
|
class CoreHandlerInterface {
|
||||||
public:
|
public:
|
||||||
tl::expected< Document, Error > request(RapidJSONPMRAlloc & mr, std::string_view path, const rublon::Document & body) const {
|
tl::expected< Document, Error > request(RapidJSONPMRAlloc & mr, std::string_view path, const Document & body) const {
|
||||||
rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::request");
|
rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::request");
|
||||||
return static_cast< const Impl * >(this)->request(mr, path, body);
|
return static_cast< const Impl * >(this)->request(mr, path, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
tl::expected< AuthenticationStatus, Error > waitForConfirmation(std::string_view tid) const {
|
bool createWSConnection(std::string_view tid) const {
|
||||||
rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::listen");
|
rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::listen");
|
||||||
return static_cast< const Impl * >(this)->waitForConfirmation(tid);
|
return static_cast< const Impl * >(this)->createWSConnection(tid);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto listen() const {
|
||||||
|
rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::listen");
|
||||||
|
return static_cast< const Impl * >(this)->listen();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace rublon
|
} // namespace rublon
|
||||||
|
|||||||
8
PAM/ssh/include/rublon/curl.hpp
Normal file → Executable file
8
PAM/ssh/include/rublon/curl.hpp
Normal file → Executable file
@ -6,10 +6,9 @@
|
|||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <map>
|
||||||
|
|
||||||
#include <memory_resource>
|
#include <memory_resource>
|
||||||
|
|
||||||
@ -78,10 +77,10 @@ class CURL {
|
|||||||
|
|
||||||
tl::expected< std::reference_wrapper< Response >, ConnectionError >
|
tl::expected< std::reference_wrapper< Response >, ConnectionError >
|
||||||
request(std::string_view uri, const Request & request, Response & response) const {
|
request(std::string_view uri, const Request & request, Response & response) const {
|
||||||
memory::MonotonicStackResource< 8 * 1024 > stackResource;
|
memory::MonotonicStackResource< 4 * 1024 > stackResource;
|
||||||
|
|
||||||
std::pmr::string response_data{&stackResource};
|
std::pmr::string response_data{&stackResource};
|
||||||
response_data.reserve(7000);
|
response_data.reserve(3000);
|
||||||
|
|
||||||
/// TODO this can be done on stack using pmr
|
/// 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);
|
auto curl_headers = std::unique_ptr< curl_slist, void (*)(curl_slist *) >(nullptr, curl_slist_free_all);
|
||||||
@ -100,7 +99,6 @@ class CURL {
|
|||||||
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
|
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
|
||||||
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_data);
|
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_data);
|
||||||
|
|
||||||
|
|
||||||
log(LogLevel::Debug, "Sending request to %s", uri.data());
|
log(LogLevel::Debug, "Sending request to %s", uri.data());
|
||||||
for(const auto &[name, value] : request.headers){
|
for(const auto &[name, value] : request.headers){
|
||||||
log(LogLevel::Debug, "Header %s:%s", name.c_str(), value.c_str());
|
log(LogLevel::Debug, "Header %s:%s", name.c_str(), value.c_str());
|
||||||
|
|||||||
149
PAM/ssh/include/rublon/error.hpp
Normal file → Executable file
149
PAM/ssh/include/rublon/error.hpp
Normal file → Executable file
@ -1,6 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <optional>
|
||||||
#include <rublon/non_owning_ptr.hpp>
|
#include <rublon/non_owning_ptr.hpp>
|
||||||
|
#include <string_view>
|
||||||
#include <tl/expected.hpp>
|
#include <tl/expected.hpp>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -8,21 +12,41 @@
|
|||||||
|
|
||||||
namespace rublon {
|
namespace rublon {
|
||||||
|
|
||||||
|
template < typename... Types >
|
||||||
|
constexpr std::array< std::string_view, sizeof...(Types) > make_array(Types... names) {
|
||||||
|
return {std::forward< Types >(names)...};
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto test = make_array("one", "two");
|
||||||
|
|
||||||
#define names constexpr static inline const char * errorClassPrettyName[]
|
#define names constexpr static inline const char * errorClassPrettyName[]
|
||||||
|
|
||||||
|
class ConfigurationError {
|
||||||
|
public:
|
||||||
|
enum class ErrorClass { RequiredValueNotFound, BadFailMode, BadInput, Empty };
|
||||||
|
constexpr static auto errorClassPrettyName = make_array("RequiredValueNotFound", "BadFailMode", "BadInput", "Empty");
|
||||||
|
constexpr static auto prettyName = "Configurtion Error";
|
||||||
|
|
||||||
|
constexpr ConfigurationError(ErrorClass e = ErrorClass::RequiredValueNotFound) : errorClass{e} {}
|
||||||
|
|
||||||
|
constexpr const char * what() const {
|
||||||
|
return errorClassPrettyName[static_cast< int >(errorClass)].data();
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorClass errorClass;
|
||||||
|
};
|
||||||
|
|
||||||
class ConnectionError {
|
class ConnectionError {
|
||||||
public:
|
public:
|
||||||
enum ErrorClass { Timeout, HttpError };
|
enum ErrorClass { Timeout, HttpError, ClientError };
|
||||||
names = {"Timeout", "Error"};
|
constexpr static auto errorClassPrettyName = make_array("Timeout", "Error", "Client Error");
|
||||||
|
constexpr static auto prettyName = "Connection Error";
|
||||||
constexpr static inline auto prettyName = "Connection Error";
|
|
||||||
|
|
||||||
constexpr ConnectionError() : errorClass{Timeout}, httpCode(200) {}
|
constexpr ConnectionError() : errorClass{Timeout}, httpCode(200) {}
|
||||||
constexpr ConnectionError(ErrorClass e, long httpCode) : errorClass{e}, httpCode(httpCode) {}
|
constexpr ConnectionError(ErrorClass e, long httpCode) : errorClass{e}, httpCode(httpCode) {}
|
||||||
|
|
||||||
constexpr const char * what() const {
|
constexpr const char * what() const {
|
||||||
return errorClassPrettyName[static_cast< int >(errorClass)];
|
return errorClassPrettyName[static_cast< int >(errorClass)].data();
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorClass errorClass;
|
ErrorClass errorClass;
|
||||||
@ -31,33 +55,34 @@ class ConnectionError {
|
|||||||
|
|
||||||
class CoreHandlerError {
|
class CoreHandlerError {
|
||||||
public:
|
public:
|
||||||
enum ErrorClass { BadSigature, RublonCoreException, BrokenData };
|
enum ErrorClass { BadSigature, RublonCoreException, BrokenData, APIException, TransactionException };
|
||||||
names = {"BadSigature", "RublonCoreException", "BrokenData"};
|
constexpr static auto errorClassPrettyName =
|
||||||
|
make_array("BadSigature", "RublonCoreException", "BrokenData", "APIException", "TransactionException");
|
||||||
|
constexpr static auto prettyName = "Core Handler Error";
|
||||||
|
|
||||||
constexpr static inline auto prettyName = "Core Handler Error";
|
// APIException -> code: 10
|
||||||
|
// TransactionException -> code: 11
|
||||||
|
|
||||||
CoreHandlerError(ErrorClass e = BadSigature) : errorClass{e} {}
|
CoreHandlerError(ErrorClass e = BadSigature, std::string r = "") : errorClass{e}, reson{std::move(r)} {}
|
||||||
CoreHandlerError(ErrorClass e, std::string r) : errorClass{e}, reson{std::move(r)} {}
|
|
||||||
|
|
||||||
constexpr const char * what() const {
|
constexpr const char * what() const {
|
||||||
return errorClassPrettyName[static_cast< int >(errorClass)];
|
return errorClassPrettyName[static_cast< int >(errorClass)].data();
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorClass errorClass;
|
ErrorClass errorClass;
|
||||||
std::string reson;
|
std::string reson; // TODO dynamic mem
|
||||||
};
|
};
|
||||||
|
|
||||||
class MethodError {
|
class MethodError {
|
||||||
public:
|
public:
|
||||||
enum ErrorClass { BadMethod, BadUserInput, NoMethodAvailable };
|
enum ErrorClass { BadMethod, BadUserInput, NoMethodAvailable };
|
||||||
names = {"BadMethod", "BadUserInput", "NoMethodAvailable"};
|
constexpr static auto errorClassPrettyName = make_array("BadMethod", "BadUserInput", "NoMethodAvailable");
|
||||||
|
constexpr static auto prettyName = "Method Error";
|
||||||
constexpr static inline auto prettyName = "Method Error";
|
|
||||||
|
|
||||||
constexpr MethodError(ErrorClass e = BadMethod) : errorClass{e} {}
|
constexpr MethodError(ErrorClass e = BadMethod) : errorClass{e} {}
|
||||||
|
|
||||||
constexpr const char * what() const {
|
constexpr const char * what() const {
|
||||||
return errorClassPrettyName[static_cast< int >(errorClass)];
|
return errorClassPrettyName[static_cast< int >(errorClass)].data();
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorClass errorClass;
|
ErrorClass errorClass;
|
||||||
@ -65,15 +90,26 @@ class MethodError {
|
|||||||
|
|
||||||
class WerificationError {
|
class WerificationError {
|
||||||
public:
|
public:
|
||||||
enum ErrorClass { WrongCode };
|
enum ErrorClass {
|
||||||
names = {"WrongCode"};
|
BadInput, // User input has incorrect characters or length
|
||||||
|
PasscodeException // Exception from core
|
||||||
|
};
|
||||||
|
constexpr static auto errorClassPrettyName = make_array("BadInput", "PasscodeException");
|
||||||
constexpr static inline auto prettyName = "Werification Error";
|
constexpr static inline auto prettyName = "Werification Error";
|
||||||
|
|
||||||
constexpr WerificationError(ErrorClass e = WrongCode) : errorClass{e} {}
|
constexpr WerificationError(ErrorClass e = PasscodeException) : errorClass{e} {}
|
||||||
|
|
||||||
constexpr const char * what() const {
|
constexpr const char * what() const {
|
||||||
return errorClassPrettyName[static_cast< int >(errorClass)];
|
return errorClassPrettyName[static_cast< int >(errorClass)].data();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional< WerificationError > fromString(std::string_view name) {
|
||||||
|
for(std::size_t i{}; i < errorClassPrettyName.size(); i++) {
|
||||||
|
if(errorClassPrettyName.at(i) == name) {
|
||||||
|
return std::make_optional(WerificationError{static_cast< ErrorClass >(i)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorClass errorClass;
|
ErrorClass errorClass;
|
||||||
@ -81,35 +117,85 @@ class WerificationError {
|
|||||||
|
|
||||||
class RublonAuthenticationInterrupt {
|
class RublonAuthenticationInterrupt {
|
||||||
public:
|
public:
|
||||||
enum ErrorClass { UserBaypass, UserDenied, UserPending };
|
enum ErrorClass { UserBaypass, UserDenied, UserPending, UserWaiting, UserNotFound };
|
||||||
names = {"UserBaypass", "UserDenied", "UserPending"};
|
constexpr static auto errorClassPrettyName =
|
||||||
|
make_array("UserBypassedException", "UserDenied", "UserPending", "UserWaiting", "UserNotFoundException");
|
||||||
constexpr static inline auto prettyName = "Rublon Authentication Interrupt";
|
constexpr static auto prettyName = "Rublon Authentication Interrupt";
|
||||||
|
|
||||||
RublonAuthenticationInterrupt(ErrorClass e = UserBaypass) : errorClass{e} {}
|
RublonAuthenticationInterrupt(ErrorClass e = UserBaypass) : errorClass{e} {}
|
||||||
|
|
||||||
constexpr const char * what() const {
|
constexpr const char * what() const {
|
||||||
return errorClassPrettyName[static_cast< int >(errorClass)];
|
return errorClassPrettyName[static_cast< int >(errorClass)].data();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional< RublonAuthenticationInterrupt > fromString(std::string_view name) {
|
||||||
|
for(std::size_t i{}; i < errorClassPrettyName.size(); i++) {
|
||||||
|
if(errorClassPrettyName.at(i) == name) {
|
||||||
|
return std::make_optional(RublonAuthenticationInterrupt{static_cast< ErrorClass >(i)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorClass errorClass;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RublonCheckApplicationException {
|
||||||
|
public:
|
||||||
|
enum ErrorClass { ApplicationNotFoundException, InvalidSignatureException, UnsupportedVersionException };
|
||||||
|
|
||||||
|
constexpr static auto errorClassPrettyName =
|
||||||
|
make_array("ApplicationNotFoundException", "InvalidSignatureException", "UnsupportedVersionException");
|
||||||
|
constexpr static auto prettyName = "Rublon Check Application Interrupt";
|
||||||
|
|
||||||
|
RublonCheckApplicationException(ErrorClass e = ApplicationNotFoundException) : errorClass{e} {}
|
||||||
|
|
||||||
|
constexpr const char * what() const {
|
||||||
|
return errorClassPrettyName[static_cast< int >(errorClass)].data();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional< RublonCheckApplicationException > fromString(std::string_view name) {
|
||||||
|
for(std::size_t i{}; i < errorClassPrettyName.size(); i++) {
|
||||||
|
if(errorClassPrettyName.at(i) == name) {
|
||||||
|
return std::make_optional(RublonCheckApplicationException{static_cast< ErrorClass >(i)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorClass errorClass;
|
ErrorClass errorClass;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Error {
|
class Error {
|
||||||
using Error_t =
|
using Error_t = std::variant< ConfigurationError,
|
||||||
std::variant< CoreHandlerError, ConnectionError, WerificationError, MethodError, RublonAuthenticationInterrupt >;
|
CoreHandlerError,
|
||||||
|
ConnectionError,
|
||||||
|
WerificationError,
|
||||||
|
MethodError,
|
||||||
|
RublonAuthenticationInterrupt,
|
||||||
|
RublonCheckApplicationException >;
|
||||||
Error_t _error;
|
Error_t _error;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Category { k_CoreHandlerError, k_ConnectionError, k_WerificationError, k_MethodError, k_RublonAuthenticationInterrupt };
|
enum Category {
|
||||||
|
k_ConfigurationError,
|
||||||
|
k_CoreHandlerError,
|
||||||
|
k_ConnectionError,
|
||||||
|
k_WerificationError,
|
||||||
|
k_MethodError,
|
||||||
|
k_RublonAuthenticationInterrupt,
|
||||||
|
k_RublonCheckApplication
|
||||||
|
};
|
||||||
|
|
||||||
Error() = default;
|
Error() = default;
|
||||||
|
|
||||||
|
Error(ConfigurationError error) : _error{error} {}
|
||||||
Error(CoreHandlerError error) : _error{error} {}
|
Error(CoreHandlerError error) : _error{error} {}
|
||||||
Error(ConnectionError error) : _error{error} {}
|
Error(ConnectionError error) : _error{error} {}
|
||||||
Error(MethodError error) : _error{error} {}
|
Error(MethodError error) : _error{error} {}
|
||||||
Error(WerificationError error) : _error{error} {}
|
Error(WerificationError error) : _error{error} {}
|
||||||
Error(RublonAuthenticationInterrupt error) : _error{error} {}
|
Error(RublonAuthenticationInterrupt error) : _error{error} {}
|
||||||
|
Error(RublonCheckApplicationException error) : _error{error} {}
|
||||||
|
|
||||||
Error(const Error &) = default;
|
Error(const Error &) = default;
|
||||||
Error(Error &&) = default;
|
Error(Error &&) = default;
|
||||||
@ -147,6 +233,11 @@ class Error {
|
|||||||
return category() == Error{E{}}.category();
|
return category() == Error{E{}}.category();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template < typename E >
|
||||||
|
constexpr bool is(typename E::ErrorClass errorClass) const {
|
||||||
|
return is< E >() && hasSameErrorClassAs(errorClass);
|
||||||
|
}
|
||||||
|
|
||||||
template < typename E >
|
template < typename E >
|
||||||
constexpr bool isSameCategoryAs(const E & e) const {
|
constexpr bool isSameCategoryAs(const E & e) const {
|
||||||
return category() == Error{e}.category();
|
return category() == Error{e}.category();
|
||||||
|
|||||||
44
PAM/ssh/include/rublon/finish.hpp
Executable file
44
PAM/ssh/include/rublon/finish.hpp
Executable file
@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <rublon/json.hpp>
|
||||||
|
#include <rublon/pam.hpp>
|
||||||
|
|
||||||
|
#include <rublon/authentication_step_interface.hpp>
|
||||||
|
#include <rublon/configuration.hpp>
|
||||||
|
#include <rublon/method/method_select.hpp>
|
||||||
|
#include <sys/utsname.h>
|
||||||
|
|
||||||
|
namespace rublon {
|
||||||
|
class Finish : public AuthenticationStep {
|
||||||
|
const char * apiPath = "/api/transaction/credentials";
|
||||||
|
const std::string _accessToken;
|
||||||
|
|
||||||
|
void addAccessToken(Document & coreRequest) const {
|
||||||
|
auto & alloc = coreRequest.GetAllocator();
|
||||||
|
coreRequest.AddMember("accessToken", Value{_accessToken.c_str(), alloc}, alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
tl::expected< bool, Error > returnOk(const Document & /*coreResponse*/) const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
const char * name = "Finalization";
|
||||||
|
|
||||||
|
Finish(const rublon::Configuration & config, std::string accessToken) : AuthenticationStep(config.systemToken.data(), ""), _accessToken{std::move(accessToken)} {}
|
||||||
|
|
||||||
|
template < typename Hander_t >
|
||||||
|
tl::expected< bool, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const {
|
||||||
|
const auto returnOk = [&](const auto & coreResponse) { return this->returnOk(coreResponse); };
|
||||||
|
|
||||||
|
RapidJSONPMRStackAlloc< 2048 > alloc{};
|
||||||
|
Document body{rapidjson::kObjectType, &alloc};
|
||||||
|
|
||||||
|
this->addSystemToken(body);
|
||||||
|
this->addAccessToken(body);
|
||||||
|
|
||||||
|
return coreHandler
|
||||||
|
.request(alloc, apiPath, body) //
|
||||||
|
.and_then(returnOk);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace rublon
|
||||||
92
PAM/ssh/include/rublon/init.hpp
Normal file → Executable file
92
PAM/ssh/include/rublon/init.hpp
Normal file → Executable file
@ -2,59 +2,37 @@
|
|||||||
|
|
||||||
#include <rublon/json.hpp>
|
#include <rublon/json.hpp>
|
||||||
#include <rublon/pam.hpp>
|
#include <rublon/pam.hpp>
|
||||||
|
#include <rublon/session.hpp>
|
||||||
|
|
||||||
#include <rublon/authentication_step_interface.hpp>
|
#include <rublon/authentication_step_interface.hpp>
|
||||||
#include <rublon/configuration.hpp>
|
#include <rublon/configuration.hpp>
|
||||||
#include <rublon/method/method_select.hpp>
|
#include <rublon/method/method_select.hpp>
|
||||||
|
#include <string>
|
||||||
#include <sys/utsname.h>
|
#include <sys/utsname.h>
|
||||||
|
|
||||||
namespace rublon {
|
namespace rublon {
|
||||||
class Verify {};
|
/// TODO move to some other place
|
||||||
|
using PAM = LinuxPam;
|
||||||
|
using Session = SessionBase< PAM, CoreHandler< CURL > >;
|
||||||
|
using Transaction = TransactionBase< Session >;
|
||||||
|
|
||||||
} // namespace rublon
|
} // namespace rublon
|
||||||
|
|
||||||
|
extern std::string g_tid;
|
||||||
|
|
||||||
namespace rublon {
|
namespace rublon {
|
||||||
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"){
|
|
||||||
return {_value, mr};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {"unknown", mr};
|
|
||||||
}
|
|
||||||
|
|
||||||
template < class MethodSelect_t = MethodSelect >
|
template < class MethodSelect_t = MethodSelect >
|
||||||
class Init : public AuthenticationStep< Init< MethodSelect_t > > {
|
class Init : public AuthenticationStep {
|
||||||
using base_t = AuthenticationStep< Init< MethodSelect_t > >;
|
using base_t = AuthenticationStep;
|
||||||
|
|
||||||
const char * apiPath = "/api/transaction/init";
|
const char * apiPath = "/api/transaction/init";
|
||||||
|
|
||||||
tl::expected< MethodSelect_t, Error > createMethod(const Document & coreResponse) const {
|
tl::expected< MethodSelect_t, Error > createMethod(const Document & coreResponse) const {
|
||||||
const auto & rublonResponse = coreResponse["result"];
|
const auto & rublonResponse = coreResponse["result"];
|
||||||
std::string tid = rublonResponse["tid"].GetString();
|
std::string tid = rublonResponse["tid"].GetString();
|
||||||
return MethodSelect_t{this->_systemToken, tid, rublonResponse["methods"]};
|
g_tid = tid;
|
||||||
|
return MethodSelect_t{this->_systemToken, tid, rublonResponse["methods"], _session.config().prompt};
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename PamInfo_t >
|
template < typename PamInfo_t >
|
||||||
@ -69,7 +47,7 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > {
|
|||||||
std::pmr::string releaseInfo{&stackResource};
|
std::pmr::string releaseInfo{&stackResource};
|
||||||
auto & alloc = coreRequest.GetAllocator();
|
auto & alloc = coreRequest.GetAllocator();
|
||||||
|
|
||||||
const auto os = osName(&stackResource);
|
const auto os = details::osName(&stackResource);
|
||||||
|
|
||||||
if(os == "unknown") {
|
if(os == "unknown") {
|
||||||
log(LogLevel::Warning, "No OS information available");
|
log(LogLevel::Warning, "No OS information available");
|
||||||
@ -80,30 +58,26 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > {
|
|||||||
|
|
||||||
Value params{rapidjson::kObjectType};
|
Value params{rapidjson::kObjectType};
|
||||||
params.AddMember("userIP", ip, alloc);
|
params.AddMember("userIP", ip, alloc);
|
||||||
params.AddMember("appVer", "v.0.0.1", alloc); /// TODO add version to cmake
|
params.AddMember("appVer", "2.0.2", alloc); /// TODO add version to cmake
|
||||||
params.AddMember("os", osNamePretty, alloc);
|
params.AddMember("os", osNamePretty, alloc);
|
||||||
|
|
||||||
coreRequest.AddMember("params", std::move(params), alloc);
|
coreRequest.AddMember("params", std::move(params), alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename PamInfo_t >
|
template<typename Pam_T>
|
||||||
tl::expected< std::reference_wrapper< const Document >, Error > checkEnrolement(const Document & coreResponse,
|
tl::expected< std::reference_wrapper< const Document >, Error > checkEnrolement(const Document & coreResponse, const Pam_T pam) const {
|
||||||
const PamInfo_t & pam) const {
|
|
||||||
using namespace std::string_view_literals;
|
using namespace std::string_view_literals;
|
||||||
const auto & resp = coreResponse;
|
const auto & resp = coreResponse;
|
||||||
|
|
||||||
if(resp.HasMember("result") and resp["result"].IsObject() and resp["result"].HasMember("status")) {
|
if(resp.HasMember("result") and resp["result"].IsObject() and resp["result"].HasMember("status")) {
|
||||||
const auto & status = resp["result"]["status"].GetString();
|
const auto & status = resp["result"]["status"].GetString();
|
||||||
log(LogLevel::Warning, "Got enrolement message with stats %s", status);
|
log(LogLevel::Warning, "Got enrolement message with stats %s", status);
|
||||||
if(status == "pending"sv and resp["result"].HasMember("webURI")) {
|
if((status == "pending"sv || status == "waiting"sv) and resp["result"].HasMember("webURI")) {
|
||||||
const auto & weburi = resp["result"]["webURI"].GetString();
|
const auto & weburi = resp["result"]["webURI"].GetString();
|
||||||
pam.print("It seams that your account is not configured properly,\nplease contact your administrator for more information");
|
pam.print("Visit %s", weburi);
|
||||||
pam.print("also, please visit %s", weburi);
|
|
||||||
|
|
||||||
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserPending}}};
|
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserPending}}};
|
||||||
}
|
}
|
||||||
if(status == "denied"sv) {
|
if(status == "denied"sv) {
|
||||||
pam.print("It seams that your account is disabled, please contact your administrator for more information");
|
|
||||||
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserDenied}}};
|
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserDenied}}};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,12 +88,17 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > {
|
|||||||
public:
|
public:
|
||||||
const char * name = "Initialization";
|
const char * name = "Initialization";
|
||||||
|
|
||||||
Init(const rublon::Configuration & config) : base_t(config.systemToken.data(), "") {}
|
const Session & _session;
|
||||||
|
|
||||||
|
Init(const Session & session) : base_t(std::string{session.config().systemToken.data(), 32}, ""), _session{session} {
|
||||||
|
log(LogLevel::Debug, "Init");
|
||||||
|
}
|
||||||
|
|
||||||
template < typename Hander_t, typename PamInfo_t = LinuxPam >
|
template < typename Hander_t, typename PamInfo_t = LinuxPam >
|
||||||
tl::expected< MethodSelect_t, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const {
|
[[deprecated]] tl::expected< MethodSelect_t, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler,
|
||||||
|
const PamInfo_t & pam) const {
|
||||||
const auto createMethod = [&](const auto & coreResponse) { return this->createMethod(coreResponse); };
|
const auto createMethod = [&](const auto & coreResponse) { return this->createMethod(coreResponse); };
|
||||||
const auto checkEnrolement = [&](const auto & coreResponse) { return this->checkEnrolement(coreResponse, pam); };
|
const auto checkEnrolement = [&](const auto & coreResponse) { return this->checkEnrolement(coreResponse,pam); };
|
||||||
|
|
||||||
RapidJSONPMRStackAlloc< 2048 > alloc{};
|
RapidJSONPMRStackAlloc< 2048 > alloc{};
|
||||||
Document body{rapidjson::kObjectType, &alloc};
|
Document body{rapidjson::kObjectType, &alloc};
|
||||||
@ -133,5 +112,22 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > {
|
|||||||
.and_then(checkEnrolement)
|
.and_then(checkEnrolement)
|
||||||
.and_then(createMethod);
|
.and_then(createMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tl::expected< Transaction, Error > openTransaction() const {
|
||||||
|
// const auto createTransaction = [&](const auto & coreResponse) { return this->createTransaction(coreResponse); };
|
||||||
|
// const auto checkEnrolement = [&](const auto & coreResponse) { return this->checkEnrolement(coreResponse, _session.pam()); };
|
||||||
|
|
||||||
|
// RapidJSONPMRStackAlloc< 2048 > alloc{};
|
||||||
|
// Document body{rapidjson::kObjectType, &alloc};
|
||||||
|
|
||||||
|
// this->addSystemToken(body);
|
||||||
|
// this->addPamInfo(body, _session.pam());
|
||||||
|
// this->addParams(body, _session.pam());
|
||||||
|
|
||||||
|
// return _session.coreHandler()
|
||||||
|
// .request(alloc, apiPath, body) //
|
||||||
|
// .and_then(checkEnrolement)
|
||||||
|
// .and_then(createTransaction);
|
||||||
|
// }
|
||||||
};
|
};
|
||||||
} // namespace rublon
|
} // namespace rublon
|
||||||
|
|||||||
50
PAM/ssh/include/rublon/json.hpp
Normal file → Executable file
50
PAM/ssh/include/rublon/json.hpp
Normal file → Executable file
@ -3,6 +3,8 @@
|
|||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <rapidjson/pointer.h>
|
#include <rapidjson/pointer.h>
|
||||||
#include <rapidjson/writer.h>
|
#include <rapidjson/writer.h>
|
||||||
|
#include <rapidjson/prettywriter.h>
|
||||||
|
#include <rapidjson/istreamwrapper.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory_resource>
|
#include <memory_resource>
|
||||||
@ -77,7 +79,7 @@ struct RapidJSONPMRAlloc {
|
|||||||
template < std::size_t N >
|
template < std::size_t N >
|
||||||
struct RapidJSONPMRStackAlloc : public RapidJSONPMRAlloc {
|
struct RapidJSONPMRStackAlloc : public RapidJSONPMRAlloc {
|
||||||
private:
|
private:
|
||||||
char _buffer[N];
|
char _buffer[N]{};
|
||||||
std::pmr::monotonic_buffer_resource mr{_buffer, N};
|
std::pmr::monotonic_buffer_resource mr{_buffer, N};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -95,4 +97,50 @@ using Writer = rapidjson::Writer< StringBuffer, rapidjson::UTF8<>, rapidjs
|
|||||||
|
|
||||||
using JSONPointer = rapidjson::GenericPointer< Value, RapidJSONPMRAlloc >;
|
using JSONPointer = rapidjson::GenericPointer< Value, RapidJSONPMRAlloc >;
|
||||||
|
|
||||||
|
template < typename Str_t >
|
||||||
|
struct StringWriter {
|
||||||
|
Str_t & _str;
|
||||||
|
using Ch = char;
|
||||||
|
StringWriter(Str_t & t) : _str{t} {}
|
||||||
|
void Put(Ch ch) {
|
||||||
|
_str += ch;
|
||||||
|
}
|
||||||
|
void Flush() {}
|
||||||
|
};
|
||||||
|
struct FileWriter {
|
||||||
|
std::unique_ptr< FILE, int (*)(FILE *) > _fp{nullptr,nullptr};
|
||||||
|
using Ch = char;
|
||||||
|
FileWriter(std::string_view filename) {
|
||||||
|
_fp = std::unique_ptr< FILE, int (*)(FILE *) >(fopen(filename.data(), "w"), fclose);
|
||||||
|
}
|
||||||
|
void Put(Ch ch) {
|
||||||
|
fputc(ch, _fp.get());
|
||||||
|
}
|
||||||
|
void Flush() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template < typename T >
|
||||||
|
static void stringifyTo(const Document & body, T & to) {
|
||||||
|
memory::Monotonic_1k_HeapResource tmpResource;
|
||||||
|
RapidJSONPMRAlloc alloc{&tmpResource};
|
||||||
|
StringWriter<T> s{to};
|
||||||
|
rapidjson::Writer< StringWriter<T>,rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc > writer{s, &alloc};
|
||||||
|
body.Accept(writer);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace rublon
|
} // namespace rublon
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
inline auto begin(const rublon::Value & __ils) noexcept {
|
||||||
|
return __ils.Begin();
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace std
|
||||||
|
|||||||
6
PAM/ssh/include/rublon/memory.hpp
Normal file → Executable file
6
PAM/ssh/include/rublon/memory.hpp
Normal file → Executable file
@ -4,16 +4,16 @@
|
|||||||
|
|
||||||
namespace rublon {
|
namespace rublon {
|
||||||
namespace memory {
|
namespace memory {
|
||||||
struct holder {
|
struct default_memory_resource {
|
||||||
static inline std::pmr::memory_resource * _mr = std::pmr::get_default_resource();
|
static inline std::pmr::memory_resource * _mr = std::pmr::get_default_resource();
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void set_default_resource(std::pmr::memory_resource * memory_resource) {
|
inline void set_default_resource(std::pmr::memory_resource * memory_resource) {
|
||||||
holder{}._mr = memory_resource;
|
default_memory_resource{}._mr = memory_resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::pmr::memory_resource * default_resource() {
|
inline std::pmr::memory_resource * default_resource() {
|
||||||
return holder{}._mr;
|
return default_memory_resource{}._mr;
|
||||||
}
|
}
|
||||||
|
|
||||||
template < std::size_t N >
|
template < std::size_t N >
|
||||||
|
|||||||
19
PAM/ssh/include/rublon/method/EMAIL.hpp
Executable file
19
PAM/ssh/include/rublon/method/EMAIL.hpp
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tl/expected.hpp>
|
||||||
|
|
||||||
|
#include <rublon/authentication_step_interface.hpp>
|
||||||
|
#include <rublon/pam.hpp>
|
||||||
|
#include <rublon/pam_action.hpp>
|
||||||
|
|
||||||
|
#include <rublon/method/websocket_based_auth.hpp>
|
||||||
|
|
||||||
|
namespace rublon::method {
|
||||||
|
|
||||||
|
class EMAIL : public WebsocketBasedAuth {
|
||||||
|
public:
|
||||||
|
EMAIL(std::string systemToken, std::string tid)
|
||||||
|
: WebsocketBasedAuth(std::move(systemToken), std::move(tid), "Email Link") {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rublon::method
|
||||||
12
PAM/ssh/include/rublon/method/OTP.hpp
Normal file → Executable file
12
PAM/ssh/include/rublon/method/OTP.hpp
Normal file → Executable file
@ -12,8 +12,16 @@ namespace rublon::method {
|
|||||||
|
|
||||||
class OTP : public PasscodeBasedAuth {
|
class OTP : public PasscodeBasedAuth {
|
||||||
public:
|
public:
|
||||||
OTP(std::string systemToken, std::string tid)
|
OTP(std::string systemToken, std::string tid, int prompts)
|
||||||
: PasscodeBasedAuth(std::move(systemToken), std::move(tid), "OTP", "Enter code from Rublon Authenticator: ", 6, true) {}
|
: PasscodeBasedAuth(std::move(systemToken),
|
||||||
|
std::move(tid),
|
||||||
|
"",
|
||||||
|
"Mobile Passcode",
|
||||||
|
"Enter the passcode from the Rublon Authenticator mobile app: ",
|
||||||
|
6,
|
||||||
|
true,
|
||||||
|
PasscodeBasedAuth::Endpoint::ConfirmCode,
|
||||||
|
prompts) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rublon::method
|
} // namespace rublon::method
|
||||||
|
|||||||
2
PAM/ssh/include/rublon/method/PUSH.hpp
Normal file → Executable file
2
PAM/ssh/include/rublon/method/PUSH.hpp
Normal file → Executable file
@ -13,7 +13,7 @@ namespace rublon::method {
|
|||||||
class PUSH : public WebsocketBasedAuth {
|
class PUSH : public WebsocketBasedAuth {
|
||||||
public:
|
public:
|
||||||
PUSH(std::string systemToken, std::string tid)
|
PUSH(std::string systemToken, std::string tid)
|
||||||
: WebsocketBasedAuth(std::move(systemToken), std::move(tid), "PUSH") {}
|
: WebsocketBasedAuth(std::move(systemToken), std::move(tid), "Mobile PUSH") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rublon::method
|
} // namespace rublon::method
|
||||||
|
|||||||
11
PAM/ssh/include/rublon/method/SMS.hpp
Normal file → Executable file
11
PAM/ssh/include/rublon/method/SMS.hpp
Normal file → Executable file
@ -12,7 +12,16 @@ namespace rublon::method {
|
|||||||
|
|
||||||
class SMS : public PasscodeBasedAuth {
|
class SMS : public PasscodeBasedAuth {
|
||||||
public:
|
public:
|
||||||
SMS(std::string systemToken, std::string tid) : PasscodeBasedAuth(std::move(systemToken), std::move(tid), "SMS", "Enter SMS passcode: ", 6, true) {}
|
SMS(std::string systemToken, std::string tid, int prompts)
|
||||||
|
: PasscodeBasedAuth(std::move(systemToken),
|
||||||
|
std::move(tid),
|
||||||
|
"",
|
||||||
|
"SMS",
|
||||||
|
"Enter SMS passcode: ",
|
||||||
|
6,
|
||||||
|
true,
|
||||||
|
PasscodeBasedAuth::Endpoint::ConfirmCode,
|
||||||
|
prompts) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rublon::method
|
} // namespace rublon::method
|
||||||
|
|||||||
2
PAM/ssh/include/rublon/method/SmsLink.hpp
Normal file → Executable file
2
PAM/ssh/include/rublon/method/SmsLink.hpp
Normal file → Executable file
@ -13,7 +13,7 @@ namespace rublon::method {
|
|||||||
class SmsLink : public WebsocketBasedAuth {
|
class SmsLink : public WebsocketBasedAuth {
|
||||||
public:
|
public:
|
||||||
SmsLink(std::string systemToken, std::string tid)
|
SmsLink(std::string systemToken, std::string tid)
|
||||||
: WebsocketBasedAuth(std::move(systemToken), std::move(tid), "smsLink") {}
|
: WebsocketBasedAuth(std::move(systemToken), std::move(tid), "SMS Link") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rublon::method
|
} // namespace rublon::method
|
||||||
|
|||||||
11
PAM/ssh/include/rublon/method/YOTP.hpp
Normal file → Executable file
11
PAM/ssh/include/rublon/method/YOTP.hpp
Normal file → Executable file
@ -12,8 +12,15 @@ namespace rublon::method {
|
|||||||
|
|
||||||
class YOTP : public PasscodeBasedAuth {
|
class YOTP : public PasscodeBasedAuth {
|
||||||
public:
|
public:
|
||||||
YOTP(std::string systemToken, std::string tid)
|
YOTP(std::string systemToken, std::string tid, std::string accessToken, int prompts)
|
||||||
: PasscodeBasedAuth(std::move(systemToken), std::move(tid), "YOTP", "Press Yubikey: ", 44, false) {}
|
: PasscodeBasedAuth(std::move(systemToken),
|
||||||
|
std::move(tid),
|
||||||
|
std::move(accessToken),
|
||||||
|
"YubiKey OTP Security Key",
|
||||||
|
"Insert and tap your YubiKey: ",
|
||||||
|
44,
|
||||||
|
false,
|
||||||
|
PasscodeBasedAuth::Endpoint::SecurityKeySSH, prompts) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rublon::method
|
} // namespace rublon::method
|
||||||
|
|||||||
178
PAM/ssh/include/rublon/method/method_select.hpp
Normal file → Executable file
178
PAM/ssh/include/rublon/method/method_select.hpp
Normal file → Executable file
@ -9,39 +9,19 @@
|
|||||||
#include <rublon/pam.hpp>
|
#include <rublon/pam.hpp>
|
||||||
#include <rublon/pam_action.hpp>
|
#include <rublon/pam_action.hpp>
|
||||||
|
|
||||||
#include <rublon/method/OTP.hpp>
|
#include <rublon/method/EMAIL.hpp>
|
||||||
#include <rublon/method/PUSH.hpp>
|
#include <rublon/method/PUSH.hpp>
|
||||||
#include <rublon/method/SMS.hpp>
|
|
||||||
#include <rublon/method/SmsLink.hpp>
|
#include <rublon/method/SmsLink.hpp>
|
||||||
|
|
||||||
|
#include <rublon/method/OTP.hpp>
|
||||||
|
#include <rublon/method/SMS.hpp>
|
||||||
#include <rublon/method/YOTP.hpp>
|
#include <rublon/method/YOTP.hpp>
|
||||||
|
|
||||||
template < class F >
|
extern std::string g_tid;
|
||||||
struct return_type;
|
|
||||||
|
|
||||||
template < class R, class... A >
|
|
||||||
struct return_type< R (*)(A...) > {
|
|
||||||
typedef R type;
|
|
||||||
};
|
|
||||||
|
|
||||||
template < typename T >
|
|
||||||
using return_type_t = typename return_type< T >::type;
|
|
||||||
|
|
||||||
namespace std {
|
|
||||||
inline ::rublon::Value::ConstValueIterator begin(const rublon::Value & __ils) noexcept {
|
|
||||||
return __ils.Begin();
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace std
|
|
||||||
|
|
||||||
namespace rublon {
|
namespace rublon {
|
||||||
|
|
||||||
|
|
||||||
class MethodProxy {
|
class MethodProxy {
|
||||||
public:
|
public:
|
||||||
template < typename Method_t >
|
template < typename Method_t >
|
||||||
@ -49,41 +29,43 @@ class MethodProxy {
|
|||||||
|
|
||||||
template < typename Handler_t, typename PamInfo_t = LinuxPam >
|
template < typename Handler_t, typename PamInfo_t = LinuxPam >
|
||||||
tl::expected< AuthenticationStatus, Error > fire(const CoreHandlerInterface< Handler_t > & coreHandler, const PamInfo_t & pam) const {
|
tl::expected< AuthenticationStatus, Error > fire(const CoreHandlerInterface< Handler_t > & coreHandler, const PamInfo_t & pam) const {
|
||||||
|
coreHandler.createWSConnection(g_tid);
|
||||||
return std::visit(
|
return std::visit(
|
||||||
[&](const auto & method) {
|
[&](const auto & method) {
|
||||||
rublon::log(LogLevel::Info, "Using '%s' method", method.name);
|
rublon::log(LogLevel::Info, "Using '%s' method", method.name);
|
||||||
return method.fire(coreHandler, pam);
|
return method.verify(coreHandler, pam);
|
||||||
},
|
},
|
||||||
_impl);
|
_impl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::variant< method::OTP, method::SMS, method::PUSH, method::SmsLink, method::YOTP > _impl;
|
std::variant< method::OTP, method::SMS, method::PUSH, method::EMAIL, method::SmsLink, method::YOTP > _impl;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PostMethod : public rublon::AuthenticationStep< PostMethod > {
|
class PostMethod : public AuthenticationStep {
|
||||||
using base_t = rublon::AuthenticationStep< PostMethod >;
|
using base_t = AuthenticationStep;
|
||||||
|
|
||||||
const char * uri = "/api/transaction/methodSSH";
|
const char * uri = "/api/transaction/methodSSH";
|
||||||
std::string _method;
|
std::string _method;
|
||||||
|
int _prompts;
|
||||||
|
|
||||||
tl::expected< MethodProxy, Error > createMethod(const Document & coreResponse) const {
|
tl::expected< MethodProxy, Error > createMethod(const Document & coreResponse) const {
|
||||||
const auto & rublonResponse = coreResponse["result"];
|
const auto & rublonResponse = coreResponse["result"];
|
||||||
std::string tid = rublonResponse["tid"].GetString();
|
std::string tid = rublonResponse["tid"].GetString();
|
||||||
|
std::string token = rublonResponse.HasMember("token") ? rublonResponse["token"].GetString() : "";
|
||||||
|
|
||||||
if(_method == "totp") {
|
if(_method == "totp") {
|
||||||
return MethodProxy{method::OTP{this->_systemToken, std::move(tid)}};
|
return MethodProxy{method::OTP{this->_systemToken, std::move(tid), _prompts}};
|
||||||
} else if(_method == "sms") {
|
} else if(_method == "sms") {
|
||||||
return MethodProxy{method::SMS{this->_systemToken, std::move(tid)}};
|
return MethodProxy{method::SMS{this->_systemToken, std::move(tid), _prompts}};
|
||||||
} else if(_method == "push"){
|
} else if(_method == "push") {
|
||||||
|
return MethodProxy{method::PUSH{this->_systemToken, std::move(tid)}};
|
||||||
|
} else if(_method == "email") {
|
||||||
return MethodProxy{method::PUSH{this->_systemToken, std::move(tid)}};
|
return MethodProxy{method::PUSH{this->_systemToken, std::move(tid)}};
|
||||||
} else if(_method == "smsLink") {
|
} else if(_method == "smsLink") {
|
||||||
return MethodProxy{method::SmsLink{this->_systemToken, std::move(tid)}};
|
return MethodProxy{method::SmsLink{this->_systemToken, std::move(tid)}};
|
||||||
} else if(_method == "yotp") {
|
} else if(_method == "yotp") {
|
||||||
return MethodProxy{method::YOTP{this->_systemToken, std::move(tid)}};
|
return MethodProxy{method::YOTP{this->_systemToken, std::move(tid), std::move(token), _prompts}};
|
||||||
}
|
} else
|
||||||
|
|
||||||
else
|
|
||||||
return tl::unexpected{MethodError{MethodError::BadMethod}};
|
return tl::unexpected{MethodError{MethodError::BadMethod}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,8 +77,8 @@ class PostMethod : public rublon::AuthenticationStep< PostMethod > {
|
|||||||
public:
|
public:
|
||||||
const char * name = "Confirm Method";
|
const char * name = "Confirm Method";
|
||||||
|
|
||||||
PostMethod(std::string systemToken, std::string tid, std::string method)
|
PostMethod(std::string systemToken, std::string tid, std::string method, int prompts)
|
||||||
: base_t(std::move(systemToken), std::move(tid)), _method{method} {}
|
: base_t(std::move(systemToken), std::move(tid)), _method{method}, _prompts{prompts} {}
|
||||||
|
|
||||||
template < typename Hander_t, typename PamInfo_t = LinuxPam >
|
template < typename Hander_t, typename PamInfo_t = LinuxPam >
|
||||||
tl::expected< MethodProxy, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const {
|
tl::expected< MethodProxy, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const {
|
||||||
@ -117,14 +99,16 @@ class PostMethod : public rublon::AuthenticationStep< PostMethod > {
|
|||||||
|
|
||||||
class MethodSelect {
|
class MethodSelect {
|
||||||
std::string _systemToken;
|
std::string _systemToken;
|
||||||
|
std::string _accessToken;
|
||||||
std::string _tid;
|
std::string _tid;
|
||||||
|
int _prompts;
|
||||||
|
|
||||||
std::vector< std::string > _methods;
|
std::vector< std::string > _methods;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template < typename Array_t >
|
template < typename Array_t >
|
||||||
MethodSelect(std::string systemToken, std::string tid, const Array_t & methodsAvailableForUser)
|
MethodSelect(std::string systemToken, std::string tid, const Array_t & methodsAvailableForUser, int prompts)
|
||||||
: _systemToken{std::move(systemToken)}, _tid{std::move(tid)} {
|
: _systemToken{std::move(systemToken)}, _tid{std::move(tid)}, _prompts{prompts} {
|
||||||
_methods.reserve(std::size(methodsAvailableForUser));
|
_methods.reserve(std::size(methodsAvailableForUser));
|
||||||
std::transform(
|
std::transform(
|
||||||
std::begin(methodsAvailableForUser), std::end(methodsAvailableForUser), std::back_inserter(_methods), [](const auto & method) {
|
std::begin(methodsAvailableForUser), std::end(methodsAvailableForUser), std::back_inserter(_methods), [](const auto & method) {
|
||||||
@ -136,9 +120,12 @@ class MethodSelect {
|
|||||||
template < typename Pam_t >
|
template < typename Pam_t >
|
||||||
tl::expected< PostMethod, Error > create(Pam_t & pam) const {
|
tl::expected< PostMethod, Error > create(Pam_t & pam) const {
|
||||||
rublon::log(LogLevel::Debug, "prompting user to select method");
|
rublon::log(LogLevel::Debug, "prompting user to select method");
|
||||||
memory::StrictMonotonic_2k_HeapResource memoryResource;
|
memory::StrictMonotonic_4k_HeapResource memoryResource;
|
||||||
std::pmr::map< int, std::string > methods_id{&memoryResource};
|
std::pmr::map< int, std::string > methods_id{&memoryResource};
|
||||||
pam.print("select method: ");
|
std::pmr::map< int, std::string > methods_names{&memoryResource};
|
||||||
|
int prompts = _prompts;
|
||||||
|
|
||||||
|
pam.print("Select the authentication method to verify your identity: ");
|
||||||
|
|
||||||
auto logMethodAvailable = [](auto & method) { //
|
auto logMethodAvailable = [](auto & method) { //
|
||||||
rublon::log(LogLevel::Debug, "Method %s found", method.c_str());
|
rublon::log(LogLevel::Debug, "Method %s found", method.c_str());
|
||||||
@ -152,30 +139,41 @@ class MethodSelect {
|
|||||||
for(const auto & method : _methods) {
|
for(const auto & method : _methods) {
|
||||||
if(method == "totp") {
|
if(method == "totp") {
|
||||||
logMethodAvailable(method);
|
logMethodAvailable(method);
|
||||||
pam.print("%d: Mobile TOTP", i + 1);
|
pam.print("%d: Mobile Passcode", i + 1);
|
||||||
methods_id[++i] = method;
|
methods_id[++i] = method;
|
||||||
|
methods_names[i] = "Mobile Passcode";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Needs changes in Core to work
|
if(method == "email") {
|
||||||
// if(method == "yotp") {
|
logMethodAvailable(method);
|
||||||
// logMethodAvailable(method);
|
pam.print("%d: Email Link", i + 1);
|
||||||
// pam.print("%d: Yubikey", i + 1);
|
methods_id[++i] = method;
|
||||||
// methods_id[++i] = method;
|
methods_names[i] = "Email Link";
|
||||||
// continue;
|
continue;
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
if(method == "yotp") {
|
||||||
|
logMethodAvailable(method);
|
||||||
|
pam.print("%d: YubiKey OTP Security Key", i + 1);
|
||||||
|
methods_id[++i] = method;
|
||||||
|
methods_names[i] = "YubiKey OTP Security Key";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if(method == "sms") {
|
if(method == "sms") {
|
||||||
logMethodAvailable(method);
|
logMethodAvailable(method);
|
||||||
pam.print("%d: SMS code", i + 1);
|
pam.print("%d: SMS Passcode", i + 1);
|
||||||
methods_id[++i] = method;
|
methods_id[++i] = method;
|
||||||
|
methods_names[i] = "SMS Passcode";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(method == "push") {
|
if(method == "push") {
|
||||||
logMethodAvailable(method);
|
logMethodAvailable(method);
|
||||||
pam.print("%d: Mobile PUSH", i + 1);
|
pam.print("%d: Mobile Push", i + 1);
|
||||||
methods_id[++i] = method;
|
methods_id[++i] = method;
|
||||||
|
methods_names[i] = "Mobile Push";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,71 +181,87 @@ class MethodSelect {
|
|||||||
logMethodAvailable(method);
|
logMethodAvailable(method);
|
||||||
pam.print("%d: SMS Link", i + 1);
|
pam.print("%d: SMS Link", i + 1);
|
||||||
methods_id[++i] = method;
|
methods_id[++i] = method;
|
||||||
|
methods_names[i] = "SMS Link";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
logMethodNotAvailable(method);
|
logMethodNotAvailable(method);
|
||||||
}
|
}
|
||||||
if(i == 0) {
|
if(i == 0) {
|
||||||
|
log(LogLevel::Warning, "None of provided methods are supported by the connector");
|
||||||
return tl::unexpected(MethodError(MethodError::ErrorClass::NoMethodAvailable));
|
return tl::unexpected(MethodError(MethodError::ErrorClass::NoMethodAvailable));
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto toMethodError = [&](conv::Error /*methodid*/) -> MethodError {
|
const auto toMethodError = [&](conv::Error /*methodid*/) -> MethodError {
|
||||||
pam.print("Input is not an number, please correct");
|
// NaN or out of range
|
||||||
return MethodError{MethodError::BadUserInput};
|
return MethodError{MethodError::BadUserInput};
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto createMethod = [&](std::uint32_t methodid) -> tl::expected< PostMethod, MethodError > {
|
const auto createMethod = [&](std::uint32_t methodid) -> tl::expected< PostMethod, MethodError > {
|
||||||
auto hasMethod = methods_id.find(methodid) != methods_id.end();
|
auto hasMethod = methods_id.find(methodid) != methods_id.end();
|
||||||
pam.print("\t selected: %s", hasMethod ? methods_id.at(methodid).c_str() : "unknown option");
|
// pam.print("\t selected: %s", hasMethod ? methods_id.at(methodid).c_str() : "unknown option");
|
||||||
if(!hasMethod) {
|
if(!hasMethod) {
|
||||||
|
log(LogLevel::Error, "User selected option %d, which is not corrent", methodid);
|
||||||
return tl::unexpected{MethodError(MethodError::BadMethod)};
|
return tl::unexpected{MethodError(MethodError::BadMethod)};
|
||||||
} else {
|
} else {
|
||||||
log(LogLevel::Info, "User selected option %d{%s}", methodid, methods_id.at(methodid).c_str());
|
log(LogLevel::Info, "User selected option %d{%s}", methodid, methods_names.at(methodid).c_str());
|
||||||
return PostMethod{_systemToken, _tid, methods_id.at(methodid)};
|
return PostMethod{_systemToken, _tid, methods_id.at(methodid), _prompts};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto askForMethodAgain = [&]() -> tl::expected< PostMethod, MethodError > {
|
|
||||||
printAvailableMethods();
|
|
||||||
return pam
|
|
||||||
.scan(conv::to_uint32, "\nSelect method [1-%d]: ", methods_id.size()) //
|
|
||||||
.transform_error(toMethodError)
|
|
||||||
.and_then(createMethod);
|
|
||||||
/// TODO or_else(printErrorAndDenyAccess); ??
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto askForMethod = [&](int methods_number) -> tl::expected< uint32_t, MethodError > {
|
const auto askForMethod = [&](int methods_number) -> tl::expected< uint32_t, MethodError > {
|
||||||
if(methods_number == 1) {
|
if(methods_number == 1) {
|
||||||
pam.print("Only one method available");
|
pam.print("Automatically selected the only available authentication method: %s", methods_id.at(1).c_str());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return pam.scan(conv::to_uint32, "\nSelect method [1-%d]: ", methods_number).transform_error(toMethodError);
|
return pam.scan(conv::to_uint32, "\nSelect method [1-%d]: ", methods_number).transform_error(toMethodError);
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto handleErrors = [&](const MethodError & e) -> tl::expected< PostMethod, MethodError > {
|
auto reducePromptCount = [&](int selected_method) -> tl::expected< uint32_t, MethodError > {
|
||||||
switch(e.errorClass) {
|
prompts--;
|
||||||
case MethodError::BadMethod: // User provided a number but the number if not found
|
return selected_method;
|
||||||
return askForMethodAgain();
|
|
||||||
case MethodError::BadUserInput: // User provided id is invalid (NAN)
|
|
||||||
return askForMethodAgain();
|
|
||||||
case MethodError::NoMethodAvailable:
|
|
||||||
/// TODO print enrolement
|
|
||||||
default:
|
|
||||||
return tl::unexpected(e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto disableAnyNewMethodPrompts = [&]() { prompts = 0; };
|
||||||
|
|
||||||
|
const auto printEnrolementInformation = [&]() {
|
||||||
|
log(LogLevel::Warning, "Enrolement send to admin about new user: %s", pam.username());
|
||||||
|
pam.print("Please check email");
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto handleErrors = [&](const MethodError & e) -> tl::expected< PostMethod, MethodError > {
|
||||||
|
switch(e.errorClass) {
|
||||||
|
case MethodError::NoMethodAvailable:
|
||||||
|
disableAnyNewMethodPrompts();
|
||||||
|
printEnrolementInformation();
|
||||||
|
break;
|
||||||
|
case MethodError::BadMethod: // User provided a number but the number if not found
|
||||||
|
pam.print("Input is not a valid method number. Enter a valid number");
|
||||||
|
break;
|
||||||
|
case MethodError::BadUserInput: // User provided id is invalid (NAN)
|
||||||
|
pam.print("Input is not a number. Enter a valid number");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return tl::unexpected(e);
|
||||||
|
};
|
||||||
|
|
||||||
const auto toGenericError = [](const MethodError & e) -> Error { return Error{e}; };
|
const auto toGenericError = [](const MethodError & e) -> Error { return Error{e}; };
|
||||||
|
|
||||||
return printAvailableMethods() //
|
return [&]() {
|
||||||
|
while(true) {
|
||||||
|
auto method = printAvailableMethods() //
|
||||||
|
.and_then(reducePromptCount)
|
||||||
.and_then(askForMethod)
|
.and_then(askForMethod)
|
||||||
.and_then(createMethod)
|
.and_then(createMethod)
|
||||||
.or_else(handleErrors)
|
.or_else(handleErrors);
|
||||||
|
|
||||||
|
if(not method && prompts > 0)
|
||||||
|
continue;
|
||||||
|
return method;
|
||||||
|
};
|
||||||
|
}()
|
||||||
.map_error(toGenericError);
|
.map_error(toGenericError);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
109
PAM/ssh/include/rublon/method/passcode_based_auth.hpp
Normal file → Executable file
109
PAM/ssh/include/rublon/method/passcode_based_auth.hpp
Normal file → Executable file
@ -1,21 +1,31 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "rublon/utils.hpp"
|
||||||
#include <tl/expected.hpp>
|
#include <tl/expected.hpp>
|
||||||
|
|
||||||
#include <rublon/authentication_step_interface.hpp>
|
#include <rublon/authentication_step_interface.hpp>
|
||||||
#include <rublon/pam.hpp>
|
#include <rublon/pam.hpp>
|
||||||
#include <rublon/pam_action.hpp>
|
#include <rublon/pam_action.hpp>
|
||||||
|
#include <rublon/websockets.hpp>
|
||||||
|
|
||||||
namespace rublon::method {
|
namespace rublon::method {
|
||||||
|
|
||||||
class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > {
|
class PasscodeBasedAuth : public AuthenticationStep {
|
||||||
protected:
|
protected:
|
||||||
using base_t = AuthenticationStep< PasscodeBasedAuth >;
|
const char * uri;
|
||||||
const char * uri = "/api/transaction/confirmCode";
|
const char * confirmField;
|
||||||
|
|
||||||
|
static constexpr const char * confirmCodeEndpoint = "/api/transaction/confirmCode";
|
||||||
|
static constexpr const char * confirmSecuritySSHEndpoint = "/api/transaction/confirmSecurityKeySSH";
|
||||||
|
|
||||||
|
static constexpr const char * fieldVericode = "vericode";
|
||||||
|
static constexpr const char * fieldOtp = "otp";
|
||||||
|
|
||||||
const char * userMessage{nullptr};
|
const char * userMessage{nullptr};
|
||||||
|
|
||||||
const uint_fast8_t length;
|
const uint_fast8_t length;
|
||||||
const bool onlyDigits;
|
const bool onlyDigits;
|
||||||
|
int _prompts;
|
||||||
|
|
||||||
constexpr static bool isdigit(char ch) {
|
constexpr static bool isdigit(char ch) {
|
||||||
return std::isdigit(static_cast< unsigned char >(ch));
|
return std::isdigit(static_cast< unsigned char >(ch));
|
||||||
@ -51,60 +61,101 @@ class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > {
|
|||||||
auto vericode = pam.scan([](const char * userInput) { return std::string{userInput}; }, userMessage);
|
auto vericode = pam.scan([](const char * userInput) { return std::string{userInput}; }, userMessage);
|
||||||
|
|
||||||
if(hasValidLength(vericode) and hasValidCharacters(vericode)) {
|
if(hasValidLength(vericode) and hasValidCharacters(vericode)) {
|
||||||
body.AddMember("vericode", Value{vericode.c_str(), alloc}, alloc);
|
Value confirmFieldValue(confirmField, alloc);
|
||||||
|
body.AddMember(confirmFieldValue, Value{vericode.c_str(), alloc}, alloc);
|
||||||
|
|
||||||
|
if(token.size()) {
|
||||||
|
this->addAccessToken(body, token);
|
||||||
|
}
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
return tl::unexpected{Error{WerificationError{WerificationError::WrongCode}}};
|
return tl::unexpected{Error{WerificationError{WerificationError::BadInput}}};
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename PamInfo_t = LinuxPam >
|
template < typename PamInfo_t = LinuxPam >
|
||||||
tl::expected< std::reference_wrapper< Document >, Error > askForPasscodeAgain(Document & body, const PamInfo_t & pam) const {
|
tl::expected< AuthenticationStatus, Error > checkAuthenticationStatus(const Document & /*coreResponse*/, const PamInfo_t & pam) const {
|
||||||
pam.print("passcode has wrong number of digits or contains illegal characters, please correct");
|
pam.print("The passcode has been validated");
|
||||||
return readPasscode(body, pam);
|
|
||||||
}
|
|
||||||
|
|
||||||
template < typename PamInfo_t = LinuxPam >
|
|
||||||
tl::expected< AuthenticationStatus, Error > checkAuthenticationStatus(const Document & coreResponse, const PamInfo_t & pam) const {
|
|
||||||
RapidJSONPMRStackAlloc< 1024 > alloc;
|
|
||||||
auto error = JSONPointer{"/result/error", &alloc}.Get(coreResponse);
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
pam.print("Wrong code");
|
|
||||||
return tl::unexpected{Error{WerificationError{WerificationError::WrongCode}}};
|
|
||||||
}
|
|
||||||
|
|
||||||
pam.print("Verification code validated");
|
|
||||||
return AuthenticationStatus{AuthenticationStatus::Action::Confirmed};
|
return AuthenticationStatus{AuthenticationStatus::Action::Confirmed};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tl::expected< AuthenticationStatus, Error > waitForCoreConfirmation(
|
||||||
|
std::shared_ptr< WebSocketSingleShotEventListener > eventListener) const {
|
||||||
|
log(LogLevel::Info, "Listening to confirmation event in PasscodeBasedAuth");
|
||||||
|
return eventListener->waitForEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename PamInfo_t = LinuxPam >
|
||||||
|
tl::expected< AuthenticationStatus, Error > errorHandler(Error error, const PamInfo_t & pam, int promptLeft) const {
|
||||||
|
if(promptLeft && error.is< WerificationError >()) {
|
||||||
|
switch(error.get< WerificationError >().errorClass) {
|
||||||
|
case WerificationError::ErrorClass::PasscodeException:
|
||||||
|
pam.print(R"(Incorrect passcode. Try again)");
|
||||||
|
break;
|
||||||
|
case WerificationError::ErrorClass::BadInput:
|
||||||
|
pam.print("The passcode has an incorrect number of digits or contains invalid characters. Try again");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tl::unexpected{error};
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const char * name;
|
const char * name;
|
||||||
|
std::string token;
|
||||||
|
|
||||||
|
enum class Endpoint { ConfirmCode, SecurityKeySSH };
|
||||||
|
|
||||||
PasscodeBasedAuth(std::string systemToken,
|
PasscodeBasedAuth(std::string systemToken,
|
||||||
std::string tid,
|
std::string tid,
|
||||||
|
std::string token,
|
||||||
const char * name,
|
const char * name,
|
||||||
const char * userMessage,
|
const char * userMessage,
|
||||||
|
|
||||||
uint_fast8_t length,
|
uint_fast8_t length,
|
||||||
bool numbersOnly)
|
bool numbersOnly,
|
||||||
: base_t(std::move(systemToken), std::move(tid)), userMessage{userMessage}, length{length}, onlyDigits{numbersOnly}, name{name} {}
|
Endpoint endpoint,
|
||||||
|
int prompts)
|
||||||
|
: AuthenticationStep(std::move(systemToken), std::move(tid)),
|
||||||
|
uri{(endpoint == Endpoint::ConfirmCode) ? confirmCodeEndpoint : confirmSecuritySSHEndpoint},
|
||||||
|
confirmField{(endpoint == Endpoint::ConfirmCode) ? fieldVericode : fieldOtp},
|
||||||
|
userMessage{userMessage},
|
||||||
|
length{length},
|
||||||
|
onlyDigits{numbersOnly},
|
||||||
|
_prompts{prompts},
|
||||||
|
name{name},
|
||||||
|
token{std::move(token)} {}
|
||||||
|
|
||||||
template < typename Hander_t, typename PamInfo_t = LinuxPam >
|
template < typename Hander_t, typename PamInfo_t = LinuxPam >
|
||||||
tl::expected< AuthenticationStatus, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const {
|
tl::expected< AuthenticationStatus, Error > verify(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const {
|
||||||
RapidJSONPMRStackAlloc< 2048 > alloc{};
|
RapidJSONPMRStackAlloc< 2048 > alloc{};
|
||||||
Document body{rapidjson::kObjectType, &alloc};
|
Document body{rapidjson::kObjectType, &alloc};
|
||||||
|
std::shared_ptr< WebSocketSingleShotEventListener > eventListener = coreHandler.listen();
|
||||||
|
int prompts = _prompts;
|
||||||
|
|
||||||
const auto checkCodeValidity = [&](const auto & coreResponse) { return this->checkAuthenticationStatus(coreResponse, pam); };
|
|
||||||
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 askForPasscodeAgain = [&](const auto & /*error*/) { return this->askForPasscodeAgain(body, pam); };
|
const auto checkCodeValidity = [&](const auto & coreResponse) { return this->checkAuthenticationStatus(coreResponse, pam); };
|
||||||
|
const auto waitForCoreToConfirm = [&](const auto &) { return waitForCoreConfirmation(eventListener); };
|
||||||
|
const auto handleError = [&](const auto error) { return errorHandler(error, pam, prompts); };
|
||||||
|
|
||||||
this->addSystemToken(body);
|
this->addSystemToken(body);
|
||||||
this->addTid(body);
|
this->addTid(body);
|
||||||
|
|
||||||
return readPasscode(body, pam) //
|
return [&]() {
|
||||||
.or_else(askForPasscodeAgain)
|
while(true) {
|
||||||
|
prompts--;
|
||||||
|
auto access = readPasscode(body, pam) //
|
||||||
.and_then(requestAuthorization)
|
.and_then(requestAuthorization)
|
||||||
.and_then(checkCodeValidity);
|
.and_then(checkCodeValidity)
|
||||||
|
.and_then(waitForCoreToConfirm)
|
||||||
|
.or_else(handleError);
|
||||||
|
if(not access && prompts) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return access;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
14
PAM/ssh/include/rublon/method/websocket_based_auth.hpp
Normal file → Executable file
14
PAM/ssh/include/rublon/method/websocket_based_auth.hpp
Normal file → Executable file
@ -11,21 +11,19 @@
|
|||||||
|
|
||||||
namespace rublon::method {
|
namespace rublon::method {
|
||||||
|
|
||||||
class WebsocketBasedAuth : public AuthenticationStep< WebsocketBasedAuth > {
|
class WebsocketBasedAuth : public AuthenticationStep {
|
||||||
using base_t = AuthenticationStep< WebsocketBasedAuth >;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const char * name = "";
|
const char * name = "";
|
||||||
|
|
||||||
WebsocketBasedAuth(std::string systemToken, std::string tid, const char * name)
|
WebsocketBasedAuth(std::string systemToken, std::string tid, const char * name)
|
||||||
: base_t(std::move(systemToken), std::move(tid)), name{name} {}
|
: AuthenticationStep(std::move(systemToken), std::move(tid)), name{name} {}
|
||||||
|
|
||||||
template < typename Hander_t, typename PamInfo_t = LinuxPam >
|
template < typename Hander_t, typename PamInfo_t = LinuxPam >
|
||||||
tl::expected< AuthenticationStatus, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const {
|
tl::expected< AuthenticationStatus, Error > verify(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const {
|
||||||
log(LogLevel::Info, "starting WS");
|
log(LogLevel::Info, "starting WS");
|
||||||
|
auto listener = coreHandler.listen();
|
||||||
pam.print("Waiting for approval");
|
pam.scan([](const auto/*ignored userinput*/){return "";}, "Rublon authentication initiated. Complete the authentication and press Enter to proceed");
|
||||||
return coreHandler.waitForConfirmation(_tid);
|
return listener->waitForEvent();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
0
PAM/ssh/include/rublon/non_owning_ptr.hpp
Normal file → Executable file
0
PAM/ssh/include/rublon/non_owning_ptr.hpp
Normal file → Executable file
9
PAM/ssh/include/rublon/pam.hpp
Normal file → Executable file
9
PAM/ssh/include/rublon/pam.hpp
Normal file → Executable file
@ -43,8 +43,13 @@ class LinuxPam {
|
|||||||
|
|
||||||
template < typename... Ti >
|
template < typename... Ti >
|
||||||
void print(const char * fmt, Ti... ti) const noexcept {
|
void print(const char * fmt, Ti... ti) const noexcept {
|
||||||
log(LogLevel::Debug, fmt, std::forward< Ti >(ti)...);
|
char buf[256]={};
|
||||||
pam_prompt(pamh, PAM_TEXT_INFO, nullptr, fmt, std::forward< Ti >(ti)...);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename Fun, typename... Ti >
|
template < typename Fun, typename... Ti >
|
||||||
|
|||||||
16
PAM/ssh/include/rublon/pam_action.hpp
Normal file → Executable file
16
PAM/ssh/include/rublon/pam_action.hpp
Normal file → Executable file
@ -1,27 +1,33 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace rublon {
|
namespace rublon {
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationStatus {
|
class AuthenticationStatus {
|
||||||
public:
|
public:
|
||||||
enum class Action { Denied, Confirmed, Bypass };
|
enum class Action { Denied, Confirmed, Bypass };
|
||||||
|
|
||||||
AuthenticationStatus(Action action) : _action{action} {}
|
AuthenticationStatus(Action action, std::string authenticationToken = "") : _action{action}, _token{std::move(authenticationToken)} {}
|
||||||
|
|
||||||
constexpr bool userAuthorized() const {
|
constexpr bool userAuthorized() const {
|
||||||
return _action == Action::Confirmed;
|
return _action == Action::Confirmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr bool bypass() const {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Action action() const {
|
Action action() const {
|
||||||
return _action;
|
return _action;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string_view accessToken() const {
|
||||||
|
return _token;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Action _action;
|
Action _action;
|
||||||
|
std::string _token;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rublon
|
} // namespace rublon
|
||||||
|
|||||||
66
PAM/ssh/include/rublon/rublon.hpp
Normal file → Executable file
66
PAM/ssh/include/rublon/rublon.hpp
Normal file → Executable file
@ -1,72 +1,34 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <rublon/init.hpp>
|
#include <rublon/session.hpp>
|
||||||
#include <rublon/json.hpp>
|
#include <rublon/json.hpp>
|
||||||
#include <rublon/pam.hpp>
|
#include <rublon/pam.hpp>
|
||||||
#include <rublon/rublon.hpp>
|
|
||||||
#include <rublon/utils.hpp>
|
#include <rublon/utils.hpp>
|
||||||
|
#include <rublon/finish.hpp>
|
||||||
|
|
||||||
namespace rublon {
|
namespace rublon {
|
||||||
|
|
||||||
// template<typename Pam_t>
|
using PAM = LinuxPam;
|
||||||
// static Configuration readConfig(const Pam_t&pam){
|
using Session = SessionBase<PAM, CoreHandler< CURL >>;
|
||||||
// auto rublonConfig = ConfigurationFactory{}.systemConfig();
|
using Transaction = TransactionBase<Session>;
|
||||||
// if(not rublonConfig.has_value()) {
|
|
||||||
// pam.print("\n");
|
|
||||||
// pam.print("Rublon configuration does not exists or is invalid");
|
|
||||||
// pam.print("\tcheck '%s' for more details\n", details::logPath());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
class RublonSession {
|
|
||||||
public:
|
|
||||||
std::pmr::string _tid;
|
|
||||||
|
|
||||||
RublonSession() {
|
|
||||||
/// create memoryResource
|
|
||||||
///
|
|
||||||
}
|
|
||||||
void startTransaction(std::string_view tid) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class RublonExceptionHandler{
|
|
||||||
public:
|
|
||||||
};
|
|
||||||
|
|
||||||
template < typename Pam_T, typename CoreHandler_T >
|
|
||||||
class RublonBase {
|
|
||||||
const Pam_T & _pam;
|
|
||||||
Configuration _config{};
|
|
||||||
|
|
||||||
void initializeLogs() {
|
|
||||||
details::initLog();
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
RublonBase(const Pam_T & pam, Configuration config) : _pam{pam}, _config{config} {}
|
|
||||||
|
|
||||||
AuthenticationStatus authenticate() const {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using RublonLinux = RublonBase< LinuxPam, CoreHandler< CURL > >;
|
|
||||||
|
|
||||||
class RublonFactory{
|
class RublonFactory{
|
||||||
public:
|
public:
|
||||||
template <typename Pam_t>
|
tl::expected<Session, Error> startSession(const PAM &pam){
|
||||||
tl::expected<RublonLinux, bool> create(const Pam_t &pam){
|
details::initLog();
|
||||||
|
|
||||||
auto config = ConfigurationFactory{}.systemConfig();
|
auto config = ConfigurationFactory{}.systemConfig();
|
||||||
|
|
||||||
if(not config.has_value()) {
|
if(not config.has_value()) {
|
||||||
pam.print("\n");
|
pam.print("The configuration file does not exist or contains incorrect values");
|
||||||
pam.print("Rublon configuration does not exists or is invalid");
|
return tl::unexpected{ConfigurationError{}};
|
||||||
pam.print("\tcheck '%s' for more details\n", details::logPath());
|
|
||||||
return tl::unexpected{false};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return RublonLinux{pam, *config};
|
return Session{pam, config.value()};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace rublon
|
} // namespace rublon
|
||||||
|
|
||||||
|
#include <rublon/init.hpp>
|
||||||
|
|||||||
40
PAM/ssh/include/rublon/session.hpp
Normal file
40
PAM/ssh/include/rublon/session.hpp
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "rublon/utils.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <rublon/configuration.hpp>
|
||||||
|
|
||||||
|
namespace rublon{
|
||||||
|
|
||||||
|
template<typename Pam_t, typename CoreHandler_t>
|
||||||
|
class SessionBase {
|
||||||
|
const Pam_t & _pam;
|
||||||
|
const Configuration _config;
|
||||||
|
|
||||||
|
CoreHandler_t _coreHandler;
|
||||||
|
/// TODO log
|
||||||
|
/// TODO momory resource
|
||||||
|
public:
|
||||||
|
SessionBase(const Pam_t &pam, Configuration config) : _pam{pam}, _config{config}, _coreHandler{_config} {
|
||||||
|
log(LogLevel::Debug, __PRETTY_FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto & coreHandler() const {return _coreHandler; }
|
||||||
|
const auto & pam() const { return _pam; }
|
||||||
|
const auto & config() const { return _config; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Session_t>
|
||||||
|
class TransactionBase{
|
||||||
|
public:
|
||||||
|
const Session_t& _session;
|
||||||
|
std::string _tid;
|
||||||
|
|
||||||
|
TransactionBase(const Session_t& session, std::string_view tid):_session{session},_tid{tid.data(), tid.size()} {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
0
PAM/ssh/include/rublon/sign.hpp
Normal file → Executable file
0
PAM/ssh/include/rublon/sign.hpp
Normal file → Executable file
35
PAM/ssh/include/rublon/static_string.hpp
Normal file
35
PAM/ssh/include/rublon/static_string.hpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// statically allocates a string buffer of (N+1) chars
|
||||||
|
template < size_t N >
|
||||||
|
class StaticString {
|
||||||
|
public:
|
||||||
|
constexpr StaticString() = default;
|
||||||
|
constexpr StaticString(const char * str) {
|
||||||
|
std::strncpy(m_str.data(), str, N);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator=(const char * str) {
|
||||||
|
std::strncpy(m_str.data(), str, N);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char * c_str() const noexcept {
|
||||||
|
return &m_str[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const char * data() const noexcept {
|
||||||
|
return &m_str[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::size_t size() const {
|
||||||
|
return strlen(m_str.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array< char, N + 1 > m_str{};
|
||||||
|
};
|
||||||
136
PAM/ssh/include/rublon/utils.hpp
Normal file → Executable file
136
PAM/ssh/include/rublon/utils.hpp
Normal file → Executable file
@ -8,15 +8,14 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <memory_resource>
|
#include <memory_resource>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#include <alloca.h>
|
#include <alloca.h>
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
#include <security/pam_appl.h>
|
#include <security/pam_appl.h>
|
||||||
#include <security/pam_modules.h>
|
#include <security/pam_modules.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@ -27,7 +26,6 @@
|
|||||||
#include <rublon/memory.hpp>
|
#include <rublon/memory.hpp>
|
||||||
|
|
||||||
namespace rublon {
|
namespace rublon {
|
||||||
|
|
||||||
inline bool fileGood(const std::filesystem::path & path) {
|
inline bool fileGood(const std::filesystem::path & path) {
|
||||||
std::ifstream file(path);
|
std::ifstream file(path);
|
||||||
return file.good();
|
return file.good();
|
||||||
@ -47,8 +45,6 @@ inline bool readFile(const std::filesystem::path & path, T & destination) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
enum class LogLevel { Debug, Info, Warning, Error };
|
enum class LogLevel { Debug, Info, Warning, Error };
|
||||||
|
|
||||||
inline auto dateStr() {
|
inline auto dateStr() {
|
||||||
@ -60,37 +56,129 @@ inline auto dateStr() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constexpr const char * LogLevelNames[]{"Debug", "Info", "Warning", "Error"};
|
constexpr const char * LogLevelNames[]{"Debug", "Info", "Warning", "Error"};
|
||||||
constexpr LogLevel g_level = LogLevel::Debug;
|
LogLevel g_level = LogLevel::Debug;
|
||||||
constexpr bool syncLogFile = true;
|
constexpr bool syncLogFile = true;
|
||||||
|
static const char * application = "";
|
||||||
|
|
||||||
|
|
||||||
|
// #include <openssl/md5.h>
|
||||||
|
// #include <sys/types.h>
|
||||||
|
// #include <sys/stat.h>
|
||||||
|
// #include <sys/mman.h>
|
||||||
|
|
||||||
|
// unsigned char result[MD5_DIGEST_LENGTH];
|
||||||
|
|
||||||
|
// // Print the MD5 sum as hex-digits.
|
||||||
|
// void print_md5_sum(unsigned char* md) {
|
||||||
|
// int i;
|
||||||
|
// for(i=0; i <MD5_DIGEST_LENGTH; i++) {
|
||||||
|
// printf("%02x",md[i]);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Get the size of the file by its file descriptor
|
||||||
|
// unsigned long get_size_by_fd(int fd) {
|
||||||
|
// struct stat statbuf;
|
||||||
|
// if(fstat(fd, &statbuf) < 0) exit(-1);
|
||||||
|
// return statbuf.st_size;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// int md5(const char*filename) {
|
||||||
|
// int file_descript;
|
||||||
|
// unsigned long file_size;
|
||||||
|
// char* file_buffer;
|
||||||
|
|
||||||
|
// file_descript = open(filename, O_RDONLY);
|
||||||
|
// if(file_descript < 0) exit(-1);
|
||||||
|
|
||||||
|
// file_size = get_size_by_fd(file_descript);
|
||||||
|
// printf("file size:\t%lu\n", file_size);
|
||||||
|
|
||||||
|
// file_buffer =(char*)mmap(nullptr, file_size, PROT_READ, MAP_SHARED, file_descript, 0);
|
||||||
|
// MD5((unsigned char*) file_buffer, file_size, result);
|
||||||
|
// munmap(file_buffer, file_size);
|
||||||
|
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
|
||||||
namespace details {
|
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 const char * logPath() {
|
||||||
constexpr auto path = "/var/log/rublon-ssh.log";
|
constexpr auto path = "/var/log/rublon-ssh.log";
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool logExists() noexcept {
|
inline void touch(const char * filename) {
|
||||||
return std::unique_ptr< FILE, int (*)(FILE *) >(fopen(logPath(), "r"), fclose).get();
|
close(open(filename, O_CREAT | O_RDWR, 0640));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void initLog() noexcept {
|
inline void mkdir(const char * dirname) {
|
||||||
if(not logExists()) {
|
::mkdir(dirname, O_CREAT | O_RDWR);
|
||||||
auto fp = fopen(logPath(), "w");
|
|
||||||
if(fp) {
|
|
||||||
fclose(fp);
|
|
||||||
chmod(logPath(), S_IRUSR | S_IWUSR | S_IRGRP);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool exists(const char * filename) {
|
||||||
|
return access(filename, F_OK) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool logMissing() noexcept {
|
||||||
|
return not exists(logPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char * initLog(const char * app = nullptr) noexcept {
|
||||||
|
if(logMissing()) {
|
||||||
|
touch(logPath());
|
||||||
|
}
|
||||||
|
if(not app)
|
||||||
|
application = app;
|
||||||
|
return logPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void doLog(LogLevel level, const char * line) noexcept {
|
inline void doLog(LogLevel level, const char * line) noexcept {
|
||||||
auto fp = std::unique_ptr< FILE, int (*)(FILE *) >(fopen(logPath(), "a+"), fclose);
|
auto fp = std::unique_ptr< FILE, int (*)(FILE *) >(fopen(initLog(), "a+"), fclose);
|
||||||
if(fp) {
|
if(fp) {
|
||||||
/// TODO add transaction ID
|
/// TODO add transaction ID
|
||||||
fprintf(fp.get(), "%s [%s] %s\n", dateStr().data(), LogLevelNames[( int ) level], line);
|
fprintf(
|
||||||
|
fp.get(), "%s %s[%s] %s\n", dateStr().data(), application == nullptr ? "" : application, LogLevelNames[( int ) level], line);
|
||||||
if(syncLogFile)
|
if(syncLogFile)
|
||||||
sync();
|
sync();
|
||||||
}
|
}
|
||||||
|
// openlog ("auth", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_AUTH);
|
||||||
|
// syslog (LOG_INFO, "[%s] %s", "pam_rublon", line);
|
||||||
|
// closelog ();
|
||||||
}
|
}
|
||||||
} // namespace details
|
} // namespace details
|
||||||
|
|
||||||
@ -134,16 +222,20 @@ class PrintUser {
|
|||||||
};
|
};
|
||||||
|
|
||||||
namespace conv {
|
namespace conv {
|
||||||
inline bool to_bool(std::string_view value) {
|
enum class Error { OutOfRange, NotANumber };
|
||||||
auto * buf = ( char * ) alloca(value.size() + 1);
|
|
||||||
buf[value.size()] = '\0';
|
inline bool to_bool(std::string_view userinput) {
|
||||||
|
if(userinput.size() > 16) {
|
||||||
|
// todo return optional
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::array< char, 16 >buf{};
|
||||||
auto asciitolower = [](char in) { return in - ((in <= 'Z' && in >= 'A') ? ('Z' - 'z') : 0); };
|
auto asciitolower = [](char in) { return in - ((in <= 'Z' && in >= 'A') ? ('Z' - 'z') : 0); };
|
||||||
|
|
||||||
std::transform(value.cbegin(), value.cend(), buf, asciitolower);
|
std::transform(userinput.cbegin(), userinput.cend(), buf.data(), asciitolower);
|
||||||
return strcmp(buf, "true") == 0;
|
return strcmp(buf.data(), "true") == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Error { OutOfRange, NotANumber };
|
|
||||||
inline tl::expected< std::uint32_t, Error > to_uint32(std::string_view userinput) noexcept {
|
inline tl::expected< std::uint32_t, Error > to_uint32(std::string_view userinput) noexcept {
|
||||||
try {
|
try {
|
||||||
return std::stoi(userinput.data());
|
return std::stoi(userinput.data());
|
||||||
|
|||||||
178
PAM/ssh/include/rublon/websockets.hpp
Normal file → Executable file
178
PAM/ssh/include/rublon/websockets.hpp
Normal file → Executable file
@ -1,19 +1,20 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <rublon/utils.hpp>
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <sio_client.h>
|
#include <rublon/error.hpp>
|
||||||
|
#include <rublon/pam_action.hpp>
|
||||||
|
#include <rublon/utils.hpp>
|
||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <sio_client.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace rublon {
|
namespace rublon {
|
||||||
#define TIMEOUT_SECONDS 90
|
|
||||||
|
|
||||||
/// {"status":"ERROR","code":400,"result":{"exception":"MobileAppMissingException","code":32,"errorMessage":"No mobile app
|
class WebSocketConnectionListener : public std::enable_shared_from_this< WebSocketConnectionListener > {
|
||||||
/// detected","details":null}}
|
|
||||||
|
|
||||||
class WebSocketConnectionListener {
|
|
||||||
public:
|
public:
|
||||||
enum class Status { Disconnected, Conected };
|
enum class Status { Disconnected, Conected };
|
||||||
|
|
||||||
@ -28,26 +29,25 @@ class WebSocketConnectionListener {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
WebSocketConnectionListener(sio::client & ws, std::mutex & lock, std::condition_variable_any & cond)
|
WebSocketConnectionListener(sio::client & ws, std::mutex & lock, std::condition_variable_any & cond)
|
||||||
: _ws(ws), _lock{lock}, _cond{cond} {
|
: _ws(ws), _lock{lock}, _cond{cond} {}
|
||||||
_ws.set_open_listener([this]() { this->on_connected(); });
|
|
||||||
_ws.set_close_listener([this](sio::client::close_reason const & reason) { this->on_close(reason); });
|
void makeCallbacks() {
|
||||||
_ws.set_fail_listener([this]() { this->on_fail(); });
|
auto _this = shared_from_this();
|
||||||
|
_ws.set_open_listener([_this]() { _this->on_connected(); });
|
||||||
|
_ws.set_close_listener([_this](sio::client::close_reason const & reason) { _this->on_close(reason); });
|
||||||
|
_ws.set_fail_listener([_this]() { _this->on_fail(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
tl::expected< Status, bool > waitForConnection() {
|
tl::expected< Status, bool > waitForConnection() {
|
||||||
std::lock_guard lock{_lock};
|
std::lock_guard lock{_lock};
|
||||||
if(!handshake) {
|
|
||||||
log(LogLevel::Info, "Waiting for connection");
|
log(LogLevel::Info, "Waiting for connection");
|
||||||
_cond.wait(_lock);
|
_cond.wait(_lock);
|
||||||
log(LogLevel::Info, "Connection OK!");
|
|
||||||
}
|
|
||||||
|
|
||||||
return _status;
|
return _status;
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_connected() {
|
void on_connected() {
|
||||||
std::unique_lock{_lock};
|
std::unique_lock{_lock};
|
||||||
log(LogLevel::Info, "WebSocket connection estamblished");
|
log(LogLevel::Info, "WebSocket connected");
|
||||||
handshake = true;
|
handshake = true;
|
||||||
_status = Status::Conected;
|
_status = Status::Conected;
|
||||||
_cond.notify_all();
|
_cond.notify_all();
|
||||||
@ -65,16 +65,16 @@ class WebSocketConnectionListener {
|
|||||||
|
|
||||||
void on_fail() {
|
void on_fail() {
|
||||||
std::unique_lock{_lock};
|
std::unique_lock{_lock};
|
||||||
log(LogLevel::Info, "WebSocket connection failed");
|
log(LogLevel::Info, "WebSocket connection fail");
|
||||||
handshake = true;
|
handshake = true;
|
||||||
_status = Status::Disconnected;
|
_status = Status::Disconnected;
|
||||||
_cond.notify_all();
|
_cond.notify_all();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class WebSocketSingleShotEventListener {
|
class WebSocketSingleShotEventListener : public std::enable_shared_from_this< WebSocketSingleShotEventListener > {
|
||||||
public:
|
public:
|
||||||
enum class Status { Approved, Denied, Expired };
|
enum class Status { Unknown, Approved, Denied, Expired };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
sio::client & _ws;
|
sio::client & _ws;
|
||||||
@ -83,128 +83,149 @@ class WebSocketSingleShotEventListener {
|
|||||||
std::condition_variable_any & _cond;
|
std::condition_variable_any & _cond;
|
||||||
|
|
||||||
bool event_received = false;
|
bool event_received = false;
|
||||||
Status _status{};
|
Status _status{Status::Unknown};
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::string _data{};
|
||||||
|
|
||||||
|
private:
|
||||||
void printobj(std::size_t i, const std::map< std::string, sio::message::ptr > & objects) {
|
void printobj(std::size_t i, const std::map< std::string, sio::message::ptr > & objects) {
|
||||||
|
std::size_t o{};
|
||||||
for(const auto & [name, msg] : objects) {
|
for(const auto & [name, msg] : objects) {
|
||||||
log(LogLevel::Debug, "Object %ld: %s", i, name.c_str());
|
log(LogLevel::Debug, "Object %ld: '%s'", i, name.c_str());
|
||||||
printMessage(i, msg);
|
printMessage(i, o++, msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void printMessage(std::size_t i, const sio::message::ptr & message) {
|
void printMessage(std::size_t i, std::size_t o, const sio::message::ptr & message) {
|
||||||
switch(message->get_flag()) {
|
switch(message->get_flag()) {
|
||||||
case sio::message::flag_integer:
|
case sio::message::flag_integer:
|
||||||
log(LogLevel::Debug, "message %ld integer : %d", i, message->get_int());
|
log(LogLevel::Debug, "message %ld/%ld integer : %d", i, o, message->get_int());
|
||||||
break;
|
break;
|
||||||
case sio::message::flag_double:
|
case sio::message::flag_double:
|
||||||
log(LogLevel::Debug, "message %ld double : %f", i, message->get_double());
|
log(LogLevel::Debug, "message %ld/%ld double : %f", i, o, message->get_double());
|
||||||
break;
|
break;
|
||||||
case sio::message::flag_string:
|
case sio::message::flag_string:
|
||||||
log(LogLevel::Debug, "message %ld string : %s", i, message->get_string().c_str());
|
log(LogLevel::Debug, "message %ld/%ld string : %s", i, o, message->get_string().c_str());
|
||||||
break;
|
break;
|
||||||
case sio::message::flag_binary:
|
case sio::message::flag_binary:
|
||||||
log(LogLevel::Debug, "message %ld binary : %s", i, message->get_binary()->c_str());
|
log(LogLevel::Debug, "message %ld/%ld binary : %s", i, o, message->get_binary()->c_str());
|
||||||
break;
|
break;
|
||||||
case sio::message::flag_array:
|
case sio::message::flag_array:
|
||||||
log(LogLevel::Debug, "message %ld array : blah", i);
|
log(LogLevel::Debug, "message %ld/%ld array : blah", i, o);
|
||||||
break;
|
break;
|
||||||
case sio::message::flag_object:
|
case sio::message::flag_object:
|
||||||
log(LogLevel::Debug, "message %ld object : obj ->", i);
|
log(LogLevel::Debug, "message %ld/%ld object : obj ->", i, o);
|
||||||
printobj(i, message->get_map());
|
printobj(i, message->get_map());
|
||||||
break;
|
break;
|
||||||
case sio::message::flag_boolean:
|
case sio::message::flag_boolean:
|
||||||
log(LogLevel::Debug, "message %ld bool : %s", i, message->get_bool() ? "yes" : "no");
|
log(LogLevel::Debug, "message %ld/%ld bool : %s", i, o, message->get_bool() ? "yes" : "no");
|
||||||
break;
|
break;
|
||||||
case sio::message::flag_null:
|
case sio::message::flag_null:
|
||||||
log(LogLevel::Debug, "message %ld NULL", i);
|
log(LogLevel::Debug, "message %ld/%ld NULL", i, o);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
log(LogLevel::Info, "Message with unknown type");
|
log(LogLevel::Info, "Message with unknown type");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void generic(sio::event & e, Status status) {
|
void notify(Status status) {
|
||||||
std::lock_guard lock{_lock};
|
|
||||||
event_received = true;
|
event_received = true;
|
||||||
_status = status;
|
_status = status;
|
||||||
|
|
||||||
log(LogLevel::Debug, "event name : %s", e.get_name().c_str());
|
|
||||||
log(LogLevel::Debug, "event nsp : %s", e.get_nsp().c_str());
|
|
||||||
log(LogLevel::Debug, "event messages: %d", e.get_messages().size());
|
|
||||||
|
|
||||||
for(std::size_t i = 0; i < e.get_messages().size(); i++) {
|
|
||||||
const auto & message = e.get_messages().at(i);
|
|
||||||
printMessage(i, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
_cond.notify_all();
|
_cond.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
void onConfirmed(sio::event & e) {
|
void onConfirmed(sio::event & e) {
|
||||||
generic(e, Status::Approved);
|
std::lock_guard lock{_lock};
|
||||||
log(LogLevel::Info, "Autentication confirmed");
|
log(LogLevel::Info, "Autentication confirmed");
|
||||||
|
_data = e.get_messages().at(1)->get_map()["data"]->get_string();
|
||||||
|
notify(Status::Approved);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDenied(sio::event & e) {
|
void onDenied(sio::event & e) {
|
||||||
generic(e, Status::Denied);
|
std::lock_guard lock{_lock};
|
||||||
log(LogLevel::Info, "Autentication denied by user");
|
log(LogLevel::Info, "Autentication denied by user");
|
||||||
|
// _data = e.get_messages().at(1)->get_map()["redirectUrl"]->get_string();
|
||||||
|
for(std::size_t i{}; i < e.get_messages().size(); i++)
|
||||||
|
printMessage(i, 0, e.get_messages().at(i));
|
||||||
|
notify(Status::Denied);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onExpired(sio::event & e) {
|
void onExpired(sio::event & /*e*/) {
|
||||||
generic(e, Status::Expired);
|
std::lock_guard lock{_lock};
|
||||||
log(LogLevel::Error, "Autentication call expierd");
|
log(LogLevel::Error, "Autentication call expierd");
|
||||||
|
notify(Status::Expired);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onErrorEvent(sio::message::ptr const & message) {
|
void onErrorEvent(sio::message::ptr const & message) {
|
||||||
std::lock_guard lock{_lock};
|
std::lock_guard lock{_lock};
|
||||||
event_received = true;
|
|
||||||
log(LogLevel::Error, "Error: %s", message->get_string().c_str());
|
log(LogLevel::Error, "Error: %s", message->get_string().c_str());
|
||||||
_cond.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
void onAny(sio::event & e) {
|
|
||||||
std::lock_guard lock{_lock};
|
|
||||||
event_received = true;
|
event_received = true;
|
||||||
log(LogLevel::Info, "onAny message received: name %s", e.get_name().c_str());
|
|
||||||
_cond.notify_all();
|
_cond.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WebSocketSingleShotEventListener(sio::client & ws, std::mutex & lock, std::condition_variable_any & cond)
|
WebSocketSingleShotEventListener(sio::client & ws, std::mutex & lock, std::condition_variable_any & cond)
|
||||||
: _ws{ws}, _lock{lock}, _cond{cond} {
|
: _ws{ws}, _lock{lock}, _cond{cond} {}
|
||||||
_ws.socket()->on("transactionConfirmed", [this](sio::event & e) { this->onConfirmed(e); });
|
|
||||||
_ws.socket()->on("transactionDenied", [this](sio::event & e) { this->onDenied(e); });
|
|
||||||
_ws.socket()->on("transactionExpired", [this](sio::event & e) { this->onExpired(e); });
|
|
||||||
|
|
||||||
_ws.socket()->on_error([this](sio::message::ptr const & message) { this->onErrorEvent(message); });
|
void makeCallbacks() {
|
||||||
_ws.socket()->on_any([this](sio::event & e) { this->onAny(e); });
|
auto _this = shared_from_this();
|
||||||
|
_ws.socket()->on("transactionConfirmed", [_this](sio::event & e) { _this->onConfirmed(e); });
|
||||||
|
_ws.socket()->on("transactionDenied", [_this](sio::event & e) { _this->onDenied(e); });
|
||||||
|
_ws.socket()->on("transactionExpired", [_this](sio::event & e) { _this->onExpired(e); });
|
||||||
|
|
||||||
|
_ws.socket()->on_error([_this](sio::message::ptr const & message) { _this->onErrorEvent(message); });
|
||||||
|
// _ws.socket()->on_any([_this](sio::event & e) { _this->onAny(e); });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO pass timeout
|
/// TODO pass timeout
|
||||||
tl::expected< Status, bool > waitForEvent() {
|
tl::expected< AuthenticationStatus, Error > waitForEvent() {
|
||||||
std::lock_guard lock{_lock};
|
std::lock_guard lock{_lock};
|
||||||
if(!event_received) {
|
if(!event_received) {
|
||||||
log(LogLevel::Info, "Waiting for WS event");
|
log(LogLevel::Info, "Waiting for WS event");
|
||||||
if(_cond.wait_until(_lock, std::chrono::system_clock::now() + std::chrono::minutes{2}) == std::cv_status::timeout) {
|
auto timeout = std::chrono::system_clock::now() + std::chrono::minutes{2};
|
||||||
|
while(!event_received) {
|
||||||
|
if(_cond.wait_until(_lock, timeout) == std::cv_status::timeout) {
|
||||||
log(LogLevel::Info, "Waiting for WS event failed due to WS timeout");
|
log(LogLevel::Info, "Waiting for WS event failed due to WS timeout");
|
||||||
}else{
|
break;
|
||||||
|
} else {
|
||||||
|
if(event_received) {
|
||||||
log(LogLevel::Info, "Event arrived");
|
log(LogLevel::Info, "Event arrived");
|
||||||
|
break;
|
||||||
|
} else
|
||||||
|
log(LogLevel::Info, "Event arrived, but it's not an action");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log(LogLevel::Info, "event already received");
|
||||||
|
}
|
||||||
|
|
||||||
return _status;
|
AuthenticationStatus status{
|
||||||
|
_status == Status::Approved ? AuthenticationStatus::Action::Confirmed : AuthenticationStatus::Action::Denied, _data};
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class WebSocket {
|
class WebSocket {
|
||||||
|
|
||||||
public:
|
|
||||||
bool connect(std::string_view uri, std::string_view tid) {
|
|
||||||
std::mutex _lock;
|
std::mutex _lock;
|
||||||
std::condition_variable_any _cond;
|
std::condition_variable_any _cond;
|
||||||
sio::client ws;
|
sio::client ws;
|
||||||
|
|
||||||
|
public:
|
||||||
|
WebSocket() = default;
|
||||||
|
~WebSocket() = default;
|
||||||
|
|
||||||
|
std::shared_ptr< WebSocketSingleShotEventListener > listen() {
|
||||||
|
const auto createEventListener = [&]() -> std::shared_ptr< WebSocketSingleShotEventListener > {
|
||||||
|
log(LogLevel::Debug, "Initializiing WebSocketSingleShotEventListener");
|
||||||
|
auto wsSingleShotEventListener = std::make_shared< WebSocketSingleShotEventListener >(ws, _lock, _cond);
|
||||||
|
wsSingleShotEventListener->makeCallbacks();
|
||||||
|
return wsSingleShotEventListener;
|
||||||
|
};
|
||||||
|
return createEventListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AttachToCore(std::string_view uri, std::string_view tid) {
|
||||||
/// needed here only for rublon core api URL, so 1k fine
|
/// needed here only for rublon core api URL, so 1k fine
|
||||||
memory::StrictMonotonic_1k_HeapResource memoryResource;
|
memory::StrictMonotonic_1k_HeapResource memoryResource;
|
||||||
std::pmr::string str{uri.data(), uri.size(), &memoryResource};
|
std::pmr::string str{uri.data(), uri.size(), &memoryResource};
|
||||||
@ -214,19 +235,19 @@ class WebSocket {
|
|||||||
str.replace(str.find(httpsPrefix), httpsPrefix.size(), "wss://");
|
str.replace(str.find(httpsPrefix), httpsPrefix.size(), "wss://");
|
||||||
}
|
}
|
||||||
str += "/ws/socket.io/";
|
str += "/ws/socket.io/";
|
||||||
// ws.set_logs_verbose();
|
|
||||||
|
|
||||||
log(LogLevel::Debug, "WS connect to %s", str.c_str());
|
// ws.set_logs_verbose();
|
||||||
ws.connect(str.c_str());
|
ws.connect(str.c_str());
|
||||||
|
|
||||||
const auto attachToTransactionConfirmationChannel = [&](const auto & /*status*/) -> tl::expected< bool, bool > {
|
const auto attachToTransactionConfirmationChannel = [&](const auto & /*status*/) -> tl::expected< bool, bool > {
|
||||||
|
log(LogLevel::Debug, "Initializiing attachToTransactionConfirmationChannel");
|
||||||
|
|
||||||
memory::StrictMonotonic_1k_HeapResource resource;
|
memory::StrictMonotonic_1k_HeapResource resource;
|
||||||
std::pmr::string channel{&resource};
|
std::pmr::string channel{&resource};
|
||||||
channel.reserve(100);
|
channel.reserve(100);
|
||||||
channel += "transactionConfirmation.";
|
channel += "transactionConfirmation.";
|
||||||
channel += tid;
|
channel += tid;
|
||||||
|
|
||||||
/// TODO check status
|
|
||||||
auto message = std::dynamic_pointer_cast< sio::object_message >(sio::object_message::create());
|
auto message = std::dynamic_pointer_cast< sio::object_message >(sio::object_message::create());
|
||||||
message->insert("channel", channel.c_str());
|
message->insert("channel", channel.c_str());
|
||||||
log(LogLevel::Debug, "emiting %s message on subscribe channel", channel.c_str());
|
log(LogLevel::Debug, "emiting %s message on subscribe channel", channel.c_str());
|
||||||
@ -235,18 +256,15 @@ class WebSocket {
|
|||||||
return true; /// TODO
|
return true; /// TODO
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto waitForUserAction = [&](const auto & /*connected*/) -> tl::expected< WebSocketSingleShotEventListener::Status, bool > {
|
auto wsConnectionListener = std::make_shared< WebSocketConnectionListener >(ws, _lock, _cond);
|
||||||
WebSocketSingleShotEventListener eventListener{ws, _lock, _cond};
|
wsConnectionListener->makeCallbacks();
|
||||||
return eventListener.waitForEvent();
|
|
||||||
};
|
|
||||||
|
|
||||||
auto v = WebSocketConnectionListener{ws, _lock, _cond}
|
auto v = wsConnectionListener
|
||||||
.waitForConnection() //
|
->waitForConnection() //
|
||||||
.and_then(attachToTransactionConfirmationChannel)
|
.and_then(attachToTransactionConfirmationChannel);
|
||||||
.and_then(waitForUserAction)
|
|
||||||
.value_or(WebSocketSingleShotEventListener::Status::Denied);
|
|
||||||
|
|
||||||
return v == WebSocketSingleShotEventListener::Status::Approved;
|
log(LogLevel::Debug, "WS Connected");
|
||||||
|
return v.value_or(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace rublon
|
} // namespace rublon
|
||||||
|
|||||||
9
PAM/ssh/lib/CMakeLists.txt
Normal file → Executable file
9
PAM/ssh/lib/CMakeLists.txt
Normal file → Executable file
@ -8,20 +8,13 @@ set_target_properties(rublon-ssh-pam PROPERTIES OUTPUT_NAME "pam_rublon")
|
|||||||
|
|
||||||
target_compile_options(rublon-ssh-pam
|
target_compile_options(rublon-ssh-pam
|
||||||
PUBLIC
|
PUBLIC
|
||||||
-fwhole-program
|
|
||||||
-fvisibility=hidden
|
|
||||||
-ffunction-sections
|
|
||||||
-fdata-sections
|
|
||||||
-fno-unwind-tables
|
|
||||||
-fno-asynchronous-unwind-tables
|
|
||||||
-flto
|
-flto
|
||||||
|
-Wno-deprecated-declarations
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_options(rublon-ssh-pam
|
target_link_options(rublon-ssh-pam
|
||||||
PUBLIC
|
PUBLIC
|
||||||
-fpic
|
-fpic
|
||||||
-s
|
|
||||||
-Wl,--gc-sections
|
|
||||||
-flto
|
-flto
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
119
PAM/ssh/lib/pam.cpp
Normal file → Executable file
119
PAM/ssh/lib/pam.cpp
Normal file → Executable file
@ -5,7 +5,11 @@
|
|||||||
#include <security/pam_modules.h>
|
#include <security/pam_modules.h>
|
||||||
#include <syslog.h>
|
#include <syslog.h>
|
||||||
|
|
||||||
|
#include <rublon/check_application.hpp>
|
||||||
|
#include <rublon/error.hpp>
|
||||||
|
#include <rublon/finish.hpp>
|
||||||
#include <rublon/rublon.hpp>
|
#include <rublon/rublon.hpp>
|
||||||
|
#include <rublon/utils.hpp>
|
||||||
|
|
||||||
#define DLL_PUBLIC __attribute__((visibility("default")))
|
#define DLL_PUBLIC __attribute__((visibility("default")))
|
||||||
|
|
||||||
@ -23,43 +27,26 @@ DLL_PUBLIC int pam_sm_acct_mgmt([[maybe_unused]] pam_handle_t * pamh,
|
|||||||
return PAM_SUCCESS;
|
return PAM_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string g_tid;
|
||||||
|
|
||||||
DLL_PUBLIC int
|
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, [[maybe_unused]] int flags, [[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) {
|
||||||
using namespace rublon;
|
using namespace rublon;
|
||||||
// std::freopen(rublon::details::logPath(), "a+", stdout);
|
details::initLog();
|
||||||
LinuxPam pam{pamh};
|
LinuxPam pam{pamh};
|
||||||
// std::freopen(rublon::details::logPath(), "a+", stderr);
|
|
||||||
auto config = ConfigurationFactory{}.systemConfig();
|
|
||||||
if(not config.has_value()) {
|
|
||||||
pam.print("\n");
|
|
||||||
pam.print("Rublon configuration does not exists or is invalid");
|
|
||||||
pam.print("\tcheck '%s' for more details\n", details::logPath());
|
|
||||||
return PAM_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::byte sharedMemory[32 * 1024] = {};
|
|
||||||
std::pmr::monotonic_buffer_resource mr{sharedMemory, std::size(sharedMemory)};
|
|
||||||
std::pmr::unsynchronized_pool_resource rublonPoolResource{&mr};
|
|
||||||
std::pmr::set_default_resource(&rublonPoolResource);
|
|
||||||
|
|
||||||
CoreHandler CH{*config};
|
|
||||||
|
|
||||||
auto selectMethod = [&](const MethodSelect & selector) { return selector.create(pam); };
|
|
||||||
auto confirmMethod = [&](const PostMethod & confirm) { return confirm.fire(CH); };
|
|
||||||
auto confirmCode = [&](const MethodProxy & method) { return method.fire(CH, pam); };
|
|
||||||
|
|
||||||
auto printAuthMessageAndExit = [&](const AuthenticationStatus status) {
|
auto printAuthMessageAndExit = [&](const AuthenticationStatus status) {
|
||||||
switch(status.action()) {
|
switch(status.action()) {
|
||||||
case AuthenticationStatus::Action::Bypass:
|
case AuthenticationStatus::Action::Bypass:
|
||||||
pam.print("\n\tRUBLON authentication bypased");
|
pam.print("RUBLON authentication BYPASSED");
|
||||||
return PAM_SUCCESS;
|
return PAM_SUCCESS;
|
||||||
|
|
||||||
case AuthenticationStatus::Action::Denied:
|
case AuthenticationStatus::Action::Denied:
|
||||||
pam.print("\n\tRUBLON authentication FAILED");
|
pam.print("RUBLON authentication FAILED");
|
||||||
return PAM_MAXTRIES;
|
return PAM_MAXTRIES;
|
||||||
|
|
||||||
case AuthenticationStatus::Action::Confirmed:
|
case AuthenticationStatus::Action::Confirmed:
|
||||||
pam.print("\n\tRUBLON authentication SUCCESS");
|
pam.print("RUBLON authentication SUCCEEDED");
|
||||||
return PAM_SUCCESS;
|
return PAM_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,19 +54,45 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
|
|||||||
return PAM_MAXTRIES;
|
return PAM_MAXTRIES;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto allowLogin = [&](const AuthenticationStatus & status) -> tl::expected< int, Error > {
|
auto session = rublon::RublonFactory{}.startSession(pam);
|
||||||
return printAuthMessageAndExit(status);
|
if(not session.has_value()) {
|
||||||
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto & CH = session.value().coreHandler();
|
||||||
|
|
||||||
|
auto selectMethod = [&](const MethodSelect & selector) { return selector.create(pam); };
|
||||||
|
auto confirmMethod = [&](const PostMethod & postMethod) { return postMethod.handle(CH); };
|
||||||
|
auto confirmCode = [&](const MethodProxy & method) mutable { return method.fire(CH, pam); };
|
||||||
|
|
||||||
|
auto finalizeTransaction = [&](const AuthenticationStatus & status) -> tl::expected< AuthenticationStatus, Error > {
|
||||||
|
if(status.userAuthorized()) {
|
||||||
|
auto tok = std::string{status.accessToken().substr(10, 60).data(), 60};
|
||||||
|
|
||||||
|
Finish finish{session.value().config(), std::move(tok)};
|
||||||
|
finish.handle(CH);
|
||||||
|
}
|
||||||
|
return status;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto allowLogin = [&](const AuthenticationStatus & status) -> tl::expected< int, Error > { return printAuthMessageAndExit(status); };
|
||||||
|
|
||||||
auto mapError = [&](const Error & error) -> tl::expected< int, Error > {
|
auto mapError = [&](const Error & error) -> tl::expected< int, Error > {
|
||||||
log(LogLevel::Error, "Authorization interrupted with {%s::%s}", error.errorClassName(), error.categoryName());
|
log(LogLevel::Error, "Process interrupted by {%s::%s}", error.errorClassName(), error.categoryName());
|
||||||
if(error.is< RublonAuthenticationInterrupt >()) {
|
if(error.is< RublonAuthenticationInterrupt >()) {
|
||||||
switch(error.get< RublonAuthenticationInterrupt >().errorClass) {
|
switch(error.get< RublonAuthenticationInterrupt >().errorClass) {
|
||||||
case RublonAuthenticationInterrupt::ErrorClass::UserBaypass:
|
case RublonAuthenticationInterrupt::ErrorClass::UserBaypass:
|
||||||
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
|
||||||
case RublonAuthenticationInterrupt::ErrorClass::UserDenied:
|
case RublonAuthenticationInterrupt::ErrorClass::UserDenied:
|
||||||
|
pam.print("Access denied! Contact your administrator for more information");
|
||||||
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
|
||||||
|
case RublonAuthenticationInterrupt::ErrorClass::UserWaiting:
|
||||||
case RublonAuthenticationInterrupt::ErrorClass::UserPending:
|
case RublonAuthenticationInterrupt::ErrorClass::UserPending:
|
||||||
|
pam.print(
|
||||||
|
"Your account is awaiting administrator's approval. \n"
|
||||||
|
"Contact your administrator and ask them to approve your account");
|
||||||
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
|
||||||
|
case RublonAuthenticationInterrupt::ErrorClass::UserNotFound:
|
||||||
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,33 +108,71 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(error.is< ConnectionError >()) {
|
if(error.is< ConnectionError >()) {
|
||||||
if(config->bypass) {
|
if(session.value().config().failMode == FailMode::deny) {
|
||||||
pam.print("Connection Error, bypass enabled");
|
pam.print("Incorrect response from the Rublon API, user bypassed");
|
||||||
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
|
||||||
} else {
|
} else {
|
||||||
pam.print("Connection Error, bypass disabled");
|
pam.print("Incorrect response from the Rublon API, user access denied");
|
||||||
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(error.is< CoreHandlerError >()) {
|
if(error.is< CoreHandlerError >()) {
|
||||||
const auto &reson = error.get< CoreHandlerError >().reson;
|
const auto & reson = error.get< CoreHandlerError >().reson;
|
||||||
pam.print("\n RUBLON server returned '%s' exception", reson.c_str());
|
pam.print("Something went wrong and authentication could not be completed, contact your administrator");
|
||||||
|
|
||||||
if(reson == "UserBypassedException" or reson == "UserNotFoundException")
|
if(reson == "UserBypassedException" or reson == "UserNotFoundException")
|
||||||
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(error.is< WerificationError >()) {
|
||||||
|
switch(error.get< WerificationError >().errorClass) {
|
||||||
|
case WerificationError::ErrorClass::PasscodeException:
|
||||||
|
pam.print(R"(Incorrect passcode)");
|
||||||
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
|
||||||
|
case WerificationError::ErrorClass::BadInput:
|
||||||
|
pam.print(R"(Ensure that the Secret Key is correct.)");
|
||||||
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(error.is< RublonCheckApplicationException >()) {
|
||||||
|
switch(error.get< RublonCheckApplicationException >().errorClass) {
|
||||||
|
case RublonCheckApplicationException::ErrorClass::ApplicationNotFoundException:
|
||||||
|
log(LogLevel::Error, R"(Could not find the application in the Rublon Admin Console.)");
|
||||||
|
log(LogLevel::Error, R"(Ensure that the application exists and the SystemToken is correct.)");
|
||||||
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
|
||||||
|
case RublonCheckApplicationException::ErrorClass::InvalidSignatureException:
|
||||||
|
log(LogLevel::Error, R"(Could not verify the signature.)");
|
||||||
|
log(LogLevel::Error, R"(Ensure that the Secret Key is correct.)");
|
||||||
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
|
||||||
|
case RublonCheckApplicationException::ErrorClass::UnsupportedVersionException:
|
||||||
|
log(LogLevel::Error, R"(The provided version of the app is unsupported.)");
|
||||||
|
log(LogLevel::Error, R"(Try changing the app version.)");
|
||||||
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
|
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto ret = Init{config.value()}
|
{
|
||||||
.fire(CH, pam) //
|
CheckApplication ca;
|
||||||
|
auto ret =
|
||||||
|
ca.call(CH, {session.value().config().systemToken.data(), session.value().config().systemToken.size()}).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()}
|
||||||
|
.handle(CH, pam) //
|
||||||
.and_then(selectMethod)
|
.and_then(selectMethod)
|
||||||
.and_then(confirmMethod)
|
.and_then(confirmMethod)
|
||||||
.and_then(confirmCode)
|
.and_then(confirmCode)
|
||||||
|
.and_then(finalizeTransaction)
|
||||||
.and_then(allowLogin)
|
.and_then(allowLogin)
|
||||||
.or_else(mapError);
|
.or_else(mapError);
|
||||||
|
|
||||||
|
|||||||
0
PAM/ssh/tests/CMakeLists.txt
Normal file → Executable file
0
PAM/ssh/tests/CMakeLists.txt
Normal file → Executable file
2
PAM/ssh/tests/authentication_step_common_tests.cpp
Normal file → Executable file
2
PAM/ssh/tests/authentication_step_common_tests.cpp
Normal file → Executable file
@ -1,3 +1,5 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <rublon/authentication_step_interface.hpp>
|
#include <rublon/authentication_step_interface.hpp>
|
||||||
#include <tl/expected.hpp>
|
#include <tl/expected.hpp>
|
||||||
|
|
||||||
|
std::string g_tid="";
|
||||||
|
|||||||
0
PAM/ssh/tests/core_handler_mock.hpp
Normal file → Executable file
0
PAM/ssh/tests/core_handler_mock.hpp
Normal file → Executable file
0
PAM/ssh/tests/core_handler_tests.cpp
Normal file → Executable file
0
PAM/ssh/tests/core_handler_tests.cpp
Normal file → Executable file
0
PAM/ssh/tests/core_response_generator.hpp
Normal file → Executable file
0
PAM/ssh/tests/core_response_generator.hpp
Normal file → Executable file
0
PAM/ssh/tests/gtest_matchers.hpp
Normal file → Executable file
0
PAM/ssh/tests/gtest_matchers.hpp
Normal file → Executable file
6
PAM/ssh/tests/http_mock.hpp
Normal file → Executable file
6
PAM/ssh/tests/http_mock.hpp
Normal file → Executable file
@ -20,8 +20,7 @@ rublon::Configuration conf{rublon::Configuration{//
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
rublon::FailMode::safe}};
|
||||||
true}};
|
|
||||||
rublon::Configuration confBypass{rublon::Configuration{//
|
rublon::Configuration confBypass{rublon::Configuration{//
|
||||||
{"320BAB778C4D4262B54CD243CDEFFAFD"},
|
{"320BAB778C4D4262B54CD243CDEFFAFD"},
|
||||||
{"39e8d771d83a2ed3cc728811911c25"},
|
{"39e8d771d83a2ed3cc728811911c25"},
|
||||||
@ -30,8 +29,7 @@ rublon::Configuration confBypass{rublon::Configuration{//
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true,
|
rublon::FailMode::safe}};
|
||||||
true}};
|
|
||||||
|
|
||||||
inline std::string randomTID() {
|
inline std::string randomTID() {
|
||||||
static const char alphanum[] =
|
static const char alphanum[] =
|
||||||
|
|||||||
0
PAM/ssh/tests/init_test.cpp
Normal file → Executable file
0
PAM/ssh/tests/init_test.cpp
Normal file → Executable file
0
PAM/ssh/tests/method_select_tests.cpp
Normal file → Executable file
0
PAM/ssh/tests/method_select_tests.cpp
Normal file → Executable file
0
PAM/ssh/tests/pam_info_mock.hpp
Normal file → Executable file
0
PAM/ssh/tests/pam_info_mock.hpp
Normal file → Executable file
0
PAM/ssh/tests/passcode_auth_tests.cpp
Normal file → Executable file
0
PAM/ssh/tests/passcode_auth_tests.cpp
Normal file → Executable file
0
PAM/ssh/tests/sign_tests.cpp
Normal file → Executable file
0
PAM/ssh/tests/sign_tests.cpp
Normal file → Executable file
0
PAM/ssh/tests/utils_tests.cpp
Normal file → Executable file
0
PAM/ssh/tests/utils_tests.cpp
Normal file → Executable file
2
os/centos/stream9/Vagrantfile
vendored
2
os/centos/stream9/Vagrantfile
vendored
@ -36,7 +36,7 @@ Vagrant.configure("2") do |config|
|
|||||||
config.vm.provision "shell", inline: <<-SHELL
|
config.vm.provision "shell", inline: <<-SHELL
|
||||||
|
|
||||||
yum update
|
yum update
|
||||||
yum install -y gcc openssl-devel curl-devel pam-devel git rapidjson-devel cmake policycoreutils-devel checkpolicy
|
yum install -y gcc openssl-devel libcurl-devel pam-devel git rapidjson-devel cmake policycoreutils-devel checkpolicy
|
||||||
|
|
||||||
# get dependencies
|
# get dependencies
|
||||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
||||||
|
|||||||
6
os/debian/11/Vagrantfile
vendored
6
os/debian/11/Vagrantfile
vendored
@ -57,10 +57,10 @@ Vagrant.configure("2") do |config|
|
|||||||
|
|
||||||
# Build project
|
# Build project
|
||||||
cd /home/vagrant/Rublon-Linux
|
cd /home/vagrant/Rublon-Linux
|
||||||
cmake -B build && cmake --build build
|
cmake -B buildir && cmake --build buildir
|
||||||
|
|
||||||
# Install
|
# Install
|
||||||
sudo cmake --install build
|
sudo cmake --install buildir
|
||||||
sudo install -m 644 rsc/rublon.config.defaults /etc/rublon.config
|
sudo install -m 644 rsc/rublon.config.defaults /etc/rublon.config
|
||||||
|
|
||||||
# Register Rublon pam
|
# Register Rublon pam
|
||||||
@ -79,6 +79,4 @@ Vagrant.configure("2") do |config|
|
|||||||
|
|
||||||
service sshd restart
|
service sshd restart
|
||||||
SHELL
|
SHELL
|
||||||
|
|
||||||
config.vm.provision "file", source: "/etc/rublon.config", destination: "/etc/rublon.config"
|
|
||||||
end
|
end
|
||||||
|
|||||||
9
os/debian/12/Vagrantfile
vendored
9
os/debian/12/Vagrantfile
vendored
@ -25,6 +25,8 @@ Vagrant.configure("2") do |config|
|
|||||||
config.vm.provider "virtualbox" do |vb|
|
config.vm.provider "virtualbox" do |vb|
|
||||||
# Display the VirtualBox GUI when booting the machine
|
# Display the VirtualBox GUI when booting the machine
|
||||||
vb.gui = true
|
vb.gui = true
|
||||||
|
vb.memory = 1024
|
||||||
|
vb.cpus = 4
|
||||||
|
|
||||||
# Fix for 'SSH auth method: Private key' stuck
|
# Fix for 'SSH auth method: Private key' stuck
|
||||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||||
@ -47,6 +49,12 @@ Vagrant.configure("2") do |config|
|
|||||||
rapidjson-dev \
|
rapidjson-dev \
|
||||||
cmake
|
cmake
|
||||||
|
|
||||||
|
# get dependencies
|
||||||
|
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
|
||||||
|
|
||||||
# Build project
|
# Build project
|
||||||
cd /home/vagrant/Rublon-Linux
|
cd /home/vagrant/Rublon-Linux
|
||||||
cmake -B build && cmake --build build
|
cmake -B build && cmake --build build
|
||||||
@ -70,4 +78,5 @@ Vagrant.configure("2") do |config|
|
|||||||
echo "bwi:bwi"|chpasswd
|
echo "bwi:bwi"|chpasswd
|
||||||
|
|
||||||
service sshd restart
|
service sshd restart
|
||||||
|
SHELL
|
||||||
end
|
end
|
||||||
|
|||||||
13
os/rhel/9/Vagrantfile
vendored
13
os/rhel/9/Vagrantfile
vendored
@ -38,8 +38,14 @@ Vagrant.configure("2") do |config|
|
|||||||
config.vm.provision "shell", inline: <<-SHELL
|
config.vm.provision "shell", inline: <<-SHELL
|
||||||
|
|
||||||
yum update
|
yum update
|
||||||
yum install -y gcc openssl-devel curl-devel pam-devel git rapidjson-devel cmake
|
yum install -y gcc openssl-devel libcurl systemd-pam git-review rapidjson-devel cmake
|
||||||
|
wget https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.9.4.tar.xz
|
||||||
|
tar -xf git*
|
||||||
|
cd git*
|
||||||
|
sudo make configure
|
||||||
|
sudo ./configure --prefix=/usr
|
||||||
|
sudo make all
|
||||||
|
sudo make install
|
||||||
# get dependencies
|
# get dependencies
|
||||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
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
|
mkdir socket.io-client-cpp/build; cd socket.io-client-cpp/build
|
||||||
@ -56,7 +62,7 @@ Vagrant.configure("2") do |config|
|
|||||||
cmake -B build && cmake --build build
|
cmake -B build && cmake --build build
|
||||||
# Install
|
# Install
|
||||||
sudo cmake --install build
|
sudo cmake --install build
|
||||||
sudo install -m 644 rsc/rublon.config.defaults /etc/rublon.config
|
sudo install -m 644 /home/vagrant/Rublon-Linux/rsc/rublon.config.defaults /etc/rublon.config
|
||||||
|
|
||||||
#handle semodule
|
#handle semodule
|
||||||
cd /home/vagrant/Rublon-Linux/service
|
cd /home/vagrant/Rublon-Linux/service
|
||||||
@ -74,6 +80,5 @@ Vagrant.configure("2") do |config|
|
|||||||
systemctl restart sshd.service
|
systemctl restart sshd.service
|
||||||
SHELL
|
SHELL
|
||||||
|
|
||||||
config.vm.provision "file", source: "/etc/rublon.config", destination: "/etc/rublon.config"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
41
os/ubuntu/20.04/Vagrantfile
vendored
41
os/ubuntu/20.04/Vagrantfile
vendored
@ -23,8 +23,10 @@ Vagrant.configure("2") do |config|
|
|||||||
config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux"
|
config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux"
|
||||||
|
|
||||||
config.vm.provider "virtualbox" do |vb|
|
config.vm.provider "virtualbox" do |vb|
|
||||||
|
vb.memory = 2560
|
||||||
|
vb.vcpu = 4
|
||||||
# Display the VirtualBox GUI when booting the machine
|
# Display the VirtualBox GUI when booting the machine
|
||||||
vb.gui = true
|
vb.gui = false
|
||||||
|
|
||||||
# Fix for 'SSH auth method: Private key' stuck
|
# Fix for 'SSH auth method: Private key' stuck
|
||||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||||
@ -34,10 +36,14 @@ Vagrant.configure("2") do |config|
|
|||||||
# Ansible, Chef, Docker, Puppet and Salt are also available. Please see the
|
# Ansible, Chef, Docker, Puppet and Salt are also available. Please see the
|
||||||
# documentation for more information about their specific syntax and use.
|
# documentation for more information about their specific syntax and use.
|
||||||
config.vm.provision "shell", inline: <<-SHELL
|
config.vm.provision "shell", inline: <<-SHELL
|
||||||
|
export BASE=/home/vagrant/Rublon-Linux
|
||||||
|
export DISTRO=ubuntu2004
|
||||||
|
export BUILDDIR=${BASE}/${DISTRO}
|
||||||
|
|
||||||
DEBIAN_FRONTEND=noniteracactive\
|
DEBIAN_FRONTEND=noniteracactive\
|
||||||
apt-get update && apt-get install -y \
|
apt-get update && apt-get install -y \
|
||||||
gcc \
|
gcc \
|
||||||
|
g++ \
|
||||||
build-essential \
|
build-essential \
|
||||||
openssh-server \
|
openssh-server \
|
||||||
libcurl4-openssl-dev \
|
libcurl4-openssl-dev \
|
||||||
@ -48,33 +54,24 @@ Vagrant.configure("2") do |config|
|
|||||||
cmake
|
cmake
|
||||||
|
|
||||||
# get dependencies
|
# get dependencies
|
||||||
|
mkdir ${BUILDDIR} -p; cd ${BUILDDIR}
|
||||||
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
|
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 -B socket.io-build -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release socket.io-client-cpp
|
||||||
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release ..
|
cmake --build socket.io-build --target install -- -j
|
||||||
cmake --build . --target install
|
ln -s /usr/bin/make /usr/bin/gmake
|
||||||
|
|
||||||
# Build project
|
# Build project
|
||||||
cd /home/vagrant/Rublon-Linux
|
cd ${BUILDDIR}
|
||||||
cmake -B build && cmake --build build
|
cmake -B rublon_ssh-build ..
|
||||||
|
cmake --build rublon_ssh-build -- -j
|
||||||
# Install
|
cmake --build rublon_ssh-build --target package -- -j
|
||||||
sudo cmake --install build
|
|
||||||
sudo install -m 644 rsc/rublon.config.defaults /etc/rublon.config
|
|
||||||
|
|
||||||
# Register Rublon pam
|
# Register Rublon pam
|
||||||
|
|
||||||
SSHD_CONF=/etc/ssh/sshd_config
|
SSHD_CONF=/etc/ssh/sshd_config
|
||||||
SSHD_PAM_CONF=/etc/pam.d/sshd
|
grep -qe "^PasswordAuthentication" $SSHD_CONF && sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF
|
||||||
|
|
||||||
grep -qe "^PasswordAuthentication" $SSHD_CONF && sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF || echo "PasswordAuthentication yes" >> $SSHD_CONF
|
|
||||||
grep -qe "^ChallengeResponseAuthentication" $SSHD_CONF && sed -i 's/^#*ChallengeResponseAuthentication[[:space:]]\+.*/ChallengeResponseAuthentication yes/' $SSHD_CONF || echo "ChallengeResponseAuthentication yes" >> $SSHD_CONF
|
|
||||||
grep -qe "^UsePAM" $SSHD_CONF && sed -i 's/^#*UsePAM[[:space:]]\+.*/UsePAM yes/' $SSHD_CONF || echo "UsePAM yes" >> $SSHD_CONF
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
useradd -s /bin/bash -m bwi
|
useradd -s /bin/bash -m bwi
|
||||||
echo "bwi:bwi"|chpasswd
|
echo "bwi:bwi"|chpasswd
|
||||||
|
sudo dpkg -i /home/vagrant/Rublon-Linux/${BUILDDIR}/*pam_*
|
||||||
|
cp /home/vagrant/Rublon-Linux/rublon.config /etc/rublon.config
|
||||||
service sshd restart
|
service sshd restart
|
||||||
SHELL
|
SHELL
|
||||||
end
|
end
|
||||||
|
|||||||
18
os/ubuntu/22.04/Vagrantfile
vendored
18
os/ubuntu/22.04/Vagrantfile
vendored
@ -23,8 +23,10 @@ Vagrant.configure("2") do |config|
|
|||||||
config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux"
|
config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux"
|
||||||
|
|
||||||
config.vm.provider "virtualbox" do |vb|
|
config.vm.provider "virtualbox" do |vb|
|
||||||
|
vb.memory = 2560
|
||||||
|
vb.vcpu = 4
|
||||||
# Display the VirtualBox GUI when booting the machine
|
# Display the VirtualBox GUI when booting the machine
|
||||||
vb.gui = true
|
vb.gui = false
|
||||||
|
|
||||||
# Fix for 'SSH auth method: Private key' stuck
|
# Fix for 'SSH auth method: Private key' stuck
|
||||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||||
@ -64,17 +66,17 @@ Vagrant.configure("2") do |config|
|
|||||||
# Register Rublon pam
|
# Register Rublon pam
|
||||||
SSHD_CONF=/etc/ssh/sshd_config
|
SSHD_CONF=/etc/ssh/sshd_config
|
||||||
SSHD_PAM_CONF=/etc/pam.d/sshd
|
SSHD_PAM_CONF=/etc/pam.d/sshd
|
||||||
|
SSHD_CONFD=/etc/ssh/sshd_config.d/60-cloudimg-settings.conf
|
||||||
|
|
||||||
grep -qe "^PasswordAuthentication" $SSHD_CONF && sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF || echo "PasswordAuthentication yes" >> $SSHD_CONF
|
grep -qe "^PasswordAuthentication" $SSHD_CONF && sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF
|
||||||
grep -qe "^ChallengeResponseAuthentication" $SSHD_CONF && sed -i 's/^#*ChallengeResponseAuthentication[[:space:]]\+.*/ChallengeResponseAuthentication yes/' $SSHD_CONF || echo "ChallengeResponseAuthentication yes" >> $SSHD_CONF
|
sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' $SSHD_CONFD
|
||||||
grep -qe "^UsePAM" $SSHD_CONF && sed -i 's/^#*UsePAM[[:space:]]\+.*/UsePAM yes/' $SSHD_CONF || echo "UsePAM yes" >> $SSHD_CONF
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
useradd -s /bin/bash -m bwi
|
useradd -s /bin/bash -m bwi
|
||||||
echo "bwi:bwi"|chpasswd
|
echo "bwi:bwi"|chpasswd
|
||||||
|
cd ./build
|
||||||
|
make package
|
||||||
|
sudo dpkg -i /home/vagrant/Rublon-Linux/build/*pam_*
|
||||||
|
cp /home/vagrant/Rublon-Linux/rublon.config /etc/rublon.config
|
||||||
service sshd restart
|
service sshd restart
|
||||||
SHELL
|
SHELL
|
||||||
end
|
end
|
||||||
|
|||||||
84
os/ubuntu/24.04/Vagrantfile
vendored
Normal file
84
os/ubuntu/24.04/Vagrantfile
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# -*- mode: ruby -*-
|
||||||
|
# vi: set ft=ruby :
|
||||||
|
|
||||||
|
# Default user
|
||||||
|
# ----------------------
|
||||||
|
# login: vagrant
|
||||||
|
# pass: vagrant
|
||||||
|
Vagrant.configure("2") do |config|
|
||||||
|
# Basic configuration
|
||||||
|
config.vm.box = "alvistack/ubuntu-24.04"
|
||||||
|
config.ssh.forward_agent = true
|
||||||
|
|
||||||
|
# Create a public network, which generally matched to bridged network.
|
||||||
|
# Bridged networks make the machine appear as another physical device on
|
||||||
|
# your network.
|
||||||
|
config.vm.network "public_network"
|
||||||
|
|
||||||
|
# Share an additional folder to the guest VM. The first argument is
|
||||||
|
# the path on the host to the actual folder. The second argument is
|
||||||
|
# the path on the guest to mount the folder. And the optional third
|
||||||
|
# argument is a set of non-required options.
|
||||||
|
config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux"
|
||||||
|
|
||||||
|
config.vm.provider "virtualbox" do |vb|
|
||||||
|
vb.memory = 2560
|
||||||
|
vb.vcpu = 4
|
||||||
|
# Display the VirtualBox GUI when booting the machine
|
||||||
|
vb.gui = false
|
||||||
|
|
||||||
|
# Fix for 'SSH auth method: Private key' stuck
|
||||||
|
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Enable provisioning with a shell script. Additional provisioners such as
|
||||||
|
# Ansible, Chef, Docker, Puppet and Salt are also available. Please see the
|
||||||
|
# documentation for more information about their specific syntax and use.
|
||||||
|
config.vm.provision "shell", inline: <<-SHELL
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
# Build project
|
||||||
|
cd /home/vagrant/Rublon-Linux
|
||||||
|
cmake -B build && cmake --build build
|
||||||
|
|
||||||
|
# Install
|
||||||
|
sudo cmake --install build
|
||||||
|
sudo install -m 644 rsc/rublon.config.defaults /etc/rublon.config
|
||||||
|
|
||||||
|
# Register Rublon pam
|
||||||
|
SSHD_CONF=/etc/ssh/sshd_config
|
||||||
|
SSHD_PAM_CONF=/etc/pam.d/sshd
|
||||||
|
|
||||||
|
grep -qe "^PasswordAuthentication" $SSHD_CONF && sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF || echo "PasswordAuthentication yes" >> $SSHD_CONF
|
||||||
|
grep -qe "^ChallengeResponseAuthentication" $SSHD_CONF && sed -i 's/^#*ChallengeResponseAuthentication[[:space:]]\+.*/ChallengeResponseAuthentication yes/' $SSHD_CONF || echo "ChallengeResponseAuthentication yes" >> $SSHD_CONF
|
||||||
|
grep -qe "^UsePAM" $SSHD_CONF && sed -i 's/^#*UsePAM[[:space:]]\+.*/UsePAM yes/' $SSHD_CONF || echo "UsePAM yes" >> $SSHD_CONF
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
useradd -s /bin/bash -m bwi
|
||||||
|
echo "bwi:bwi"|chpasswd
|
||||||
|
cd ./build
|
||||||
|
make package
|
||||||
|
sudo dpkg -i /home/vagrant/Rublon-Linux/build/*pam_*
|
||||||
|
cp /home/vagrant/Rublon-Linux/rublon.config /etc/rublon.config
|
||||||
|
service sshd restart
|
||||||
|
SHELL
|
||||||
|
end
|
||||||
4
pack.cmake
Normal file → Executable file
4
pack.cmake
Normal file → Executable file
@ -12,7 +12,6 @@ set(CPACK_PACKAGE_VENDOR "Rublon")
|
|||||||
|
|
||||||
#set(CPACK_VERBATIM_VARIABLES YES)
|
#set(CPACK_VERBATIM_VARIABLES YES)
|
||||||
|
|
||||||
|
|
||||||
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
|
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
|
||||||
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
|
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
|
||||||
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
|
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
|
||||||
@ -30,8 +29,7 @@ set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS YES)
|
|||||||
|
|
||||||
set(CPACK_GENERATOR "DEB")
|
set(CPACK_GENERATOR "DEB")
|
||||||
# set(CPACK_DEBIAN_DEV_PACKAGE_DEPENDS "libcurl4(>= 7.0.0), libc(>= 2.0)")
|
# set(CPACK_DEBIAN_DEV_PACKAGE_DEPENDS "libcurl4(>= 7.0.0), libc(>= 2.0)")
|
||||||
# set(CPACK_DEBIAN_PACKAGE_DEPENDS "libcurl4(>= 7.0.0), libc(>= 2.0)")
|
# set(CPACK_DEBIAN_PACKAGE_DEPENDS "libcurl4(>= 7.0.0), libc(>= 2.0), libssl(>= 1.0)")
|
||||||
|
|
||||||
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/service/postinst")
|
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/service/postinst")
|
||||||
|
|
||||||
include(CPack)
|
include(CPack)
|
||||||
|
|||||||
3
rsc/rublon.config.defaults
Normal file → Executable file
3
rsc/rublon.config.defaults
Normal file → Executable file
@ -2,8 +2,7 @@ systemToken=
|
|||||||
secretKey=
|
secretKey=
|
||||||
userDomain=
|
userDomain=
|
||||||
rublonApiServer=https://core.rublon.net
|
rublonApiServer=https://core.rublon.net
|
||||||
failMode=bypass
|
failMode=safe
|
||||||
offlineBypass=true
|
|
||||||
prompt=1
|
prompt=1
|
||||||
logging=true
|
logging=true
|
||||||
enablePasswdEmail=true
|
enablePasswdEmail=true
|
||||||
|
|||||||
0
service/login_rublon.mod
Normal file → Executable file
0
service/login_rublon.mod
Normal file → Executable file
0
service/login_rublon.pp
Normal file → Executable file
0
service/login_rublon.pp
Normal file → Executable file
0
service/login_rublon.te
Normal file → Executable file
0
service/login_rublon.te
Normal file → Executable file
20
service/postinst
Normal file → Executable file
20
service/postinst
Normal file → Executable file
@ -6,33 +6,29 @@ RUBLON_CONFIG=/etc/rublon.config
|
|||||||
|
|
||||||
if [ ! -f /etc/rublon.config ]
|
if [ ! -f /etc/rublon.config ]
|
||||||
then
|
then
|
||||||
mkdir -p /etc/rublon
|
|
||||||
cp -a /usr/share/rublon/rublon.config.defaults $RUBLON_CONFIG
|
cp -a /usr/share/rublon/rublon.config.defaults $RUBLON_CONFIG
|
||||||
chown root:root $RUBLON_CONFIG
|
chown root:root $RUBLON_CONFIG
|
||||||
chmod 640 $RUBLON_CONFIG
|
chmod 640 $RUBLON_CONFIG
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# get system id
|
if [ -f /etc/os-release ]
|
||||||
. /etc/os-release
|
then
|
||||||
|
. /etc/os-release
|
||||||
|
fi
|
||||||
|
|
||||||
grep -qe "^PasswordAuthentication" $SSHD_CONF && \
|
grep -qe "^PasswordAuthentication" $SSHD_CONF && \
|
||||||
sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF || \
|
sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF || \
|
||||||
echo "PasswordAuthentication yes" >> $SSHD_CONF
|
echo "PasswordAuthentication yes" >> $SSHD_CONF
|
||||||
|
|
||||||
grep -qe "^ChallengeResponseAuthentication" $SSHD_CONF && \
|
grep -qe "^ChallengeResponseAuthentication" $SSHD_CONF && \
|
||||||
sed -i 's/^#*ChallengeResponseAuthentication[[:space:]]\+.*/ChallengeResponseAuthentication yes/' $SSHD_CONF || \
|
sed -i 's/^#*ChallengeResponseAuthentication[[:space:]]\+.*/ChallengeResponseAuthentication yes/' $SSHD_CONF || \
|
||||||
echo "ChallengeResponseAuthentication yes" >> $SSHD_CONF
|
echo "ChallengeResponseAuthentication yes" >> $SSHD_CONF
|
||||||
|
|
||||||
grep -qe "^UsePAM" $SSHD_CONF && \
|
grep -qe "^UsePAM" $SSHD_CONF && \
|
||||||
sed -i 's/^#*UsePAM[[:space:]]\+.*/UsePAM yes/' $SSHD_CONF || \
|
sed -i 's/^#*UsePAM[[:space:]]\+.*/UsePAM yes/' $SSHD_CONF || \
|
||||||
echo "UsePAM yes" >> $SSHD_CONF
|
echo "UsePAM yes" >> $SSHD_CONF
|
||||||
|
|
||||||
|
sed -i 's/KbdInteractiveAuthentication/#KbdInteractiveAuthentication/' $SSHD_CONF
|
||||||
|
|
||||||
grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth required pam_rublon.so' $SSHD_PAM_CONF
|
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 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aaccount required pam_rublon.so' $SSHD_PAM_CONF
|
||||||
|
|
||||||
|
|
||||||
# ### UBUNTU 22.04 notes
|
|
||||||
# on ubuntu 22.04 options
|
|
||||||
# PasswordAuthentication yes
|
|
||||||
# ChallengeResponseAuthentication yes
|
|
||||||
# need to be set in configuration file AFTER UsePAM yes
|
|
||||||
# it will fail to use PAM (and will lock the machine) it this is not true
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user