From c2f7ead468d5e65c57e7ecb49d7fbba4254c46b7 Mon Sep 17 00:00:00 2001 From: Paul Cristian Sarbu Date: Tue, 2 Jul 2024 16:47:13 +0200 Subject: Replace the Auth and Auth::TLS singletons Use a builder pattern for creation and validation, in a manner that allows also other authentication methods to be added in the future besides the current TLS/SSL. The main Auth instances are built early and then passed by not_null const pointers, to avoid passing temporaries, replacing the previous Auth::TLS instances passed by simple nullable const pointers. Where needed, these passed Auth instances are also stored, by const ref. Tests also build Auth instances as needed, either with the default 'no certification' or from the test environment arguments. --- src/buildtool/auth/authentication.hpp | 269 ++++++++++++++++++++-------------- 1 file changed, 162 insertions(+), 107 deletions(-) (limited to 'src/buildtool/auth/authentication.hpp') 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 #include #include @@ -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 method = {}; +}; + +class Auth::TLS::Builder final { public: - [[nodiscard]] static auto Instance() noexcept -> Auth& { - static Auth instance{}; - return instance; + auto SetCACertificate( + std::optional 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 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 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 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 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> { + // 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 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(cert)), - std::istreambuf_iterator()); - *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 ca_cert_file_; + std::optional client_cert_file_; + std::optional client_key_file_; + std::optional server_cert_file_; + std::optional 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 { + 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(cert)), + std::istreambuf_iterator()); + return tmp; + } catch (std::exception const& e) { + Logger::Log(LogLevel::Error, e.what()); + } + return std::nullopt; + } }; -#endif + +#endif // INCLUDED_SRC_BUILDTOOL_AUTH_AUTHENTICATION_HPP -- cgit v1.2.3