Allow passing secrets to build action

This commit is contained in:
Martchus 2022-07-10 20:08:31 +02:00
parent df2b5ba9f6
commit 255da5b091
10 changed files with 85 additions and 37 deletions

View File

@ -85,7 +85,7 @@ use_cpp_utilities(VISIBILITY PUBLIC)
# find passwordfile # find passwordfile
find_package(passwordfile${CONFIGURATION_PACKAGE_SUFFIX} 5.0.0 REQUIRED) find_package(passwordfile${CONFIGURATION_PACKAGE_SUFFIX} 5.0.0 REQUIRED)
use_password_file(VISIBILITY PUBLIC) use_password_file()
# 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)")

View File

@ -13,6 +13,7 @@ enum class UserPermissions : std::uint64_t {
ModifyBuildActions = ReadBuildActionsDetails | DownloadArtefacts | (1 << 2), ModifyBuildActions = ReadBuildActionsDetails | DownloadArtefacts | (1 << 2),
PerformAdminActions = (1 << 3), PerformAdminActions = (1 << 3),
TryAgain = (1 << 4), TryAgain = (1 << 4),
AccessSecrets = (1 << 5),
DefaultPermissions = ReadBuildActionsDetails, DefaultPermissions = ReadBuildActionsDetails,
}; };

View File

@ -3,6 +3,8 @@
#include "../webapi/session.h" #include "../webapi/session.h"
#include <passwordfile/io/passwordfile.h>
#include <reflective_rapidjson/binary/reflector-chronoutilities.h> #include <reflective_rapidjson/binary/reflector-chronoutilities.h>
#include <reflective_rapidjson/json/reflector-chronoutilities.h> #include <reflective_rapidjson/json/reflector-chronoutilities.h>
@ -259,7 +261,7 @@ bool BuildAction::haveSucceeded(const std::vector<std::shared_ptr<BuildAction>>
* the build action is setup-globally visible. * the build action is setup-globally visible.
* \returns Returns immediately. The real work is done in a build action thread. * \returns Returns immediately. The real work is done in a build action thread.
*/ */
LibPkg::StorageID BuildAction::start(ServiceSetup &setup) LibPkg::StorageID BuildAction::start(ServiceSetup &setup, std::unique_ptr<Io::PasswordFile> &&secrets)
{ {
if (!isScheduled()) { if (!isScheduled()) {
return 0; return 0;
@ -269,6 +271,13 @@ LibPkg::StorageID BuildAction::start(ServiceSetup &setup)
status = BuildActionStatus::Running; status = BuildActionStatus::Running;
m_setup = &setup; m_setup = &setup;
// grab secrets from session
// note: That's done regardless of the type because we might need to pass the secrets to the next
// action in the chain (regardless of the current build action's type).
if (secrets) {
m_secrets = std::move(secrets);
}
switch (type) { switch (type) {
case BuildActionType::Invalid: case BuildActionType::Invalid:
resultData = "type is invalid"; resultData = "type is invalid";
@ -366,7 +375,13 @@ LibPkg::StorageID BuildAction::conclude(BuildActionResult result)
const auto followUps = m_setup->building.followUpBuildActions(id); const auto followUps = m_setup->building.followUpBuildActions(id);
for (auto &followUpAction : followUps) { for (auto &followUpAction : followUps) {
if (followUpAction->isScheduled() && BuildAction::haveSucceeded(m_setup->building.getBuildActions(followUpAction->startAfter))) { if (followUpAction->isScheduled() && BuildAction::haveSucceeded(m_setup->building.getBuildActions(followUpAction->startAfter))) {
followUpAction->start(*m_setup); auto secrets = std::unique_ptr<Io::PasswordFile>();
if (m_secrets) {
secrets = std::make_unique<Io::PasswordFile>();
secrets->setPath(m_secrets->path());
secrets->setPassword(m_secrets->password());
}
followUpAction->start(*m_setup, std::move(secrets));
} }
} }
// note: Not cleaning up the follow-up actions here because at some point I might implement recursive restarting. // note: Not cleaning up the follow-up actions here because at some point I might implement recursive restarting.

View File

@ -36,6 +36,10 @@
class BuildActionsTests; class BuildActionsTests;
namespace Io {
class PasswordFile;
}
namespace LibRepoMgr { namespace LibRepoMgr {
struct ServiceSetup; struct ServiceSetup;
@ -208,7 +212,7 @@ public:
static bool haveSucceeded(const std::vector<std::shared_ptr<BuildAction>> &buildActions); static bool haveSucceeded(const std::vector<std::shared_ptr<BuildAction>> &buildActions);
bool isAborted() const; bool isAborted() const;
const std::atomic_bool &aborted() const; const std::atomic_bool &aborted() const;
LibPkg::StorageID start(ServiceSetup &setup); LibPkg::StorageID start(ServiceSetup &setup, std::unique_ptr<Io::PasswordFile> &&secrets);
void assignStartAfter(const std::vector<std::shared_ptr<BuildAction>> &startsAfterBuildActions); void assignStartAfter(const std::vector<std::shared_ptr<BuildAction>> &startsAfterBuildActions);
void abort(); void abort();
void appendOutput(std::string_view output); void appendOutput(std::string_view output);
@ -256,6 +260,7 @@ private:
std::mutex m_outputSessionMutex; std::mutex m_outputSessionMutex;
std::shared_ptr<BuildProcessSession> m_outputSession; std::shared_ptr<BuildProcessSession> m_outputSession;
std::unique_ptr<InternalBuildAction> m_internalBuildAction; std::unique_ptr<InternalBuildAction> m_internalBuildAction;
std::unique_ptr<Io::PasswordFile> m_secrets;
}; };
inline bool BuildActionBase::isScheduled() const inline bool BuildActionBase::isScheduled() const

View File

@ -15,6 +15,8 @@
#include <reflective_rapidjson/binary/serializable.h> #include <reflective_rapidjson/binary/serializable.h>
#include <reflective_rapidjson/json/errorformatting.h> #include <reflective_rapidjson/json/errorformatting.h>
#include <passwordfile/io/passwordfile.h>
#include <c++utilities/application/argumentparser.h> #include <c++utilities/application/argumentparser.h>
#include <c++utilities/conversion/stringbuilder.h> #include <c++utilities/conversion/stringbuilder.h>
#include <c++utilities/conversion/stringconversion.h> #include <c++utilities/conversion/stringconversion.h>
@ -307,7 +309,8 @@ StorageID ServiceSetup::BuildSetup::storeBuildAction(const std::shared_ptr<Build
} }
// immediately start if all follow-up actions have succeeded // immediately start if all follow-up actions have succeeded
if (allSucceeded && buildAction->setup()) { if (allSucceeded && buildAction->setup()) {
return buildAction->start(*buildAction->setup()); // FIXME: Do we actually ever get here?
return buildAction->start(*buildAction->setup(), std::unique_ptr<Io::PasswordFile>());
} }
} else { } else {
for (const auto id : buildAction->startAfter) { for (const auto id : buildAction->startAfter) {

View File

@ -8,6 +8,8 @@
#include "../buildactions/buildactionprivate.h" #include "../buildactions/buildactionprivate.h"
#include "../buildactions/subprocess.h" #include "../buildactions/subprocess.h"
#include <passwordfile/io/passwordfile.h>
#include <c++utilities/conversion/stringconversion.h> #include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/io/ansiescapecodes.h> #include <c++utilities/io/ansiescapecodes.h>
#include <c++utilities/io/misc.h> #include <c++utilities/io/misc.h>
@ -72,6 +74,7 @@ private:
std::string m_configDbFile, m_buildingDbFile; std::string m_configDbFile, m_buildingDbFile;
ServiceSetup m_setup; ServiceSetup m_setup;
std::unique_ptr<Io::PasswordFile> m_secrets;
std::shared_ptr<BuildAction> m_buildAction; std::shared_ptr<BuildAction> m_buildAction;
std::filesystem::path m_workingDir; std::filesystem::path m_workingDir;
double m_timeoutFactor = 0.0; double m_timeoutFactor = 0.0;
@ -190,7 +193,7 @@ void BuildActionsTests::resetBuildAction()
void BuildActionsTests::runBuildAction(const char *message, CppUtilities::TimeSpan timeout) void BuildActionsTests::runBuildAction(const char *message, CppUtilities::TimeSpan timeout)
{ {
resetBuildAction(); resetBuildAction();
m_buildAction->start(m_setup); m_buildAction->start(m_setup, std::move(m_secrets));
auto &ioc = m_setup.building.ioContext; auto &ioc = m_setup.building.ioContext;
ioc.restart(); ioc.restart();
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> workGuard = boost::asio::make_work_guard(ioc); boost::asio::executor_work_guard<boost::asio::io_context::executor_type> workGuard = boost::asio::make_work_guard(ioc);

View File

@ -279,7 +279,7 @@ void postBuildAction(const Params &params, ResponseHandler &&handler)
// start build action immediately or just add to setup-global list for now // start build action immediately or just add to setup-global list for now
auto buildLock2 = params.setup.building.lockToWrite(); auto buildLock2 = params.setup.building.lockToWrite();
if (startImmediately) { if (startImmediately) {
buildAction->start(params.setup); buildAction->start(params.setup, params.session.secrets());
} else { } else {
params.setup.building.storeBuildAction(buildAction); params.setup.building.storeBuildAction(buildAction);
} }
@ -385,7 +385,7 @@ static std::vector<std::shared_ptr<BuildAction>> allocateBuildActionIDs(ServiceS
return previousActions; return previousActions;
} }
static bool startFirstBuildActions(ServiceSetup &setup, SequencedBuildActions &newActionSequence) static bool startFirstBuildActions(const Params &params, SequencedBuildActions &newActionSequence)
{ {
auto handledFirstAction = false; auto handledFirstAction = false;
for (auto &sequencedAction : newActionSequence.actions) { for (auto &sequencedAction : newActionSequence.actions) {
@ -393,13 +393,13 @@ static bool startFirstBuildActions(ServiceSetup &setup, SequencedBuildActions &n
auto &action = *maybeAction; auto &action = *maybeAction;
handledFirstAction = true; handledFirstAction = true;
if (action->isScheduled()) { if (action->isScheduled()) {
action->start(setup); action->start(params.setup, params.session.secrets());
} }
if (!newActionSequence.concurrent) { if (!newActionSequence.concurrent) {
return true; return true;
} }
} else if (auto *const subSequence = std::get_if<SequencedBuildActions>(&sequencedAction)) { } else if (auto *const subSequence = std::get_if<SequencedBuildActions>(&sequencedAction)) {
if (startFirstBuildActions(setup, *subSequence) && !newActionSequence.concurrent) { if (startFirstBuildActions(params, *subSequence) && !newActionSequence.concurrent) {
return true; return true;
} }
} }
@ -475,7 +475,7 @@ void postBuildActionsFromTask(const Params &params, ResponseHandler &&handler, c
// start first build action immediately (read-lock sufficient because build action not part of setup-global list yet) // start first build action immediately (read-lock sufficient because build action not part of setup-global list yet)
if (startNow) { if (startNow) {
startFirstBuildActions(params.setup, newActionSequence); startFirstBuildActions(params, newActionSequence);
} }
// add build actions to setup-global list // add build actions to setup-global list
@ -547,7 +547,7 @@ void postCloneBuildActions(const Params &params, ResponseHandler &&handler)
clone->type = orig->type; clone->type = orig->type;
clone->startAfter = orig->startAfter; clone->startAfter = orig->startAfter;
if (startImmediately) { if (startImmediately) {
clone->start(params.setup); clone->start(params.setup, params.session.secrets());
} }
params.setup.building.storeBuildAction(std::move(clone)); params.setup.building.storeBuildAction(std::move(clone));
cloneIds.emplace_back(id); cloneIds.emplace_back(id);
@ -572,7 +572,7 @@ void postStartBuildActions(const Params &params, ResponseHandler &&handler)
return; return;
} }
for (auto &action : buildActionsSearchResult.actions) { for (auto &action : buildActionsSearchResult.actions) {
action->start(params.setup); action->start(params.setup, params.session.secrets());
} }
buildActionsSearchResult.lock = std::monostate{}; buildActionsSearchResult.lock = std::monostate{};
handler(makeText(params.request(), "ok")); handler(makeText(params.request(), "ok"));

View File

@ -35,9 +35,9 @@ const Router Server::s_router = {
{ { http::verb::get, "/api/v0/build-action/details" }, Route{&Routes::getBuildActionDetails, UserPermissions::ReadBuildActionsDetails} }, { { http::verb::get, "/api/v0/build-action/details" }, Route{&Routes::getBuildActionDetails, UserPermissions::ReadBuildActionsDetails} },
{ { http::verb::get, "/api/v0/build-action/logfile" }, Route{&Routes::getBuildActionLogFile, UserPermissions::ReadBuildActionsDetails} }, { { http::verb::get, "/api/v0/build-action/logfile" }, Route{&Routes::getBuildActionLogFile, UserPermissions::ReadBuildActionsDetails} },
{ { http::verb::get, "/api/v0/build-action/artefact" }, Route{&Routes::getBuildActionArtefact, UserPermissions::DownloadArtefacts} }, { { http::verb::get, "/api/v0/build-action/artefact" }, Route{&Routes::getBuildActionArtefact, UserPermissions::DownloadArtefacts} },
{ { http::verb::post, "/api/v0/build-action" }, Route{&Routes::postBuildAction, UserPermissions::ModifyBuildActions} }, { { http::verb::post, "/api/v0/build-action" }, Route{&Routes::postBuildAction, UserPermissions::ModifyBuildActions | UserPermissions::AccessSecrets} },
{ { http::verb::post, "/api/v0/build-action/clone" }, Route{&Routes::postCloneBuildActions, UserPermissions::ModifyBuildActions} }, { { http::verb::post, "/api/v0/build-action/clone" }, Route{&Routes::postCloneBuildActions, UserPermissions::ModifyBuildActions | UserPermissions::AccessSecrets} },
{ { http::verb::post, "/api/v0/build-action/start" }, Route{&Routes::postStartBuildActions, UserPermissions::ModifyBuildActions} }, { { http::verb::post, "/api/v0/build-action/start" }, Route{&Routes::postStartBuildActions, UserPermissions::ModifyBuildActions | UserPermissions::AccessSecrets} },
{ { http::verb::post, "/api/v0/build-action/stop" }, Route{&Routes::postStopBuildActions, UserPermissions::ModifyBuildActions} }, { { http::verb::post, "/api/v0/build-action/stop" }, Route{&Routes::postStopBuildActions, UserPermissions::ModifyBuildActions} },
{ { http::verb::post, "/api/v0/quit" }, Route{&Routes::postQuit, UserPermissions::PerformAdminActions} }, { { http::verb::post, "/api/v0/quit" }, Route{&Routes::postQuit, UserPermissions::PerformAdminActions} },
}; };

View File

@ -7,6 +7,8 @@
#include "../serversetup.h" #include "../serversetup.h"
#include <passwordfile/io/passwordfile.h>
#include <c++utilities/conversion/stringbuilder.h> #include <c++utilities/conversion/stringbuilder.h>
#include <c++utilities/io/ansiescapecodes.h> #include <c++utilities/io/ansiescapecodes.h>
#include <c++utilities/io/misc.h> #include <c++utilities/io/misc.h>
@ -22,6 +24,17 @@ using namespace CppUtilities::EscapeCodes;
namespace LibRepoMgr { namespace LibRepoMgr {
namespace WebAPI { namespace WebAPI {
Session::Session(boost::asio::ip::tcp::socket &&socket, ServiceSetup &setup)
: m_socket(std::move(socket))
, m_strand(m_socket.get_executor())
, m_setup(setup)
{
}
Session::~Session()
{
}
void Session::receive() void Session::receive()
{ {
m_parser = make_unique<RequestParser>(); m_parser = make_unique<RequestParser>();
@ -89,13 +102,19 @@ void Session::received(boost::system::error_code ec, size_t bytesTransferred)
return; return;
} }
// prepare file with secrets for user // prepare file with secrets for user
if(!userAuth.name.empty() && !userAuth.password.empty()) { if (!userAuth.name.empty() && !userAuth.password.empty()
&& (static_cast<PermissionFlags>(requiredPermissions) & static_cast<PermissionFlags>(UserPermissions::AccessSecrets))) {
try { try {
m_secrets.clear(); if (m_secrets) {
m_secrets.setPath(argsToString("secrets/"sv, userAuth.name)); m_secrets->clear();
m_secrets.setPassword(userAuth.password.data(), userAuth.password.size()); } else {
m_secrets = std::make_unique<Io::PasswordFile>();
}
m_secrets->setPath(argsToString("secrets/"sv, userAuth.name));
m_secrets->setPassword(userAuth.password.data(), userAuth.password.size());
} catch (const std::ios_base::failure &e) { } 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; cerr << Phrases::WarningMessage << "Failed to close password file \"" << m_secrets->path()
<< "\" (before preparing new one): " << e.what() << Phrases::End;
} }
} }
} }
@ -112,10 +131,12 @@ void Session::received(boost::system::error_code ec, size_t bytesTransferred)
// discard password; secrets are expected to be read on the immediate call of the route // discard password; secrets are expected to be read on the immediate call of the route
try { try {
m_secrets.clearPassword(); if (m_secrets) {
m_secrets.close(); m_secrets->clearPassword();
m_secrets->close();
}
} catch (const std::ios_base::failure &e) { } catch (const std::ios_base::failure &e) {
cerr << Phrases::WarningMessage << "Failed to close password file \"" << m_secrets.path() << "\": " << e.what() << Phrases::End; cerr << Phrases::WarningMessage << "Failed to close password file \"" << m_secrets->path() << "\": " << e.what() << Phrases::End;
} }
return; return;
} }

View File

@ -3,7 +3,7 @@
#include "./typedefs.h" #include "./typedefs.h"
#include <passwordfile/io/passwordfile.h> #include "../global.h"
#include <boost/beast/core.hpp> #include <boost/beast/core.hpp>
#include <boost/beast/http.hpp> #include <boost/beast/http.hpp>
@ -12,15 +12,22 @@
#include <boost/asio/ip/tcp.hpp> #include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp> #include <boost/asio/strand.hpp>
#include <memory>
namespace Io {
class PasswordFile;
}
namespace LibRepoMgr { namespace LibRepoMgr {
struct ServiceSetup; struct ServiceSetup;
namespace WebAPI { namespace WebAPI {
class Session : public std::enable_shared_from_this<Session> { class LIBREPOMGR_EXPORT Session : public std::enable_shared_from_this<Session> {
public: public:
Session(boost::asio::ip::tcp::socket &&socket, ServiceSetup &config); Session(boost::asio::ip::tcp::socket &&socket, ServiceSetup &config);
~Session();
void receive(); void receive();
void respond(std::shared_ptr<Response> &&response); void respond(std::shared_ptr<Response> &&response);
@ -33,7 +40,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(); std::unique_ptr<Io::PasswordFile> &&secrets();
private: private:
boost::asio::ip::tcp::socket m_socket; boost::asio::ip::tcp::socket m_socket;
@ -42,16 +49,9 @@ 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; std::unique_ptr<Io::PasswordFile> m_secrets;
}; };
inline Session::Session(boost::asio::ip::tcp::socket &&socket, ServiceSetup &setup)
: m_socket(std::move(socket))
, m_strand(m_socket.get_executor())
, m_setup(setup)
{
}
inline const Request &Session::request() const inline const Request &Session::request() const
{ {
return m_parser->get(); return m_parser->get();
@ -67,9 +67,9 @@ inline boost::asio::ip::tcp::socket &Session::socket()
return m_socket; return m_socket;
} }
inline Io::PasswordFile &Session::secrets() inline std::unique_ptr<Io::PasswordFile> &&Session::secrets()
{ {
return m_secrets; return std::move(m_secrets);
} }
} // namespace WebAPI } // namespace WebAPI