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_package(passwordfile${CONFIGURATION_PACKAGE_SUFFIX} 5.0.0 REQUIRED)
use_password_file(VISIBILITY PUBLIC)
use_password_file()
# find boost libraries
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),
PerformAdminActions = (1 << 3),
TryAgain = (1 << 4),
AccessSecrets = (1 << 5),
DefaultPermissions = ReadBuildActionsDetails,
};

View File

@ -3,6 +3,8 @@
#include "../webapi/session.h"
#include <passwordfile/io/passwordfile.h>
#include <reflective_rapidjson/binary/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.
* \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()) {
return 0;
@ -269,6 +271,13 @@ LibPkg::StorageID BuildAction::start(ServiceSetup &setup)
status = BuildActionStatus::Running;
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) {
case BuildActionType::Invalid:
resultData = "type is invalid";
@ -366,7 +375,13 @@ LibPkg::StorageID BuildAction::conclude(BuildActionResult result)
const auto followUps = m_setup->building.followUpBuildActions(id);
for (auto &followUpAction : followUps) {
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.

View File

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

View File

@ -15,6 +15,8 @@
#include <reflective_rapidjson/binary/serializable.h>
#include <reflective_rapidjson/json/errorformatting.h>
#include <passwordfile/io/passwordfile.h>
#include <c++utilities/application/argumentparser.h>
#include <c++utilities/conversion/stringbuilder.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
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 {
for (const auto id : buildAction->startAfter) {

View File

@ -8,6 +8,8 @@
#include "../buildactions/buildactionprivate.h"
#include "../buildactions/subprocess.h"
#include <passwordfile/io/passwordfile.h>
#include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/io/ansiescapecodes.h>
#include <c++utilities/io/misc.h>
@ -72,6 +74,7 @@ private:
std::string m_configDbFile, m_buildingDbFile;
ServiceSetup m_setup;
std::unique_ptr<Io::PasswordFile> m_secrets;
std::shared_ptr<BuildAction> m_buildAction;
std::filesystem::path m_workingDir;
double m_timeoutFactor = 0.0;
@ -190,7 +193,7 @@ void BuildActionsTests::resetBuildAction()
void BuildActionsTests::runBuildAction(const char *message, CppUtilities::TimeSpan timeout)
{
resetBuildAction();
m_buildAction->start(m_setup);
m_buildAction->start(m_setup, std::move(m_secrets));
auto &ioc = m_setup.building.ioContext;
ioc.restart();
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
auto buildLock2 = params.setup.building.lockToWrite();
if (startImmediately) {
buildAction->start(params.setup);
buildAction->start(params.setup, params.session.secrets());
} else {
params.setup.building.storeBuildAction(buildAction);
}
@ -385,7 +385,7 @@ static std::vector<std::shared_ptr<BuildAction>> allocateBuildActionIDs(ServiceS
return previousActions;
}
static bool startFirstBuildActions(ServiceSetup &setup, SequencedBuildActions &newActionSequence)
static bool startFirstBuildActions(const Params &params, SequencedBuildActions &newActionSequence)
{
auto handledFirstAction = false;
for (auto &sequencedAction : newActionSequence.actions) {
@ -393,13 +393,13 @@ static bool startFirstBuildActions(ServiceSetup &setup, SequencedBuildActions &n
auto &action = *maybeAction;
handledFirstAction = true;
if (action->isScheduled()) {
action->start(setup);
action->start(params.setup, params.session.secrets());
}
if (!newActionSequence.concurrent) {
return true;
}
} else if (auto *const subSequence = std::get_if<SequencedBuildActions>(&sequencedAction)) {
if (startFirstBuildActions(setup, *subSequence) && !newActionSequence.concurrent) {
if (startFirstBuildActions(params, *subSequence) && !newActionSequence.concurrent) {
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)
if (startNow) {
startFirstBuildActions(params.setup, newActionSequence);
startFirstBuildActions(params, newActionSequence);
}
// add build actions to setup-global list
@ -547,7 +547,7 @@ void postCloneBuildActions(const Params &params, ResponseHandler &&handler)
clone->type = orig->type;
clone->startAfter = orig->startAfter;
if (startImmediately) {
clone->start(params.setup);
clone->start(params.setup, params.session.secrets());
}
params.setup.building.storeBuildAction(std::move(clone));
cloneIds.emplace_back(id);
@ -572,7 +572,7 @@ void postStartBuildActions(const Params &params, ResponseHandler &&handler)
return;
}
for (auto &action : buildActionsSearchResult.actions) {
action->start(params.setup);
action->start(params.setup, params.session.secrets());
}
buildActionsSearchResult.lock = std::monostate{};
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/logfile" }, Route{&Routes::getBuildActionLogFile, UserPermissions::ReadBuildActionsDetails} },
{ { 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/clone" }, Route{&Routes::postCloneBuildActions, UserPermissions::ModifyBuildActions} },
{ { http::verb::post, "/api/v0/build-action/start" }, Route{&Routes::postStartBuildActions, 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 | UserPermissions::AccessSecrets} },
{ { 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/quit" }, Route{&Routes::postQuit, UserPermissions::PerformAdminActions} },
};

View File

@ -7,6 +7,8 @@
#include "../serversetup.h"
#include <passwordfile/io/passwordfile.h>
#include <c++utilities/conversion/stringbuilder.h>
#include <c++utilities/io/ansiescapecodes.h>
#include <c++utilities/io/misc.h>
@ -22,6 +24,17 @@ using namespace CppUtilities::EscapeCodes;
namespace LibRepoMgr {
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()
{
m_parser = make_unique<RequestParser>();
@ -89,13 +102,19 @@ void Session::received(boost::system::error_code ec, size_t bytesTransferred)
return;
}
// 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 {
m_secrets.clear();
m_secrets.setPath(argsToString("secrets/"sv, userAuth.name));
m_secrets.setPassword(userAuth.password.data(), userAuth.password.size());
if (m_secrets) {
m_secrets->clear();
} 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) {
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
try {
m_secrets.clearPassword();
m_secrets.close();
if (m_secrets) {
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;
cerr << Phrases::WarningMessage << "Failed to close password file \"" << m_secrets->path() << "\": " << e.what() << Phrases::End;
}
return;
}

View File

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