Pass sudo password and GPG passphrase from encrypted file
Not tested yet; so far just an idea to make everything at least a little more secure
This commit is contained in:
parent
255da5b091
commit
aece080986
|
@ -229,6 +229,7 @@ public:
|
||||||
void streamFile(const WebAPI::Params ¶ms, const std::string &filePath, boost::beast::string_view fileMimeType,
|
void streamFile(const WebAPI::Params ¶ms, const std::string &filePath, boost::beast::string_view fileMimeType,
|
||||||
boost::beast::string_view contentDisposition = boost::beast::string_view());
|
boost::beast::string_view contentDisposition = boost::beast::string_view());
|
||||||
ServiceSetup *setup();
|
ServiceSetup *setup();
|
||||||
|
Io::PasswordFile *secrets();
|
||||||
using ReflectiveRapidJSON::JsonSerializable<BuildAction>::fromJson;
|
using ReflectiveRapidJSON::JsonSerializable<BuildAction>::fromJson;
|
||||||
using ReflectiveRapidJSON::JsonSerializable<BuildAction>::toJson;
|
using ReflectiveRapidJSON::JsonSerializable<BuildAction>::toJson;
|
||||||
using ReflectiveRapidJSON::JsonSerializable<BuildAction>::toJsonDocument;
|
using ReflectiveRapidJSON::JsonSerializable<BuildAction>::toJsonDocument;
|
||||||
|
@ -319,6 +320,11 @@ inline ServiceSetup *BuildAction::setup()
|
||||||
return m_setup;
|
return m_setup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Io::PasswordFile *BuildAction::secrets()
|
||||||
|
{
|
||||||
|
return m_secrets.get();
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Appends the specified arguments to the build action's log but *not* to the overall service log.
|
* \brief Appends the specified arguments to the build action's log but *not* to the overall service log.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -577,6 +577,7 @@ private:
|
||||||
bool needsStaging = false;
|
bool needsStaging = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void readSecrets();
|
||||||
[[nodiscard]] std::string locateGlobalConfigPath(const std::string &chrootDir, std::string_view trailingPath) const;
|
[[nodiscard]] std::string locateGlobalConfigPath(const std::string &chrootDir, std::string_view trailingPath) const;
|
||||||
void makeMakepkgConfigFile(const std::filesystem::path &makepkgConfigPath);
|
void makeMakepkgConfigFile(const std::filesystem::path &makepkgConfigPath);
|
||||||
void makePacmanConfigFile(
|
void makePacmanConfigFile(
|
||||||
|
@ -629,6 +630,9 @@ private:
|
||||||
std::string m_globalPackageCacheDir;
|
std::string m_globalPackageCacheDir;
|
||||||
std::string m_globalTestFilesDir;
|
std::string m_globalTestFilesDir;
|
||||||
std::string m_gpgKey;
|
std::string m_gpgKey;
|
||||||
|
std::string m_gpgPassphrase;
|
||||||
|
std::string m_sudoUser;
|
||||||
|
std::string m_sudoPassword;
|
||||||
std::string m_chrootRootUser;
|
std::string m_chrootRootUser;
|
||||||
boost::filesystem::path m_makePkgPath;
|
boost::filesystem::path m_makePkgPath;
|
||||||
boost::filesystem::path m_makeChrootPkgPath;
|
boost::filesystem::path m_makeChrootPkgPath;
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
#include "../logging.h"
|
#include "../logging.h"
|
||||||
#include "../serversetup.h"
|
#include "../serversetup.h"
|
||||||
|
|
||||||
|
#include <passwordfile/io/entry.h>
|
||||||
|
#include <passwordfile/io/field.h>
|
||||||
|
#include <passwordfile/io/passwordfile.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>
|
||||||
#include <c++utilities/io/ansiescapecodes.h>
|
#include <c++utilities/io/ansiescapecodes.h>
|
||||||
|
@ -286,6 +290,9 @@ void ConductBuild::run()
|
||||||
m_pacmanConfigPath = m_workingDirectory + "/pacman.conf";
|
m_pacmanConfigPath = m_workingDirectory + "/pacman.conf";
|
||||||
m_pacmanStagingConfigPath = m_workingDirectory + "/pacman-staging.conf";
|
m_pacmanStagingConfigPath = m_workingDirectory + "/pacman-staging.conf";
|
||||||
|
|
||||||
|
// read secrets
|
||||||
|
readSecrets();
|
||||||
|
|
||||||
// parse build preparation
|
// parse build preparation
|
||||||
auto errors = ReflectiveRapidJSON::JsonDeserializationErrors();
|
auto errors = ReflectiveRapidJSON::JsonDeserializationErrors();
|
||||||
try {
|
try {
|
||||||
|
@ -483,6 +490,64 @@ void ConductBuild::run()
|
||||||
downloadSourcesAndContinueBuilding();
|
downloadSourcesAndContinueBuilding();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Reads secrets from the encrypted password file.
|
||||||
|
* \remarks The password is supposed to be pre-supplied by the web session that started the build action (or
|
||||||
|
* passed by the previous build action).
|
||||||
|
*/
|
||||||
|
void LibRepoMgr::ConductBuild::readSecrets()
|
||||||
|
{
|
||||||
|
auto *const secretsFile = m_buildAction->secrets();
|
||||||
|
if (!secretsFile) {
|
||||||
|
m_buildAction->log()(Phrases::WarningMessage, "No secrets present all.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!secretsFile->hasRootEntry()) {
|
||||||
|
try {
|
||||||
|
if (!secretsFile->isOpen()) {
|
||||||
|
secretsFile->open(Io::PasswordFileOpenFlags::ReadOnly);
|
||||||
|
}
|
||||||
|
secretsFile->load();
|
||||||
|
secretsFile->close();
|
||||||
|
} catch (const std::runtime_error &e) {
|
||||||
|
const auto note = secretsFile->password().empty() ? " (password was empty)"sv : std::string_view();
|
||||||
|
m_buildAction->log()(
|
||||||
|
Phrases::WarningMessage, "Unable to load secrets from \"", secretsFile->path(), '\"', note, ':', ' ', e.what(), '\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto *const secrets = secretsFile->rootEntry();
|
||||||
|
auto sudoPath = std::list<std::string>{ "build", "sudo" };
|
||||||
|
auto gpgPath = std::list<std::string>{ "build", "gpg" };
|
||||||
|
if (auto sudoEntry = secrets->entryByPath(sudoPath); sudoEntry && sudoEntry->type() == Io::EntryType::Account) {
|
||||||
|
for (auto fields = static_cast<Io::AccountEntry *>(sudoEntry)->fields(); const auto &field : fields) {
|
||||||
|
if (field.name() == "username") {
|
||||||
|
m_sudoUser = field.value();
|
||||||
|
} else if (field.name() == "password") {
|
||||||
|
m_sudoPassword = field.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (auto gpgEntry = secrets->entryByPath(gpgPath); gpgEntry && gpgEntry->type() == Io::EntryType::Account) {
|
||||||
|
for (auto fields = static_cast<Io::AccountEntry *>(gpgEntry)->fields(); const auto &field : fields) {
|
||||||
|
if (field.name() == "key") {
|
||||||
|
m_gpgKey = field.value();
|
||||||
|
} else if (field.name() == "passphrase") {
|
||||||
|
m_gpgPassphrase = field.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_sudoUser.empty() || m_sudoPassword.empty()) {
|
||||||
|
m_buildAction->log()(Phrases::WarningMessage, "No sudo username and password present. Not switching to a dedicated build user.");
|
||||||
|
m_sudoPassword.clear(); // don't write password to stdin if we don't invoke sudo
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!m_gpgKey.empty() && m_gpgPassphrase.empty()) {
|
||||||
|
m_buildAction->log()(Phrases::WarningMessage, "GPG key is prsent but no passphrase. Signing with key assuming no passphrase is required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// \cond
|
/// \cond
|
||||||
static void findPackageExtension(const IniFile::ScopeData &iniScope, const std::string &key, std::string &result)
|
static void findPackageExtension(const IniFile::ScopeData &iniScope, const std::string &key, std::string &result)
|
||||||
{
|
{
|
||||||
|
@ -920,7 +985,7 @@ InvocationResult ConductBuild::invokeMakechrootpkg(
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine options/variables to pass
|
// determine options/variables to pass
|
||||||
std::vector<std::string> makechrootpkgFlags, makepkgFlags;
|
std::vector<std::string> makechrootpkgFlags, makepkgFlags, sudoArgs;
|
||||||
// -> cleanup/upgrade
|
// -> cleanup/upgrade
|
||||||
if (!packageProgress.skipChrootCleanup) {
|
if (!packageProgress.skipChrootCleanup) {
|
||||||
makechrootpkgFlags.emplace_back("-c");
|
makechrootpkgFlags.emplace_back("-c");
|
||||||
|
@ -940,6 +1005,10 @@ InvocationResult ConductBuild::invokeMakechrootpkg(
|
||||||
makechrootpkgFlags.emplace_back(m_globalTestFilesDir + "/:/testfiles");
|
makechrootpkgFlags.emplace_back(m_globalTestFilesDir + "/:/testfiles");
|
||||||
makepkgFlags.emplace_back("TEST_FILE_PATH=/testfiles");
|
makepkgFlags.emplace_back("TEST_FILE_PATH=/testfiles");
|
||||||
}
|
}
|
||||||
|
// -> "sudo …" prefixc to launch build as different user
|
||||||
|
if (!m_sudoUser.empty() && !m_sudoPassword.empty()) {
|
||||||
|
sudoArgs = { "sudo", "--user", m_sudoUser, "--stdin" };
|
||||||
|
}
|
||||||
|
|
||||||
// invoke makecontainerpkg instead if container-flag set
|
// invoke makecontainerpkg instead if container-flag set
|
||||||
if (m_useContainer) {
|
if (m_useContainer) {
|
||||||
|
@ -1008,9 +1077,9 @@ InvocationResult ConductBuild::invokeMakechrootpkg(
|
||||||
locks.reserve(2);
|
locks.reserve(2);
|
||||||
locks.emplace_back(m_setup.locks.acquireToWrite(m_buildAction->log(), chrootDir % '/' + packageProgress.chrootUser));
|
locks.emplace_back(m_setup.locks.acquireToWrite(m_buildAction->log(), chrootDir % '/' + packageProgress.chrootUser));
|
||||||
locks.emplace_back(std::move(chrootLock));
|
locks.emplace_back(std::move(chrootLock));
|
||||||
processSession->launch(boost::process::start_dir(packageProgress.buildDirectory), m_makeChrootPkgPath, makechrootpkgFlags, "-C",
|
processSession->launch(boost::process::start_dir(packageProgress.buildDirectory), m_makeChrootPkgPath, sudoArgs, makechrootpkgFlags, "-C",
|
||||||
m_globalPackageCacheDir, "-r", chrootDir, "-l", packageProgress.chrootUser, packageProgress.makechrootpkgFlags, "--", makepkgFlags,
|
m_globalPackageCacheDir, "-r", chrootDir, "-l", packageProgress.chrootUser, packageProgress.makechrootpkgFlags, "--", makepkgFlags,
|
||||||
packageProgress.makepkgFlags);
|
packageProgress.makepkgFlags, boost::process::std_in < boost::asio::buffer(m_sudoPassword));
|
||||||
return InvocationResult::Ok;
|
return InvocationResult::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1294,8 +1363,12 @@ void ConductBuild::invokeGpg(
|
||||||
// consider the package failed
|
// consider the package failed
|
||||||
signingSession->addResponse(std::move(binaryPackageName));
|
signingSession->addResponse(std::move(binaryPackageName));
|
||||||
});
|
});
|
||||||
processSession->launch(boost::process::start_dir(packageProgress.buildDirectory), m_gpgPath, "--detach-sign", "--yes", "--use-agent",
|
auto pinentryArgs = std::vector<std::string>();
|
||||||
"--no-armor", "-u", m_gpgKey, binaryPackage.fileName);
|
if (!m_gpgPassphrase.empty()) {
|
||||||
|
pinentryArgs = { "--pinentry-mode", "loopback", "--passphrase-fd", "0" };
|
||||||
|
}
|
||||||
|
processSession->launch(boost::process::start_dir(packageProgress.buildDirectory), m_gpgPath, pinentryArgs, "--detach-sign", "--yes",
|
||||||
|
"--use-agent", "--no-armor", "-u", m_gpgKey, binaryPackage.fileName, boost::process::std_in < boost::asio::buffer(m_gpgPassphrase));
|
||||||
m_buildAction->log()(Phrases::InfoMessage, "Signing ", binaryPackage.fileName, '\n');
|
m_buildAction->log()(Phrases::InfoMessage, "Signing ", binaryPackage.fileName, '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue