Allow secrets stored in encrypted files to be loaded from route handlers
This commit is contained in:
parent
05176d70ef
commit
df2b5ba9f6
|
@ -83,6 +83,10 @@ set(CONFIGURATION_PACKAGE_SUFFIX
|
||||||
find_package(c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.11.0 REQUIRED)
|
find_package(c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.11.0 REQUIRED)
|
||||||
use_cpp_utilities(VISIBILITY PUBLIC)
|
use_cpp_utilities(VISIBILITY PUBLIC)
|
||||||
|
|
||||||
|
# find passwordfile
|
||||||
|
find_package(passwordfile${CONFIGURATION_PACKAGE_SUFFIX} 5.0.0 REQUIRED)
|
||||||
|
use_password_file(VISIBILITY PUBLIC)
|
||||||
|
|
||||||
# find boost libraries
|
# find boost libraries
|
||||||
option(BOOST_STATIC_LINKAGE "${STATIC_LINKAGE}" "link statically against Boost (instead of dynamically)")
|
option(BOOST_STATIC_LINKAGE "${STATIC_LINKAGE}" "link statically against Boost (instead of dynamically)")
|
||||||
set(Boost_USE_MULTITHREADED ON)
|
set(Boost_USE_MULTITHREADED ON)
|
||||||
|
|
|
@ -51,39 +51,41 @@ static constexpr char toLower(const char c)
|
||||||
return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c;
|
return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserPermissions ServiceSetup::Authentication::authenticate(std::string_view authorizationHeader) const
|
UserAuth ServiceSetup::Authentication::authenticate(std::string_view authorizationHeader) const
|
||||||
{
|
{
|
||||||
// extract user name and password from base64 encoded header value
|
// extract user name and password from base64 encoded header value
|
||||||
|
auto auth = UserAuth();
|
||||||
if (!CppUtilities::startsWith(authorizationHeader, "Basic ") && authorizationHeader.size() < 100) {
|
if (!CppUtilities::startsWith(authorizationHeader, "Basic ") && authorizationHeader.size() < 100) {
|
||||||
return UserPermissions::DefaultPermissions;
|
return auth;
|
||||||
}
|
}
|
||||||
std::pair<std::unique_ptr<std::uint8_t[]>, std::uint32_t> data;
|
std::pair<std::unique_ptr<std::uint8_t[]>, std::uint32_t> data;
|
||||||
try {
|
try {
|
||||||
data = CppUtilities::decodeBase64(authorizationHeader.data() + 6, static_cast<std::uint32_t>(authorizationHeader.size() - 6));
|
data = CppUtilities::decodeBase64(authorizationHeader.data() + 6, static_cast<std::uint32_t>(authorizationHeader.size() - 6));
|
||||||
} catch (const CppUtilities::ConversionException &) {
|
} catch (const CppUtilities::ConversionException &) {
|
||||||
return UserPermissions::DefaultPermissions;
|
return auth;
|
||||||
}
|
}
|
||||||
const auto parts = CppUtilities::splitStringSimple<std::vector<std::string_view>>(
|
const auto parts = CppUtilities::splitStringSimple<std::vector<std::string_view>>(
|
||||||
std::string_view(reinterpret_cast<const char *>(data.first.get()), data.second), ":", 2);
|
std::string_view(reinterpret_cast<const char *>(data.first.get()), data.second), ":", 2);
|
||||||
if (parts.size() != 2) {
|
if (parts.size() != 2) {
|
||||||
return UserPermissions::DefaultPermissions;
|
return auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// find relevant user
|
// find relevant user
|
||||||
const std::string_view userName = parts[0], password = parts[1];
|
const std::string_view userName = parts[0], password = parts[1];
|
||||||
if (userName.empty() || password.empty()) {
|
if (userName.empty() || password.empty()) {
|
||||||
return UserPermissions::DefaultPermissions;
|
return auth;
|
||||||
}
|
}
|
||||||
if (userName == "try" && password == "again") {
|
if (userName == "try" && password == "again") {
|
||||||
return UserPermissions::TryAgain;
|
auth.permissions = UserPermissions::TryAgain;
|
||||||
|
return auth;
|
||||||
}
|
}
|
||||||
const auto user = users.find(std::string(userName));
|
const auto user = users.find(std::string(userName));
|
||||||
if (user == users.cend()) {
|
if (user == users.cend()) {
|
||||||
return UserPermissions::DefaultPermissions;
|
return auth;
|
||||||
}
|
}
|
||||||
constexpr auto sha512HexSize = 128;
|
constexpr auto sha512HexSize = 128;
|
||||||
if (user->second.passwordSha512.size() != sha512HexSize) {
|
if (user->second.passwordSha512.size() != sha512HexSize) {
|
||||||
return UserPermissions::DefaultPermissions;
|
return auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// hash password
|
// hash password
|
||||||
|
@ -98,12 +100,15 @@ UserPermissions ServiceSetup::Authentication::authenticate(std::string_view auth
|
||||||
for (unsigned char hashNumber : hash) {
|
for (unsigned char hashNumber : hash) {
|
||||||
const auto digits = CppUtilities::numberToString(hashNumber, 16);
|
const auto digits = CppUtilities::numberToString(hashNumber, 16);
|
||||||
if ((toLower(*(i++)) != toLower(digits.size() < 2 ? '0' : digits.front())) || (toLower(*(i++)) != toLower(digits.back()))) {
|
if ((toLower(*(i++)) != toLower(digits.size() < 2 ? '0' : digits.front())) || (toLower(*(i++)) != toLower(digits.back()))) {
|
||||||
return UserPermissions::DefaultPermissions;
|
return auth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the user's permissions
|
// return the user's permissions
|
||||||
return user->second.permissions;
|
auth.permissions = user->second.permissions;
|
||||||
|
auth.name = userName;
|
||||||
|
auth.password = password;
|
||||||
|
return auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace LibRepoMgr
|
} // namespace LibRepoMgr
|
||||||
|
|
|
@ -16,6 +16,12 @@ enum class UserPermissions : std::uint64_t {
|
||||||
DefaultPermissions = ReadBuildActionsDetails,
|
DefaultPermissions = ReadBuildActionsDetails,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct UserAuth {
|
||||||
|
std::string_view name;
|
||||||
|
std::string_view password;
|
||||||
|
UserPermissions permissions = UserPermissions::DefaultPermissions;
|
||||||
|
};
|
||||||
|
|
||||||
constexpr UserPermissions operator|(UserPermissions lhs, UserPermissions rhs)
|
constexpr UserPermissions operator|(UserPermissions lhs, UserPermissions rhs)
|
||||||
{
|
{
|
||||||
return static_cast<UserPermissions>(
|
return static_cast<UserPermissions>(
|
||||||
|
|
|
@ -162,7 +162,7 @@ struct LIBREPOMGR_EXPORT ServiceSetup : public LibPkg::Lockable {
|
||||||
std::unordered_map<std::string, UserInfo> users;
|
std::unordered_map<std::string, UserInfo> users;
|
||||||
|
|
||||||
void applyConfig(const std::string &userName, const std::multimap<std::string, std::string> &multimap);
|
void applyConfig(const std::string &userName, const std::multimap<std::string, std::string> &multimap);
|
||||||
UserPermissions authenticate(std::string_view authorizationHeader) const;
|
UserAuth authenticate(std::string_view authorizationHeader) const;
|
||||||
} auth;
|
} auth;
|
||||||
|
|
||||||
struct LIBREPOMGR_EXPORT Locks {
|
struct LIBREPOMGR_EXPORT Locks {
|
||||||
|
|
|
@ -74,21 +74,34 @@ void Session::received(boost::system::error_code ec, size_t bytesTransferred)
|
||||||
respond(Render::makeAuthRequired(request));
|
respond(Render::makeAuthRequired(request));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto userPermissions = m_setup.auth.authenticate(std::string_view(authInfo->value().data(), authInfo->value().size()));
|
const auto userAuth = m_setup.auth.authenticate(std::string_view(authInfo->value().data(), authInfo->value().size()));
|
||||||
using PermissionFlags = std::underlying_type_t<UserPermissions>;
|
using PermissionFlags = std::underlying_type_t<UserPermissions>;
|
||||||
if (static_cast<PermissionFlags>(userPermissions) & static_cast<PermissionFlags>(UserPermissions::TryAgain)) {
|
if (static_cast<PermissionFlags>(userAuth.permissions) & static_cast<PermissionFlags>(UserPermissions::TryAgain)) {
|
||||||
// send the 401 response again if credentials are 'try again' to show the password prompt for the XMLHttpRequest again
|
// send the 401 response again if credentials are 'try again' to show the password prompt for the XMLHttpRequest again
|
||||||
// note: This is kind of a hack. Maybe there's a better solution to make XMLHttpRequest forget wrongly entered credentials
|
// note: This is kind of a hack. Maybe there's a better solution to make XMLHttpRequest forget wrongly entered credentials
|
||||||
// and instead show the login prompt again?
|
// and instead show the login prompt again?
|
||||||
respond(Render::makeAuthRequired(request));
|
respond(Render::makeAuthRequired(request));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((static_cast<PermissionFlags>(requiredPermissions) & static_cast<PermissionFlags>(userPermissions))
|
if ((static_cast<PermissionFlags>(requiredPermissions) & static_cast<PermissionFlags>(userAuth.permissions))
|
||||||
!= static_cast<PermissionFlags>(requiredPermissions)) {
|
!= static_cast<PermissionFlags>(requiredPermissions)) {
|
||||||
respond(Render::makeForbidden(request));
|
respond(Render::makeForbidden(request));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// prepare file with secrets for user
|
||||||
|
if(!userAuth.name.empty() && !userAuth.password.empty()) {
|
||||||
|
try {
|
||||||
|
m_secrets.clear();
|
||||||
|
m_secrets.setPath(argsToString("secrets/"sv, userAuth.name));
|
||||||
|
m_secrets.setPassword(userAuth.password.data(), userAuth.password.size());
|
||||||
|
} catch (const std::ios_base::failure &e) {
|
||||||
|
cerr << Phrases::WarningMessage << "Failed to close password file \"" << m_secrets.path() << "\" (before preparing new one): " << e.what() << Phrases::End;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// invoke the route's handler
|
||||||
|
// note: The error handling is in vain if an exception in a deferred handler is thrown.
|
||||||
try {
|
try {
|
||||||
route.handler(move(params),
|
route.handler(move(params),
|
||||||
std::bind(
|
std::bind(
|
||||||
|
@ -96,6 +109,14 @@ void Session::received(boost::system::error_code ec, size_t bytesTransferred)
|
||||||
} catch (const BadRequest &badRequest) {
|
} catch (const BadRequest &badRequest) {
|
||||||
respond(Render::makeBadRequest(request, badRequest.what()));
|
respond(Render::makeBadRequest(request, badRequest.what()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// discard password; secrets are expected to be read on the immediate call of the route
|
||||||
|
try {
|
||||||
|
m_secrets.clearPassword();
|
||||||
|
m_secrets.close();
|
||||||
|
} catch (const std::ios_base::failure &e) {
|
||||||
|
cerr << Phrases::WarningMessage << "Failed to close password file \"" << m_secrets.path() << "\": " << e.what() << Phrases::End;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include "./typedefs.h"
|
#include "./typedefs.h"
|
||||||
|
|
||||||
|
#include <passwordfile/io/passwordfile.h>
|
||||||
|
|
||||||
#include <boost/beast/core.hpp>
|
#include <boost/beast/core.hpp>
|
||||||
#include <boost/beast/http.hpp>
|
#include <boost/beast/http.hpp>
|
||||||
|
|
||||||
|
@ -31,6 +33,7 @@ public:
|
||||||
void received(boost::system::error_code ec, std::size_t bytesTransferred);
|
void received(boost::system::error_code ec, std::size_t bytesTransferred);
|
||||||
void responded(boost::system::error_code ec, std::size_t bytesTransferred, bool shouldClose);
|
void responded(boost::system::error_code ec, std::size_t bytesTransferred, bool shouldClose);
|
||||||
static boost::beast::string_view determineMimeType(std::string_view path, boost::beast::string_view fallback = "text/plain");
|
static boost::beast::string_view determineMimeType(std::string_view path, boost::beast::string_view fallback = "text/plain");
|
||||||
|
Io::PasswordFile &secrets();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
boost::asio::ip::tcp::socket m_socket;
|
boost::asio::ip::tcp::socket m_socket;
|
||||||
|
@ -39,6 +42,7 @@ private:
|
||||||
std::unique_ptr<RequestParser> m_parser;
|
std::unique_ptr<RequestParser> m_parser;
|
||||||
ServiceSetup &m_setup;
|
ServiceSetup &m_setup;
|
||||||
std::shared_ptr<void> m_res;
|
std::shared_ptr<void> m_res;
|
||||||
|
Io::PasswordFile m_secrets;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline Session::Session(boost::asio::ip::tcp::socket &&socket, ServiceSetup &setup)
|
inline Session::Session(boost::asio::ip::tcp::socket &&socket, ServiceSetup &setup)
|
||||||
|
@ -63,6 +67,11 @@ inline boost::asio::ip::tcp::socket &Session::socket()
|
||||||
return m_socket;
|
return m_socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Io::PasswordFile &Session::secrets()
|
||||||
|
{
|
||||||
|
return m_secrets;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace WebAPI
|
} // namespace WebAPI
|
||||||
} // namespace LibRepoMgr
|
} // namespace LibRepoMgr
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue