Skip to content

Commit

Permalink
feat: Add tls mtls support for c++ api (#546)
Browse files Browse the repository at this point in the history
  • Loading branch information
ddiakiteaneo committed Sep 2, 2024
2 parents 0085177 + 66d4359 commit fd7db19
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 3 deletions.
2 changes: 1 addition & 1 deletion packages/cpp/ArmoniK.Api.Client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ protobuf_generate(
set_source_files_properties(${PROTO_GENERATED_FILES} PROPERTIES SKIP_UNITY_BUILD_INCLUSION on)
list(APPEND PROTO_GENERATED_FILES ${PROTO_GENERATED_MESSAGES})

target_link_libraries(${PROJECT_NAME} PUBLIC protobuf::libprotobuf gRPC::grpc++_unsecure ArmoniK.Api.Common)
target_link_libraries(${PROJECT_NAME} PUBLIC protobuf::libprotobuf gRPC::grpc++ ArmoniK.Api.Common)
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 14)

setup_options(${PROJECT_NAME})
Expand Down
42 changes: 42 additions & 0 deletions packages/cpp/ArmoniK.Api.Client/header/channel/ChannelFactory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#pragma once

#include "logger/logger.h"
#include "logger/writer.h"
#include "utils/Configuration.h"
#include <grpcpp/channel.h>
#include <grpcpp/security/credentials.h>

namespace armonik {
namespace api {
namespace client {
class ChannelFactory {
public:
/**
* @brief Creates a channel factory from the given configuration
* @param configuration The channel configuration
* @param logger The logger
*/
explicit ChannelFactory(armonik::api::common::utils::Configuration configuration, common::logger::Logger &logger);

/**
* @brief Creates the new gRPC channel
* @return New channel
*/
std::shared_ptr<::grpc::Channel> create_channel();

/**
*
* @return A bool on whether the gRPC channel is secure or not
*/
bool isSecureChannel() const noexcept;

private:
armonik::api::common::logger::LocalLogger logger_;
std::shared_ptr<::grpc::ChannelCredentials> credentials_{nullptr};
std::string endpoint_;
armonik::api::common::utils::Configuration configuration_;
bool is_secure_{false};
};
} // namespace client
} // namespace api
} // namespace armonik
174 changes: 174 additions & 0 deletions packages/cpp/ArmoniK.Api.Client/source/channel/ChannelFactory.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#include "channel/ChannelFactory.h"

#include "exceptions/ArmoniKApiException.h"
#include "options/ControlPlane.h"
#include "utils/ChannelArguments.h"
#include <grpcpp/create_channel.h>
#include <grpcpp/security/credentials.h>
#include <grpcpp/security/tls_credentials_options.h>

#include <fstream>
#include <sstream>
#include <utility>

namespace armonik {
namespace api {
namespace client {

using namespace grpc::experimental;

/**
* In TLS without SSL validation, this certificate is used for the function TlsCredentials options when a root
* certificate is not provided
*/
const std::string root_self_signed = R"(-----BEGIN CERTIFICATE REQUEST-----
MIIEhjCCAm4CAQAwQTELMAkGA1UEBhMCRlIxEzARBgNVBAgMClNvbWUtU3RhdGUx
DjAMBgNVBAcMBVBhcmlzMQ0wCwYDVQQKDARBbmVvMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEA3lBl8so+JRen+tfbrytXMmYAvjt/WquctbkbFIN6prdp
uShiRb6kX9jobcOQCleQ08LBLPPoQ7AemymPxT0dq+YPFw33LgrIBpKe0JWYzujB
Ujj39b1EmKonnsx+C6DL2KSkIf7ayoBNdjDgunWkVC4M6hoJE7XYyZ78HKndfuvL
C4zs3o1EizvSpp+O/IzD/y5pnZEBoxMLCRNB8vD7w7mQMhx+6Amx7KkfCDKLOQO4
/K2x8r4Y65+IvxFMyxUsR1Z5XPVv37u7u2akbh3HlUE+m0xzVOk+BmHFYxm/eEAF
4p1Jt3bZWu03eF4f8tmgN31Rv0uV+BRN7na44inXNnyd+2qczaCI1IQmsy23Vu0A
eX61Gu06ifViJAybbcWll3VQjWqj5XtsN2+yr2bGfZw8fpjGXVWTL0+nZSqZPWSo
IYlXMHjcygWyMJXTMVTTN+fV7dd9s1LFVnpdHFFOtmRzY8FlRRSpOoqG8XQXXsk0
pE9904wHaXcwSEe4KtuzgZgNngRCtT61G6k+onhrGa6UVCKpfvMYtS3NEsMNNYsY
I5Hn7Unj/0xBO6IM5Os6PImWWMk8rLSXC3IdtEAHgShS+/xbh2ZVOveSeMXWaecm
u2RIe5wQa5ZXLr03XtkdMB1pebJbdoFrs0ev/sklk1dZfbX06vJSd8eokM9oIIcC
AwEAAaAAMA0GCSqGSIb3DQEBCwUAA4ICAQCr75dBYjypzqDqQ6TiKWuYO8rq6TIh
pdZHw5ystwvD6sn+tPbc7iNbnvDF6GeTgMdKAuwNz0YJMZq9v39hZzTCyMqRLNlT
TU3kYaTWGHDK0HE2O3pHKppHAc2YbAsSxuS8KMHx0wW0abVHiEeudc/nULJppX1/
ObouzLGSJJwZctXEzk/Ye7bD1sneSqVnrdFD1IOBVQVRGoJznAt7WWxvGk9LPW51
+MybzTilL4rk5+ezA4UCIMrQCDwZcI+UCcKqDajDz+7kn81f1K4g1G6dTh+M8qIV
lx6/Bfy3P6DHF1ww0i/hRQht1O9cyUo3mDZzAq20OsIDvkhjNGma/IEbkZ9z0P5C
/5YwAW+GuwG2GrD016y5OjZVrAG/KIfyS6FLQfgN/ww5Y9tK6vO5XkelED7zNPrq
em1zkId2H0Az5dIC2OpnAg3+NuGrehfIXziiY+8MGIivqI/Rulnv7m2l2vjHi66K
GztDm5ohMdfjitFIfPDFYPMH7KES4vivic8zlq9FJYNp8tUYEBR1wW7W03IJPm6e
pUwvXHPjId/qBjlBixZt2ZqC8X4S95wAfVjtS3O33Zsm4oevwlvywfYIK8nTG5SD
bDCNVTg3w/OQLQQdWUl6FunmYinukBgmqnsJnwgrhzBENbmgbgfOZZWGtG5ODENb
wc+KqiSg9c9iqA==
-----END CERTIFICATE REQUEST-----)";

/**
*
* @param path The path to the file to be read
* @return content of the file as a std::string
*/
std::string read_file(const absl::string_view &path) {
std::ifstream file(path.data(), std::ios::in | std::ios::binary);
if (file.is_open()) {
std::ostringstream sstr;
sstr << file.rdbuf();
return sstr.str();
} else {
return {};
}
}

/**
* @brief Check if it's https connexion
* @param controlPlane The control plane object for the current configuration
* @param endpoint The endpoint
* @return a boolean on wether http or https connexion
*/
bool initialize_protocol_endpoint(const common::options::ControlPlane &controlPlane, std::string &endpoint) {
absl::string_view endpoint_view = controlPlane.getEndpoint();
const auto delim = endpoint_view.find("://");
const auto http_delim = endpoint_view.find("http://");
const auto https_delim = endpoint_view.find("https://");
if ((endpoint_view.find("unix") == 0) ||
(endpoint_view[0] == '/' && endpoint_view.find(':') == absl::string_view::npos)) {
endpoint = {endpoint_view.cbegin(), endpoint_view.cend()};
if (endpoint[0] == '/') {
endpoint.insert(0, "unix://");
} else {
endpoint.insert(0, "unix:");
}
return false;
}
if (https_delim != absl::string_view::npos) {
const auto tmp = endpoint_view.substr(https_delim + 8);
endpoint = {tmp.cbegin(), tmp.cend()};
return true;
} else {
if (http_delim != absl::string_view::npos) {
const auto tmp = endpoint_view.substr(http_delim + 7);
endpoint = {tmp.cbegin(), tmp.cend()};
} else {
endpoint = {endpoint_view.cbegin(), endpoint_view.cend()};
}
return false;
}
}

/**
*
* @param rootCertificate The root certificate to validate the server one against
* @param userPublicPem The client certificate for mTLS
* @param userPrivatePem The client key for mTLS
* @return a pointer to a certificate provider interface
*/
std::shared_ptr<CertificateProviderInterface> create_certificate_provider(const std::string &rootCertificate,
const std::string &userPublicPem,
const std::string &userPrivatePem) {
if (rootCertificate.empty()) {
return std::make_shared<StaticDataCertificateProvider>(
std::vector<IdentityKeyCertPair>{IdentityKeyCertPair{userPrivatePem, userPublicPem}});
} else if (userPrivatePem.empty() || userPublicPem.empty()) {
return std::make_shared<StaticDataCertificateProvider>(rootCertificate);
} else {
return std::make_shared<StaticDataCertificateProvider>(
rootCertificate, std::vector<IdentityKeyCertPair>{IdentityKeyCertPair{userPrivatePem, userPublicPem}});
}
}

std::shared_ptr<grpc::Channel> ChannelFactory::create_channel() {
auto channel = grpc::CreateCustomChannel(endpoint_, credentials_, common::utils::getChannelArguments(configuration_));
logger_.log(common::logger::Level::Debug, "Created new channel ");

return channel;
}

ChannelFactory::ChannelFactory(armonik::api::common::utils::Configuration configuration, common::logger::Logger &logger)
: logger_(logger.local()), configuration_(std::move(configuration)) {
const auto control_plane = configuration_.get_control_plane();
const bool is_https = initialize_protocol_endpoint(control_plane, endpoint_);

auto root_cert_pem = read_file(control_plane.getCaCertPemPath());
auto user_private_pem = read_file(control_plane.getUserKeyPemPath());
auto user_public_pem = read_file(control_plane.getUserCertPemPath());

if (is_https) {
if (!user_private_pem.empty() && !user_public_pem.empty()) {
if (control_plane.isSslValidation()) {
credentials_ = grpc::SslCredentials(grpc::SslCredentialsOptions{
std::move(root_cert_pem), std::move(user_private_pem), std::move(user_public_pem)});
} else {
throw common::exceptions::ArmoniKApiException("mTLS without SSL validation is not supported.");
}
} else {
if (control_plane.isSslValidation()) {
credentials_ = grpc::SslCredentials(grpc::SslCredentialsOptions{std::move(root_cert_pem)});
} else {
TlsChannelCredentialsOptions tls_options;
// Set up TLS credentials options by setting root certificate to random certificate
tls_options.set_certificate_provider(
create_certificate_provider(root_self_signed, user_public_pem, user_private_pem));
// Disable SSL certificate validation by setting verify_server to false
tls_options.set_verify_server_certs(control_plane.isSslValidation());
// Create TLS credentials with the specified options
credentials_ = TlsCredentials(tls_options);
}
}
is_secure_ = true;
} else {
// Create gRPC insecure credentials
credentials_ = grpc::InsecureChannelCredentials();
}
}

bool ChannelFactory::isSecureChannel() const noexcept { return is_secure_; }

} // namespace client
} // namespace api
} // namespace armonik
2 changes: 1 addition & 1 deletion packages/cpp/ArmoniK.Api.Common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ file(MAKE_DIRECTORY ${PROJECT_BUILD_DIR})

add_library(${PROJECT_NAME} ${PROTO_GENERATED_FILES} ${SRC_CLIENT_FILES} ${HEADER_CLIENT_FILES} ${simdjson_SOURCE_DIR}/singleheader/simdjson.cpp ${simdjson_SOURCE_DIR}/singleheader/simdjson.h)

target_link_libraries(${PROJECT_NAME} PUBLIC protobuf::libprotobuf gRPC::grpc++_unsecure)
target_link_libraries(${PROJECT_NAME} PUBLIC protobuf::libprotobuf gRPC::grpc++)
target_compile_definitions(${PROJECT_NAME} PUBLIC FMT_HEADER_ONLY=1)
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 14)

Expand Down
2 changes: 1 addition & 1 deletion packages/cpp/ArmoniK.Api.Tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ add_executable(${PROJECT_NAME} ${SRC_CLIENT_FILES} ${HEADER_CLIENT_FILES})

set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 14)

target_link_libraries(${PROJECT_NAME} PUBLIC protobuf::libprotobuf gRPC::grpc++_unsecure ArmoniK.Api.Client ArmoniK.Api.Common GTest::gtest_main GTest::gmock_main)
target_link_libraries(${PROJECT_NAME} PUBLIC protobuf::libprotobuf gRPC::grpc++ ArmoniK.Api.Client ArmoniK.Api.Common GTest::gtest_main GTest::gmock_main)

if(MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE /W4)
Expand Down

0 comments on commit fd7db19

Please sign in to comment.