diff options
Diffstat (limited to 'src/buildtool/auth/authentication.hpp')
-rw-r--r-- | src/buildtool/auth/authentication.hpp | 269 |
1 files changed, 162 insertions, 107 deletions
diff --git a/src/buildtool/auth/authentication.hpp b/src/buildtool/auth/authentication.hpp index ae45561a..cd4ff86d 100644 --- a/src/buildtool/auth/authentication.hpp +++ b/src/buildtool/auth/authentication.hpp @@ -14,6 +14,7 @@ #ifndef INCLUDED_SRC_BUILDTOOL_AUTH_AUTHENTICATION_HPP #define INCLUDED_SRC_BUILDTOOL_AUTH_AUTHENTICATION_HPP + #include <cstdint> #include <filesystem> #include <fstream> @@ -25,135 +26,189 @@ #include "gsl/gsl" #include "src/buildtool/logging/log_level.hpp" #include "src/buildtool/logging/logger.hpp" -enum class AuthMethod : std::uint8_t { kNONE, kTLS }; +#include "src/utils/cpp/expected.hpp" + +struct Auth final { + struct TLS final { + class Builder; + + // CA certificate bundle + std::string const ca_cert = {}; + // Client-side signed certificate + std::string const client_cert = {}; + // Client-side private key + std::string const client_key = {}; + // Server-side signed certificate + std::string const server_cert = {}; + // Server-side private key + std::string const server_key = {}; + }; -class Auth { + std::variant<std::monostate, TLS> method = {}; +}; + +class Auth::TLS::Builder final { public: - [[nodiscard]] static auto Instance() noexcept -> Auth& { - static Auth instance{}; - return instance; + auto SetCACertificate( + std::optional<std::filesystem::path> cert_file) noexcept -> Builder& { + ca_cert_file_ = std::move(cert_file); + return *this; } - void SetAuthMethod(AuthMethod x) { auth_ = x; } - [[nodiscard]] auto GetAuthMethod() const noexcept -> AuthMethod { - return auth_; + auto SetClientCertificate( + std::optional<std::filesystem::path> cert_file) noexcept -> Builder& { + client_cert_file_ = std::move(cert_file); + return *this; } - class TLS { - public: - [[nodiscard]] static auto Instance() noexcept -> TLS& { - static TLS instance{}; - return instance; - } - - [[nodiscard]] auto CACert() const noexcept -> const std::string& { - return ca_cert_; - } - - [[nodiscard]] auto ClientCert() const noexcept -> const std::string& { - return client_cert_; - } - - [[nodiscard]] auto ClientKey() const noexcept -> const std::string& { - return client_key_; - } - - [[nodiscard]] auto ServerCert() const noexcept -> const std::string& { - return server_cert_; - } + auto SetClientKey(std::optional<std::filesystem::path> key_file) noexcept + -> Builder& { + client_key_file_ = std::move(key_file); + return *this; + } - [[nodiscard]] auto ServerKey() const noexcept -> const std::string& { - return server_key_; - } + auto SetServerCertificate( + std::optional<std::filesystem::path> cert_file) noexcept -> Builder& { + server_cert_file_ = std::move(cert_file); + return *this; + } - [[nodiscard]] auto SetCACertificate( - std::filesystem::path const& cert_file) noexcept -> bool { - return set(cert_file, &ca_cert_); - } + auto SetServerKey(std::optional<std::filesystem::path> key_file) noexcept + -> Builder& { + server_key_file_ = std::move(key_file); + return *this; + } - [[nodiscard]] auto SetClientCertificate( - std::filesystem::path const& cert_file) noexcept -> bool { - return set(cert_file, &client_cert_); + /// \brief Finalize building, validate the entries, and create an Auth with + /// TLS as method. Validation ensures that either both tls_client_cert or + /// tls_client_key are set, or none of the two. + /// \return Auth on success, error string on failure, nullopt if no TLS + /// configuration fields were set. + [[nodiscard]] auto Build() noexcept + -> std::optional<expected<Auth, std::string>> { + // To not duplicate default arguments of Auth::TLS in builder, + // create a default config and copy default arguments from there. + Auth::TLS const default_auth_tls; + bool tls_args_exist = false; + + // Set and validate the CA certification. + // If provided, the CA certificate bundle should of course not be empty. + auto ca_cert = default_auth_tls.ca_cert; + if (ca_cert_file_.has_value()) { + if (auto content = read(*ca_cert_file_)) { + if (content->empty()) { + return unexpected( + std::string("Please provide tls-ca-cert")); + } + ca_cert = *std::move(content); + tls_args_exist = true; + } + else { + return unexpected( + fmt::format("Could not read '{}' CA certificate.", + ca_cert_file_->string())); + } } - [[nodiscard]] auto SetClientKey( - std::filesystem::path const& key_file) noexcept -> bool { - return set(key_file, &client_key_); + // Set and validate the client-side certification. + // To enable mTLS, both tls_client_{certificate,key} must be supplied. + auto client_cert = default_auth_tls.client_cert; + if (client_cert_file_.has_value()) { + if (auto content = read(*client_cert_file_)) { + client_cert = *std::move(content); + tls_args_exist = true; + } + else { + return unexpected( + fmt::format("Could not read '{}' client certificate.", + client_cert_file_->string())); + } } - - [[nodiscard]] auto SetServerCertificate( - std::filesystem::path const& cert_file) noexcept -> bool { - return set(cert_file, &server_cert_); + auto client_key = default_auth_tls.client_key; + if (client_key_file_.has_value()) { + if (auto content = read(*client_key_file_)) { + client_key = *std::move(content); + tls_args_exist = true; + } + else { + return unexpected(fmt::format("Could not read '{}' client key.", + client_key_file_->string())); + } } - - [[nodiscard]] auto SetServerKey( - std::filesystem::path const& key_file) noexcept -> bool { - return set(key_file, &server_key_); + if (client_cert.empty() != client_key.empty()) { + std::string error = client_cert.empty() + ? "Please also provide tls-client-cert" + : "Please also provide tls-client-key"; + return unexpected(std::move(error)); } - // must be called after the parsing of cmd line arguments - // we ensure that either both tls_client_cert or tls_client_key are set - // or none of the two. - [[nodiscard]] auto Validate() const noexcept -> bool { - if (CACert().empty()) { - Logger::Log(LogLevel::Error, "Please provide tls-ca-cert"); - return false; - } - // to enable mTLS, both tls_client_{certificate,key} must be - // supplied - if (ClientCert().empty() && not(ClientKey().empty())) { - Logger::Log(LogLevel::Error, - "Please also provide tls-client-cert"); - return false; + // Set and validate the server-side certification. + // To enable mTLS, both tls_server_{certificate,key} must be supplied. + auto server_cert = default_auth_tls.server_cert; + if (server_cert_file_.has_value()) { + if (auto content = read(*server_cert_file_)) { + server_cert = *std::move(content); + tls_args_exist = true; } - if (not(ClientCert().empty()) && ClientKey().empty()) { - Logger::Log(LogLevel::Error, - "Please also provide tls-client-key"); - return false; + else { + return unexpected( + fmt::format("Could not read '{}' server certificate.", + server_cert_file_->string())); } - - // to enable mTLS, both tls_server_{certificate,key} must be - // supplied - if (ServerCert().empty() && not(ServerKey().empty())) { - Logger::Log(LogLevel::Error, - "Please also provide tls-server-cert"); - return false; + } + auto server_key = default_auth_tls.server_key; + if (server_key_file_.has_value()) { + if (auto content = read(*server_key_file_)) { + server_key = *std::move(content); + tls_args_exist = true; } - if (not(ServerCert().empty()) && ServerKey().empty()) { - Logger::Log(LogLevel::Error, - "Please also provide tls-server-key"); - return false; + else { + return unexpected(fmt::format("Could not read '{}' server key.", + server_key_file_->string())); } - return true; + } + if (server_cert.empty() != server_key.empty()) { + std::string error = server_cert.empty() + ? "Please also provide tls-server-cert" + : "Please also provide tls-server-key"; + return unexpected(std::move(error)); } - private: - std::string ca_cert_; - std::string client_cert_; - std::string client_key_; - std::string server_cert_; - std::string server_key_; - // auxiliary function to set the content of the members of this class - [[nodiscard]] static auto set( - std::filesystem::path const& x, - gsl::not_null<std::string*> const& member) noexcept -> bool { - Auth::Instance().SetAuthMethod(AuthMethod::kTLS); - try { - // if the file does not exist, it will throw an exception - auto file = std::filesystem::canonical(x); - std::ifstream cert{file}; - std::string tmp((std::istreambuf_iterator<char>(cert)), - std::istreambuf_iterator<char>()); - *member = std::move(tmp); - } catch (std::exception const& e) { - Logger::Log(LogLevel::Error, e.what()); - return false; - } - return true; + // If no TLS arguments were ever set, there is nothing to build. + if (not tls_args_exist) { + return std::nullopt; } - }; + + // Return an authentication configuration with mTLS enabled. + return Auth{.method = Auth::TLS{.ca_cert = std::move(ca_cert), + .client_cert = std::move(client_cert), + .client_key = std::move(client_key), + .server_cert = std::move(server_cert), + .server_key = std::move(server_key)}}; + } private: - AuthMethod auth_{AuthMethod::kNONE}; + std::optional<std::filesystem::path> ca_cert_file_; + std::optional<std::filesystem::path> client_cert_file_; + std::optional<std::filesystem::path> client_key_file_; + std::optional<std::filesystem::path> server_cert_file_; + std::optional<std::filesystem::path> server_key_file_; + + /// \brief Auxiliary function to read the content of certification files. + [[nodiscard]] static auto read(std::filesystem::path const& x) noexcept + -> std::optional<std::string> { + try { + // if the file does not exist, it will throw an exception + auto file = std::filesystem::canonical(x); + std::ifstream cert{file}; + std::string tmp((std::istreambuf_iterator<char>(cert)), + std::istreambuf_iterator<char>()); + return tmp; + } catch (std::exception const& e) { + Logger::Log(LogLevel::Error, e.what()); + } + return std::nullopt; + } }; -#endif + +#endif // INCLUDED_SRC_BUILDTOOL_AUTH_AUTHENTICATION_HPP |