Bwi/v2.0.2 rc1 (#10)

* Remove unused options from rublon default config

* Remove safe|secure options

* Allow 9 digits long passcode for passcode bypass

* Change name of 'Mobile Passcode' to 'Passcode'

* Do not display any prompt when user is waiting

* remove unused alloca.h header

* Add autopushPrompt option

* Change name OTP method

* Change enrolement message handling

* ad static string ctor

* Addded postrm script

* Rename 01_rublon_ssh.conf to 01-rublon-ssh.conf

* restart sshd service after rublon package instalation

* Fix sshd not restarting bug on ubuntu 24.04

* disable logging from websocket-io

* change package name to match old package name

* Fix compilation issue when using non owning ptr

* Set version to 2.0.0
This commit is contained in:
rublon-bwi 2024-06-17 08:57:26 +02:00 committed by GitHub
parent 9415174eba
commit 6a3882fa47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 336 additions and 120 deletions

View File

@ -7,7 +7,7 @@ include(GNUInstallDirs)
set(PROJECT_VERSION_MAJOR 2)
set(PROJECT_VERSION_MINOR 0)
set(PROJECT_VERSION_PATCH 1)
set(PROJECT_VERSION_PATCH 0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
@ -16,7 +16,6 @@ set(CMAKE_CXX_EXTENSIONS NO)
add_compile_options(-Wall -Wextra -Wpedantic -Wno-format-security)
option(ENABLE_TESTS "Enable tests" OFF)
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)
@ -26,6 +25,7 @@ add_custom_target(INSTSCRIPTS_IDE SUORCES ${CMAKE_CURRENT_LIST_DIR}/service/post
install(
FILES
${CMAKE_CURRENT_LIST_DIR}/rsc/rublon.config.defaults
${CMAKE_CURRENT_LIST_DIR}/service/01-rublon-ssh.conf.default
DESTINATION
share/rublon
COMPONENT
@ -41,5 +41,4 @@ if (${ENABLE_TESTS})
endif()
add_subdirectory(PAM/ssh)
include(pack.cmake)

View File

@ -22,7 +22,7 @@ set(INC
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/websockets.hpp
)
add_library(rublon-ssh
add_library(rublon-ssh-ifc
INTERFACE
)
@ -31,7 +31,7 @@ if(${CMAKE_VERSION} VERSION_GREATER "3.19.0")
add_library(rublon-ssh_ide INTERFACE ${INC})
endif()
target_include_directories(rublon-ssh
target_include_directories(rublon-ssh-ifc
INTERFACE
extern
${CMAKE_CURRENT_LIST_DIR}/include

View File

@ -1,29 +1,54 @@
#pragma once
#include <rapidjson/prettywriter.h>
#include <rapidjson/rapidjson.h>
#include <rublon/core_handler.hpp>
#include <rublon/curl.hpp>
#include <rublon/error.hpp>
#include <rublon/json.hpp>
#include <rublon/memory.hpp>
#include <rublon/utils.hpp>
#include <string>
#include <string_view>
namespace rublon {
std::string exec(const char * cmd) {
std::array< char, 128 > buffer;
std::string result;
std::unique_ptr< FILE, decltype(&pclose) > pipe(popen(cmd, "r"), pclose);
if(!pipe) {
return "";
}
while(fgets(buffer.data(), static_cast< int >(buffer.size()), pipe.get()) != nullptr) {
result += buffer.data();
}
return result;
}
std::map< std::string, std::string > getSSHDConfig() {
std::istringstream iss(exec("sshd -T"));
std::map< std::string, std::string > result;
for(std::string line; std::getline(iss, line);) {
auto first_token = line.substr(0, line.find(' '));
result[first_token] = line.substr(line.find(' ') + 1);
}
return result;
}
class Status {
std::string_view _statusDirPath = "/var/lib/rublon";
std::string_view _statusFilePath = "/var/lib/rublon/install.json";
RapidJSONPMRStackAlloc< 4 * 1024 > _alloc;
RapidJSONPMRStackAlloc< 8 * 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";
std::string_view _appVersionKey = "/appVer";
std::string_view _appTypeKey = "/type";
std::string_view _paramSystemName = "/params/os";
std::string_view _paramSSHDBase = "/params/sshd_config/";
public:
Status() : _data{&_alloc} {
@ -44,31 +69,68 @@ class Status {
void updateAppVersion(std::string_view newVersion) {
RapidJSONPMRStackAlloc< 128 > stackAlloc;
auto version = JSONPointer{_appVersionKey.data(), &stackAlloc}.Get(_data);
auto jsonPointer = JSONPointer{_appVersionKey.data(), &stackAlloc};
auto version = jsonPointer.Get(_data);
if(not version || version->GetString() != newVersion) {
_statusUpdated = true;
auto version = Value{newVersion.data(), _data.GetAllocator()};
JSONPointer{_appVersionKey.data(), &stackAlloc}.Set(_data, version);
markUpdated();
jsonPointer.Set(_data, Value{newVersion.data(), _data.GetAllocator()});
}
}
void updateSystemVersion(std::string_view system) {
RapidJSONPMRStackAlloc< 128 > stackAlloc;
auto version = JSONPointer{_paramSystemName.data(), &stackAlloc}.Get(_data);
auto jsonPointer = JSONPointer{_paramSystemName.data(), &stackAlloc};
auto version = jsonPointer.Get(_data);
if(not version || version->GetString() != system) {
_statusUpdated = true;
auto version = Value{system.data(), _data.GetAllocator()};
JSONPointer{_paramSystemName.data(), &stackAlloc}.Set(_data, version);
markUpdated();
jsonPointer.Set(_data, Value{system.data(), _data.GetAllocator()});
}
}
void updateSSHDConfig() {
using namespace std::string_view_literals;
constexpr auto keys = make_array("authenticationmethods"sv,
"challengeresponseauthentication"sv,
"kbdinteractiveauthentication"sv,
"logingracetime"sv,
"maxauthtries"sv,
"passwordauthentication"sv,
"permitemptypasswords"sv,
"permitrootlogin"sv,
"pubkeyauthentication"sv,
"usepam"sv);
auto config = getSSHDConfig();
for(const auto key : keys) {
auto [currentPair, inserted] = config.try_emplace(std::string{key.data()}, "N/A");
auto & [currentKey, currentValue] = *currentPair;
const auto jsonPath = std::string{_paramSSHDBase.data()} + key.data();
RapidJSONPMRStackAlloc< 512 > stackAlloc;
auto jsonPointer = JSONPointer{jsonPath.c_str(), &stackAlloc};
auto oldValue = jsonPointer.Get(_data);
if(not oldValue || oldValue->GetString() != currentValue) {
_statusUpdated = true;
jsonPointer.Set(_data, Value{currentValue.c_str(), _data.GetAllocator()});
}
}
}
void markUpdated() {
_statusUpdated = true;
}
bool updated() const {
return _statusUpdated;
}
void save() {
if(updated()) {
memory::Monotonic_1k_HeapResource tmpResource;
memory::Monotonic_8k_HeapResource tmpResource;
RapidJSONPMRAlloc alloc{&tmpResource};
FileWriter s{_statusFilePath};
rapidjson::PrettyWriter< FileWriter, rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc > writer{s, &alloc};
@ -77,6 +139,18 @@ class Status {
}
}
std::string print() {
std::string result;
memory::Monotonic_8k_HeapResource tmpResource;
RapidJSONPMRAlloc alloc{&tmpResource};
StringWriter s{result};
rapidjson::PrettyWriter< StringWriter< std::string >, rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc > writer{s, &alloc};
writer.SetIndent(' ', 2);
_data.Accept(writer);
return result;
}
Document & data() {
return _data;
}
@ -99,8 +173,9 @@ class CheckApplication {
const auto persist = [&](const auto /*ok*/) { return this->persistStatus(status); };
status.updateAppVersion("2.0.2");
status.updateAppVersion("2.0.0");
status.updateSystemVersion(details::osName(&mr));
status.updateSSHDConfig();
if(status.updated()) {
auto & alloc = status.data().GetAllocator();

View File

@ -64,9 +64,9 @@ namespace {
template <>
auto to(std::string_view arg) -> tl::expected< FailMode, ConfigurationError > {
if(arg == "safe" || "bypass")
if(arg == "bypass")
return FailMode::bypass;
if(arg == "secure" || arg == "deny")
if(arg == "deny")
return FailMode::deny;
return tl::unexpected{ConfigurationError{ConfigurationError::ErrorClass::BadFailMode}};
}

View File

@ -117,6 +117,7 @@ class WerificationError {
class RublonAuthenticationInterrupt {
public:
// UserPending -> user has no methods configured
enum ErrorClass { UserBaypass, UserDenied, UserPending, UserWaiting, UserNotFound };
constexpr static auto errorClassPrettyName =
make_array("UserBypassedException", "UserDenied", "UserPending", "UserWaiting", "UserNotFoundException");

View File

@ -31,8 +31,8 @@ class Init : public AuthenticationStep {
tl::expected< MethodSelect_t, Error > createMethod(const Document & coreResponse) const {
const auto & rublonResponse = coreResponse["result"];
std::string tid = rublonResponse["tid"].GetString();
g_tid = tid;
return MethodSelect_t{this->_systemToken, tid, rublonResponse["methods"], _session.config().prompt};
g_tid = tid; ///TODO set tid in session
return MethodSelect_t{this->_systemToken, tid, rublonResponse["methods"], _session.config().prompt, _session.config().autopushPrompt};
}
template < typename PamInfo_t >
@ -58,7 +58,7 @@ class Init : public AuthenticationStep {
Value params{rapidjson::kObjectType};
params.AddMember("userIP", ip, alloc);
params.AddMember("appVer", "2.0.2", alloc); /// TODO add version to cmake
params.AddMember("appVer", "2.0.0", alloc); /// TODO add version to cmake
params.AddMember("os", osNamePretty, alloc);
coreRequest.AddMember("params", std::move(params), alloc);
@ -69,15 +69,20 @@ class Init : public AuthenticationStep {
using namespace std::string_view_literals;
const auto & resp = coreResponse;
///TODO refactor this
if(resp.HasMember("result") and resp["result"].IsObject() and resp["result"].HasMember("status")) {
const auto & status = resp["result"]["status"].GetString();
log(LogLevel::Warning, "Got enrolement message with stats %s", status);
if((status == "pending"sv || status == "waiting"sv) and resp["result"].HasMember("webURI")) {
const auto & weburi = resp["result"]["webURI"].GetString();
pam.print("Visit %s", weburi);
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserPending}}};
if(status == "pending"sv ) {
if(resp["result"].HasMember("webURI")){
const auto & weburi = resp["result"]["webURI"].GetString();
pam.print("Visit %s", weburi);
}
}
if(status == "denied"sv) {
else if(status == "waiting"sv){
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserWaiting}}};
}
else if(status == "denied"sv) {
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserDenied}}};
}
}
@ -86,7 +91,7 @@ class Init : public AuthenticationStep {
}
public:
const char * name = "Initialization";
const char * _name = "Initialization";
const Session & _session;

View File

@ -17,7 +17,7 @@ class OTP : public PasscodeBasedAuth {
std::move(tid),
"",
"Mobile Passcode",
"Enter the passcode from the Rublon Authenticator mobile app: ",
"Enter the passcode from the Authenticator app: ",
6,
true,
PasscodeBasedAuth::Endpoint::ConfirmCode,

View File

@ -12,8 +12,8 @@ namespace rublon::method {
class PUSH : public WebsocketBasedAuth {
public:
PUSH(std::string systemToken, std::string tid)
: WebsocketBasedAuth(std::move(systemToken), std::move(tid), "Mobile PUSH") {}
PUSH(std::string systemToken, std::string tid, bool autopush)
: WebsocketBasedAuth(std::move(systemToken), std::move(tid), "Mobile PUSH", autopush) {}
};
} // namespace rublon::method

View File

@ -1,11 +1,15 @@
#pragma once
#include <set>
#include <string_view>
#include <variant>
#include <vector>
#include <tl/expected.hpp>
#include <variant>
#include <rublon/core_handler.hpp>
#include <rublon/error.hpp>
#include <rublon/memory.hpp>
#include <rublon/pam.hpp>
#include <rublon/pam_action.hpp>
@ -21,7 +25,6 @@ extern std::string g_tid;
namespace rublon {
class MethodProxy {
public:
template < typename Method_t >
@ -32,7 +35,7 @@ class MethodProxy {
coreHandler.createWSConnection(g_tid);
return std::visit(
[&](const auto & method) {
rublon::log(LogLevel::Info, "Using '%s' method", method.name);
log(LogLevel::Info, "Using '%s' method", method._name);
return method.verify(coreHandler, pam);
},
_impl);
@ -47,6 +50,7 @@ class PostMethod : public AuthenticationStep {
const char * uri = "/api/transaction/methodSSH";
std::string _method;
int _prompts;
bool _autopushPrompt;
tl::expected< MethodProxy, Error > createMethod(const Document & coreResponse) const {
const auto & rublonResponse = coreResponse["result"];
@ -58,9 +62,9 @@ class PostMethod : public AuthenticationStep {
} else if(_method == "sms") {
return MethodProxy{method::SMS{this->_systemToken, std::move(tid), _prompts}};
} else if(_method == "push") {
return MethodProxy{method::PUSH{this->_systemToken, std::move(tid)}};
return MethodProxy{method::PUSH{this->_systemToken, std::move(tid), _autopushPrompt}};
} else if(_method == "email") {
return MethodProxy{method::PUSH{this->_systemToken, std::move(tid)}};
return MethodProxy{method::EMAIL{this->_systemToken, std::move(tid)}};
} else if(_method == "smsLink") {
return MethodProxy{method::SmsLink{this->_systemToken, std::move(tid)}};
} else if(_method == "yotp") {
@ -75,10 +79,10 @@ class PostMethod : public AuthenticationStep {
}
public:
const char * name = "Confirm Method";
const char * _name = "Confirm Method";
PostMethod(std::string systemToken, std::string tid, std::string method, int prompts)
: base_t(std::move(systemToken), std::move(tid)), _method{method}, _prompts{prompts} {}
PostMethod(std::string systemToken, std::string tid, std::string method, int prompts, bool autopushPrompt)
: base_t(std::move(systemToken), std::move(tid)), _method{method}, _prompts{prompts}, _autopushPrompt{autopushPrompt} {}
template < typename Hander_t, typename PamInfo_t = LinuxPam >
tl::expected< MethodProxy, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const {
@ -102,19 +106,29 @@ class MethodSelect {
std::string _accessToken;
std::string _tid;
int _prompts;
bool _autopushPrompt;
std::vector< std::string > _methods;
std::vector< std::string > _methodsAvailable;
public:
template < typename Array_t >
MethodSelect(std::string systemToken, std::string tid, const Array_t & methodsAvailableForUser, int prompts)
: _systemToken{std::move(systemToken)}, _tid{std::move(tid)}, _prompts{prompts} {
_methods.reserve(std::size(methodsAvailableForUser));
std::transform(
std::begin(methodsAvailableForUser), std::end(methodsAvailableForUser), std::back_inserter(_methods), [](const auto & method) {
return method.GetString();
});
rublon::log(LogLevel::Debug, "User has %d methods available", _methods.size());
MethodSelect(std::string systemToken, std::string tid, const Array_t & methodsAvailableForUser, int prompts, bool autopushPrompt)
: _systemToken{std::move(systemToken)}, _tid{std::move(tid)}, _prompts{prompts}, _autopushPrompt{autopushPrompt} {
using namespace std::string_view_literals;
memory::MonotonicStackResource< 2024 > stackResource;
std::pmr::vector< std::string_view > _methods;
_methodsAvailable.reserve(std::size(methodsAvailableForUser));
std::pmr::set< std::string_view > methodsSupported{{"totp"sv, "email"sv, "yotp"sv, "sms"sv, "push"sv, "smsLink"sv}, &stackResource};
transform_if(
std::begin(methodsAvailableForUser),
std::end(methodsAvailableForUser),
std::back_inserter(_methodsAvailable),
[&](const auto & method) { return method.GetString(); },
[&](const auto & method) { return methodsSupported.find(method.GetString()) != methodsSupported.end(); });
rublon::log(LogLevel::Debug, "User has %d methods available", _methodsAvailable.size());
}
template < typename Pam_t >
@ -122,9 +136,14 @@ class MethodSelect {
rublon::log(LogLevel::Debug, "prompting user to select method");
memory::StrictMonotonic_4k_HeapResource memoryResource;
std::pmr::map< int, std::string > methods_id{&memoryResource};
std::pmr::map< int, std::string > methods_names{&memoryResource};
std::pmr::map< int, std::pmr::string > methods_names{&memoryResource};
int prompts = _prompts;
if(_methodsAvailable.size() == 0) {
log(LogLevel::Warning, "None of provided methods are supported by the connector");
return tl::unexpected(MethodError(MethodError::ErrorClass::NoMethodAvailable));
}
pam.print("Select the authentication method to verify your identity: ");
auto logMethodAvailable = [](auto & method) { //
@ -136,12 +155,12 @@ class MethodSelect {
auto printAvailableMethods = [&]() -> tl::expected< int, MethodError > {
int i{};
for(const auto & method : _methods) {
for(const auto & method : _methodsAvailable) {
if(method == "totp") {
logMethodAvailable(method);
pam.print("%d: Mobile Passcode", i + 1);
pam.print("%d: Passcode", i + 1);
methods_id[++i] = method;
methods_names[i] = "Mobile Passcode";
methods_names[i] = "Passcode";
continue;
}
@ -203,11 +222,11 @@ class MethodSelect {
auto hasMethod = methods_id.find(methodid) != methods_id.end();
// pam.print("\t selected: %s", hasMethod ? methods_id.at(methodid).c_str() : "unknown option");
if(!hasMethod) {
log(LogLevel::Error, "User selected option %d, which is not corrent", methodid);
log(LogLevel::Error, "User selected option %d, which is not correct", methodid);
return tl::unexpected{MethodError(MethodError::BadMethod)};
} else {
log(LogLevel::Info, "User selected option %d{%s}", methodid, methods_names.at(methodid).c_str());
return PostMethod{_systemToken, _tid, methods_id.at(methodid), _prompts};
return PostMethod{_systemToken, _tid, methods_id.at(methodid), _prompts, _autopushPrompt};
}
};

View File

@ -21,6 +21,8 @@ class PasscodeBasedAuth : public AuthenticationStep {
static constexpr const char * fieldVericode = "vericode";
static constexpr const char * fieldOtp = "otp";
static constexpr auto _bypassCodeLength = 9;
const char * userMessage{nullptr};
const uint_fast8_t length;
@ -36,11 +38,11 @@ class PasscodeBasedAuth : public AuthenticationStep {
}
bool hasValidLength(std::string_view userInput) const {
if(userInput.size() == length) {
if(userInput.size() == length || userInput.size() == _bypassCodeLength) {
log(LogLevel::Debug, "User input size %d is correct", userInput.size());
return true;
} else {
log(LogLevel::Warning, "User input size %d is different then %d", userInput.size(), length);
log(LogLevel::Warning, "User input size %d is different than %d", userInput.size(), length);
return false;
}
}
@ -50,7 +52,7 @@ class PasscodeBasedAuth : public AuthenticationStep {
log(LogLevel::Debug, "User input contains valid characters");
return true;
} else {
log(LogLevel::Warning, "User input contains characters different then digits");
log(LogLevel::Warning, "User input contains characters different than digits");
return false;
}
}
@ -101,7 +103,7 @@ class PasscodeBasedAuth : public AuthenticationStep {
}
public:
const char * name;
const char * _name;
std::string token;
enum class Endpoint { ConfirmCode, SecurityKeySSH };
@ -109,7 +111,7 @@ class PasscodeBasedAuth : public AuthenticationStep {
PasscodeBasedAuth(std::string systemToken,
std::string tid,
std::string token,
const char * name,
const char * _name,
const char * userMessage,
uint_fast8_t length,
@ -123,7 +125,7 @@ class PasscodeBasedAuth : public AuthenticationStep {
length{length},
onlyDigits{numbersOnly},
_prompts{prompts},
name{name},
_name{_name},
token{std::move(token)} {}
template < typename Hander_t, typename PamInfo_t = LinuxPam >

View File

@ -13,16 +13,21 @@ namespace rublon::method {
class WebsocketBasedAuth : public AuthenticationStep {
public:
const char * name = "";
const char * _name = "";
const bool _autopushPrompt = true;
WebsocketBasedAuth(std::string systemToken, std::string tid, const char * name)
: AuthenticationStep(std::move(systemToken), std::move(tid)), name{name} {}
WebsocketBasedAuth(std::string systemToken, std::string tid, const char * name, bool autopushPrompt = true)
: AuthenticationStep(std::move(systemToken), std::move(tid)), _name{name}, _autopushPrompt{autopushPrompt} {}
template < typename Hander_t, typename PamInfo_t = LinuxPam >
tl::expected< AuthenticationStatus, Error > verify(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const {
log(LogLevel::Info, "starting WS");
auto listener = coreHandler.listen();
pam.scan([](const auto/*ignored userinput*/){return "";}, "Rublon authentication initiated. Complete the authentication and press Enter to proceed");
if(not _autopushPrompt)
pam.print("Autopush");
else
pam.scan([](const auto /*ignored userinput*/) { return ""; },
"Rublon authentication initiated. Complete the authentication and press Enter to proceed");
return listener->waitForEvent();
}
};

View File

@ -1,5 +1,7 @@
#pragma once
#include <cassert>
namespace rublon {
template < typename T >

View File

@ -1,13 +1,36 @@
#pragma once
#include <array>
#include <string>
#include <string_view>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <rublon/utils.hpp>
namespace rublon {
inline std::array< char, SHA256_DIGEST_LENGTH * 2 + 1 > SHA256(const char * const path) {
std::string fileContent;
readFile(path, fileContent);
std::array< char, SHA256_DIGEST_LENGTH * 2 + 1 > xRublon{};
std::array< unsigned char, SHA256_DIGEST_LENGTH + 1 > hash{};
SHA256_CTX ctx;
SHA256_Init(&ctx);
SHA256_Update(&ctx, fileContent.data(), fileContent.size());
SHA256_Final(hash.data(), &ctx);
for(unsigned int i = 0; i < SHA256_DIGEST_LENGTH; i++)
sprintf(&xRublon[i * 2], "%02x", ( unsigned int ) hash[i]);
return xRublon;
}
// +1 for \0
inline std::array< char, 64 + 1 > signData(std::string_view data, std::string_view secretKey) {
std::array< char, 64 + 1 > xRublon;

View File

@ -3,12 +3,41 @@
#include <array>
#include <cstddef>
#include <cstring>
#include <utility>
template <typename T, std::size_t N, std::size_t... Idx>
constexpr std::array<T, N> toStdArray(T (&arr)[N], std::index_sequence<Idx...>)
{
return {arr[Idx]...};
}
template <std::size_t NewSize, typename T, std::size_t OldSize, std::size_t... Indexes>
constexpr std::array<T, NewSize> resize(const std::array<T, OldSize>& arr, std::index_sequence<Indexes...>)
{
return {arr[Indexes]...};
}
template <std::size_t NewSize, typename T, std::size_t OldSize>
constexpr std::array<T, NewSize> resize(const std::array<T, OldSize>& arr)
{
constexpr std::size_t minSize = std::min(OldSize, NewSize);
return resize(arr, std::make_index_sequence<minSize>());
}
// statically allocates a string buffer of (N+1) chars
template < size_t N >
class StaticString {
public:
constexpr StaticString() = default;
constexpr StaticString(const char (&chars)[N])
: m_str(toStdArray(chars))
{
}
constexpr StaticString(std::array<const char, N> chars)
: m_str(std::move(chars))
{
}
constexpr StaticString(const char * str) {
std::strncpy(m_str.data(), str, N);
}
@ -18,18 +47,26 @@ class StaticString {
}
const char * c_str() const noexcept {
return &m_str[0];
return m_str.data();
}
const char * data() const noexcept {
return &m_str[0];
return m_str.data();
}
std::size_t size() const {
return strlen(m_str.data());
}
template <std::size_t M>
constexpr StaticString<N + M - 1> operator+(const StaticString<M> &rhs) const
{
return join(resize<N-1>(m_str), rhs.m_str);
}
template <std::size_t M>
friend class StaticString;
private:
std::array< char, N + 1 > m_str{};
};

View File

@ -1,6 +1,7 @@
#pragma once
#include "tl/expected.hpp"
#include <algorithm>
#include <array>
#include <cctype>
@ -13,8 +14,6 @@
#include <sstream>
#include <string_view>
#include <alloca.h>
#include <fcntl.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
@ -56,11 +55,10 @@ inline auto dateStr() {
}
constexpr const char * LogLevelNames[]{"Debug", "Info", "Warning", "Error"};
LogLevel g_level = LogLevel::Debug;
LogLevel g_level = LogLevel::Debug;
constexpr bool syncLogFile = true;
static const char * application = "";
// #include <openssl/md5.h>
// #include <sys/types.h>
// #include <sys/stat.h>
@ -103,7 +101,6 @@ static const char * application = "";
namespace details {
std::pmr::string osName(std::pmr::memory_resource * mr) {
memory::MonotonicStackResource< 8 * 1024 > stackResource;
@ -229,7 +226,7 @@ namespace conv {
// todo return optional
return false;
}
std::array< char, 16 >buf{};
std::array< char, 16 > buf{};
auto asciitolower = [](char in) { return in - ((in <= 'Z' && in >= 'A') ? ('Z' - 'z') : 0); };
std::transform(userinput.cbegin(), userinput.cend(), buf.data(), asciitolower);
@ -285,4 +282,15 @@ namespace details {
} // namespace details
template < class InputIterator, class OutputIterator, class UnaryOperator, class Pred >
OutputIterator transform_if(InputIterator first1, InputIterator last1, OutputIterator result, UnaryOperator op, Pred pred) {
while(first1 != last1) {
if(pred(*first1)) {
*result = op(*first1);
++result;
}
++first1;
}
return result;
}
} // namespace rublon

View File

@ -212,7 +212,9 @@ class WebSocket {
sio::client ws;
public:
WebSocket() = default;
WebSocket(){
ws.set_logs_quiet();
};
~WebSocket() = default;
std::shared_ptr< WebSocketSingleShotEventListener > listen() {

View File

@ -1,26 +1,26 @@
add_library(rublon-ssh-pam
add_library(rublon-ssh
SHARED
pam.cpp
)
set_target_properties(rublon-ssh-pam PROPERTIES PREFIX "")
set_target_properties(rublon-ssh-pam PROPERTIES OUTPUT_NAME "pam_rublon")
set_target_properties(rublon-ssh PROPERTIES PREFIX "")
set_target_properties(rublon-ssh PROPERTIES OUTPUT_NAME "pam_rublon")
target_compile_options(rublon-ssh-pam
target_compile_options(rublon-ssh
PUBLIC
-flto
-Wno-deprecated-declarations
)
target_link_options(rublon-ssh-pam
target_link_options(rublon-ssh
PUBLIC
-fpic
-flto
)
target_link_libraries(rublon-ssh-pam
target_link_libraries(rublon-ssh
PUBLIC
rublon-ssh
rublon-ssh-ifc
-lcurl
-lssl
-lcrypto
@ -42,7 +42,7 @@ endif()
install(
TARGETS
rublon-ssh-pam
rublon-ssh
DESTINATION
${_destination}
COMPONENT

View File

@ -59,6 +59,10 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
}
if(!session->config().logging){
g_level = LogLevel::Warning;
}
auto & CH = session.value().coreHandler();
auto selectMethod = [&](const MethodSelect & selector) { return selector.create(pam); };
@ -89,7 +93,7 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
case RublonAuthenticationInterrupt::ErrorClass::UserWaiting:
case RublonAuthenticationInterrupt::ErrorClass::UserPending:
pam.print(
"Your account is awaiting administrator's approval. \n"
"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:
@ -104,7 +108,7 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
case MethodError::ErrorClass::BadUserInput:
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
case MethodError::ErrorClass::NoMethodAvailable:
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
}
}

View File

@ -1,5 +1,5 @@
# these are cache variables, so they could be overwritten with -D,
set(CPACK_PACKAGE_NAME ${PROJECT_NAME}
set(CPACK_PACKAGE_NAME rublon-ssh
CACHE STRING "The resulting package name"
)
@ -17,6 +17,7 @@ set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
set(CPACK_PACKAGE_CONTACT "bwi@rublon.com")
set(CPACK_DEBIAN_PAM_PACKAGE_NAME rublon-ssh)
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Bartosz Wieczorek")
#set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
@ -28,8 +29,12 @@ set(CPACK_DEB_COMPONENT_INSTALL YES)
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS YES)
set(CPACK_GENERATOR "DEB")
#set(CPACK_GENERATOR "RPM")
#set(CPACK_RPM_SPEC_MORE_DEFINE "%define _build_id_links none")
#set(CPACK_RPM_FILE_NAME RPM-DEFAULT)
# 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), 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;${CMAKE_CURRENT_SOURCE_DIR}/service/postrm")
include(CPack)

View File

@ -1,9 +1,7 @@
systemToken=
secretKey=
userDomain=
rublonApiServer=https://core.rublon.net
failMode=safe
failMode=deny
prompt=1
logging=true
enablePasswdEmail=true
autopushPrompt=false

View File

@ -0,0 +1,4 @@
UsePAM yes
PasswordAuthentication yes
ChallengeResponseAuthentication yes
#KbdInteractiveAuthentication no

View File

@ -3,32 +3,36 @@
SSHD_CONF=/etc/ssh/sshd_config
SSHD_PAM_CONF=/etc/pam.d/sshd
RUBLON_CONFIG=/etc/rublon.config
RUBLON_SSH_CONFIG=/etc/ssh/sshd_config.d/01-rublon-ssh.conf
if [ ! -f /etc/rublon.config ]
if [ ! -f $RUBLON_CONFIG ]
then
cp -a /usr/share/rublon/rublon.config.defaults $RUBLON_CONFIG
chown root:root $RUBLON_CONFIG
chmod 640 $RUBLON_CONFIG
fi
if [ ! -f $RUBLON_SSH_CONFIG ]
then
cp -a /usr/share/rublon/01-rublon-ssh.conf.default $RUBLON_SSH_CONFIG
chown root:root $RUBLON_SSH_CONFIG
chmod 640 $RUBLON_SSH_CONFIG
fi
if [ -f /etc/os-release ]
then
. /etc/os-release
fi
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
sed -i 's/KbdInteractiveAuthentication/#KbdInteractiveAuthentication/' $SSHD_CONF
#if [ $ID == "rhel" ]
#then
# cd /home/vagrant/Rublon-Linux/service
# checkmodule -M -m -o login_rublon.mod login_rublon.te
# semodule_package -o login_rublon.pp -m login_rublon.mod
# semodule -i login_rublon.pp
#fi
grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth required pam_rublon.so' $SSHD_PAM_CONF
grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aaccount required pam_rublon.so' $SSHD_PAM_CONF
deb-systemd-invoke restart ssh.service

23
service/postrm Normal file
View File

@ -0,0 +1,23 @@
#!/bin/bash
RUBLON_CONFIG=/etc/rublon.config
RUBLON_SSH_CONFIG=/etc/ssh/sshd_config.d/01-rublon-ssh.conf
SSHD_PAM_CONF=/etc/pam.d/sshd
if [ $1 == 'purge' ]
then
if [ -f $RUBLON_CONFIG ]
then
rm $RUBLON_CONFIG
fi
if [ -f $RUBLON_SSH_CONFIG ]
then
rm $RUBLON_SSH_CONFIG
fi
fi
sed -i '/auth required pam_rublon.so/d' $SSHD_PAM_CONF
sed -i '/account required pam_rublon.so/d' $SSHD_PAM_CONF
deb-systemd-invoke restart ssh.service