From aece080986a269abd28a2cbaa2c8c899a0b524ad Mon Sep 17 00:00:00 2001 From: Martchus Date: Tue, 12 Jul 2022 00:14:08 +0200 Subject: [PATCH] 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 --- librepomgr/buildactions/buildaction.h | 6 ++ librepomgr/buildactions/buildactionprivate.h | 4 + librepomgr/buildactions/conductbuild.cpp | 83 ++++++++++++++++++-- 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/librepomgr/buildactions/buildaction.h b/librepomgr/buildactions/buildaction.h index 07b179e..a9bbf3c 100644 --- a/librepomgr/buildactions/buildaction.h +++ b/librepomgr/buildactions/buildaction.h @@ -229,6 +229,7 @@ public: void streamFile(const WebAPI::Params ¶ms, const std::string &filePath, boost::beast::string_view fileMimeType, boost::beast::string_view contentDisposition = boost::beast::string_view()); ServiceSetup *setup(); + Io::PasswordFile *secrets(); using ReflectiveRapidJSON::JsonSerializable::fromJson; using ReflectiveRapidJSON::JsonSerializable::toJson; using ReflectiveRapidJSON::JsonSerializable::toJsonDocument; @@ -319,6 +320,11 @@ inline ServiceSetup *BuildAction::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. */ diff --git a/librepomgr/buildactions/buildactionprivate.h b/librepomgr/buildactions/buildactionprivate.h index a2c09b8..ab73409 100644 --- a/librepomgr/buildactions/buildactionprivate.h +++ b/librepomgr/buildactions/buildactionprivate.h @@ -577,6 +577,7 @@ private: bool needsStaging = false; }; + void readSecrets(); [[nodiscard]] std::string locateGlobalConfigPath(const std::string &chrootDir, std::string_view trailingPath) const; void makeMakepkgConfigFile(const std::filesystem::path &makepkgConfigPath); void makePacmanConfigFile( @@ -629,6 +630,9 @@ private: std::string m_globalPackageCacheDir; std::string m_globalTestFilesDir; std::string m_gpgKey; + std::string m_gpgPassphrase; + std::string m_sudoUser; + std::string m_sudoPassword; std::string m_chrootRootUser; boost::filesystem::path m_makePkgPath; boost::filesystem::path m_makeChrootPkgPath; diff --git a/librepomgr/buildactions/conductbuild.cpp b/librepomgr/buildactions/conductbuild.cpp index 948053b..ba3ee6d 100644 --- a/librepomgr/buildactions/conductbuild.cpp +++ b/librepomgr/buildactions/conductbuild.cpp @@ -6,6 +6,10 @@ #include "../logging.h" #include "../serversetup.h" +#include +#include +#include + #include #include #include @@ -286,6 +290,9 @@ void ConductBuild::run() m_pacmanConfigPath = m_workingDirectory + "/pacman.conf"; m_pacmanStagingConfigPath = m_workingDirectory + "/pacman-staging.conf"; + // read secrets + readSecrets(); + // parse build preparation auto errors = ReflectiveRapidJSON::JsonDeserializationErrors(); try { @@ -483,6 +490,64 @@ void ConductBuild::run() 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{ "build", "sudo" }; + auto gpgPath = std::list{ "build", "gpg" }; + if (auto sudoEntry = secrets->entryByPath(sudoPath); sudoEntry && sudoEntry->type() == Io::EntryType::Account) { + for (auto fields = static_cast(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(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 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 - std::vector makechrootpkgFlags, makepkgFlags; + std::vector makechrootpkgFlags, makepkgFlags, sudoArgs; // -> cleanup/upgrade if (!packageProgress.skipChrootCleanup) { makechrootpkgFlags.emplace_back("-c"); @@ -940,6 +1005,10 @@ InvocationResult ConductBuild::invokeMakechrootpkg( makechrootpkgFlags.emplace_back(m_globalTestFilesDir + "/:/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 if (m_useContainer) { @@ -1008,9 +1077,9 @@ InvocationResult ConductBuild::invokeMakechrootpkg( locks.reserve(2); locks.emplace_back(m_setup.locks.acquireToWrite(m_buildAction->log(), chrootDir % '/' + packageProgress.chrootUser)); 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, - packageProgress.makepkgFlags); + packageProgress.makepkgFlags, boost::process::std_in < boost::asio::buffer(m_sudoPassword)); return InvocationResult::Ok; } @@ -1294,8 +1363,12 @@ void ConductBuild::invokeGpg( // consider the package failed signingSession->addResponse(std::move(binaryPackageName)); }); - processSession->launch(boost::process::start_dir(packageProgress.buildDirectory), m_gpgPath, "--detach-sign", "--yes", "--use-agent", - "--no-armor", "-u", m_gpgKey, binaryPackage.fileName); + auto pinentryArgs = std::vector(); + 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'); }