Skip to content

Commit

Permalink
Minimal support for TLS connection from python and C++ clients. (#3948)
Browse files Browse the repository at this point in the history
* Minimal support for SSL connection from python and C++ clients.

* Followup to slack comment from Charles.

* Added arg check.

* Rename use_ssl to use_tls.

* Made the python slightly more pythonic.

* Use TLS for the session channel; allow for target name override.

* Fixed a typo.

* Fix python issues.

* Follow optional convention in the modified file, as discussed over DM with Jianfeng.

* Added generalized options to C++ client.

* Added support for generic options to the python client.
  • Loading branch information
jcferretti authored and devinrsmith committed Jun 14, 2023
1 parent 8e01ac8 commit 6bbe135
Show file tree
Hide file tree
Showing 16 changed files with 466 additions and 132 deletions.
2 changes: 2 additions & 0 deletions cpp-client/deephaven/client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,14 @@ set(ALL_FILES

src/columns.cc
src/expressions.cc
src/client_options.cc
src/client.cc
src/flight.cc

include/public/deephaven/client/columns.h
include/public/deephaven/client/expressions.h
include/public/deephaven/client/client.h
include/public/deephaven/client/client_options.h
include/public/deephaven/client/flight.h
include/public/deephaven/client/utility/arrow_util.h

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#pragma once

#include <chrono>
#include <functional>
#include <future>
#include <memory>
#include <vector>
Expand All @@ -12,6 +13,7 @@
#include <cstdint>
#include <arrow/flight/client.h>

#include "deephaven/client/client_options.h"
#include "deephaven/client/utility/executor.h"
#include "deephaven/dhcore/utility/callbacks.h"
#include "deephaven/proto/ticket.pb.h"
Expand Down Expand Up @@ -90,14 +92,17 @@ class Server : public std::enable_shared_from_this<Server> {
typedef io::deephaven::proto::backplane::script::grpc::StartConsoleResponse StartConsoleResponse;
typedef io::deephaven::proto::backplane::script::grpc::ExecuteCommandResponse ExecuteCommandResponse;

typedef deephaven::client::ClientOptions ClientOptions;
typedef deephaven::client::utility::Executor Executor;

template<typename T>
using SFCallback = deephaven::dhcore::utility::SFCallback<T>;
typedef SFCallback<ExportedTableCreationResponse> EtcCallback;

public:
static std::shared_ptr<Server> createFromTarget(const std::string &target, const std::string &authorizationValue);
static std::shared_ptr<Server> createFromTarget(
const std::string &target,
const ClientOptions &client_options);
Server(const Server &other) = delete;
Server &operator=(const Server &other) = delete;
Server(Private,
Expand All @@ -107,6 +112,7 @@ class Server : public std::enable_shared_from_this<Server> {
std::unique_ptr<TableService::Stub> tableStub,
std::unique_ptr<ConfigService::Stub> configStub,
std::unique_ptr<arrow::flight::FlightClient> flightClient,
ClientOptions::extra_headers_t extraHeaders,
std::string sessionToken,
std::chrono::milliseconds expirationInterval,
std::chrono::system_clock::time_point nextHandshakeTime);
Expand Down Expand Up @@ -220,21 +226,21 @@ class Server : public std::enable_shared_from_this<Server> {
void sendRpc(const TReq &req, std::shared_ptr<SFCallback<TResp>> responseCallback,
TStub *stub, const TPtrToMember &pm);

std::pair<std::string, std::string> getAuthHeader() const;
void forEachHeaderNameAndValue(std::function<
void(const std::string &, const std::string &)> fun);

// TODO: make this private
void setExpirationInterval(std::chrono::milliseconds interval);

private:
static const char *const authorizationKey;
typedef std::unique_ptr<::grpc::ClientAsyncResponseReader<ExportedTableCreationResponse>>
(TableService::Stub::*selectOrUpdateMethod_t)(::grpc::ClientContext *context,
const SelectOrUpdateRequest &request, ::grpc::CompletionQueue *cq);

Ticket selectOrUpdateHelper(Ticket parentTicket, std::vector<std::string> columnSpecs,
std::shared_ptr<EtcCallback> etcCallback, selectOrUpdateMethod_t method);

void addSessionToken(grpc::ClientContext *ctx);

static void processCompletionQueueForever(const std::shared_ptr<Server> &self);
bool processNextCompletionQueueItem();

Expand All @@ -247,6 +253,7 @@ class Server : public std::enable_shared_from_this<Server> {
std::unique_ptr<TableService::Stub> tableStub_;
std::unique_ptr<ConfigService::Stub> configStub_;
std::unique_ptr<arrow::flight::FlightClient> flightClient_;
const ClientOptions::extra_headers_t extraHeaders_;
grpc::CompletionQueue completionQueue_;

std::atomic<int32_t> nextFreeTicketId_;
Expand All @@ -265,7 +272,9 @@ void Server::sendRpc(const TReq &req, std::shared_ptr<SFCallback<TResp>> respons
auto now = std::chrono::system_clock::now();
// Keep this in a unique_ptr at first, for cleanup in case addAuthToken throws an exception.
auto response = std::make_unique<ServerResponseHolder<TResp>>(now, std::move(responseCallback));
addSessionToken(&response->ctx_);
forEachHeaderNameAndValue([&response](const std::string &name, const std::string &value) {
response->ctx_.AddMetadata(name, value);
});
auto rpc = (stub->*pm)(&response->ctx_, req, &completionQueue_);
// It is the responsibility of "processNextCompletionQueueItem" to deallocate the storage pointed
// to by 'response'.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <memory>
#include <string_view>
#include "deephaven/client/columns.h"
#include "deephaven/client/client_options.h"
#include "deephaven/client/expressions.h"
#include "deephaven/dhcore/ticking/ticking.h"
#include "deephaven/dhcore/utility/callbacks.h"
Expand Down Expand Up @@ -152,58 +153,6 @@ class TableHandleManager {
std::shared_ptr<impl::TableHandleManagerImpl> impl_;
};

/**
* The ClientOptions object is intended to be passed to Client::connect(). For convenience, the mutating methods can be
* chained. For example:
* auto client = Client::connect("localhost:10000", ClientOptions().setBasicAuthentication("foo", "bar").setSessionType("groovy")
*/
class ClientOptions {
public:
/*
* Default constructor. Creates a default ClientOptions object with default authentication and Python scripting.
*/
ClientOptions();
/**
* Move constructor
*/
ClientOptions(ClientOptions &&other) noexcept;
/**
* Move assigment operator.
*/
ClientOptions &operator=(ClientOptions &&other) noexcept;
/**
* Destructor
*/
~ClientOptions();

/**
* Modifies the ClientOptions object to set the default authentication scheme.
* @return *this, so that methods can be chained.
*/
ClientOptions &setDefaultAuthentication();
/**
* Modifies the ClientOptions object to set the basic authentication scheme.
* @return *this, so that methods can be chained.
*/
ClientOptions &setBasicAuthentication(const std::string &username, const std::string &password);
/**
* Modifies the ClientOptions object to set a custom authentication scheme.
* @return *this, so that methods can be chained.
*/
ClientOptions &setCustomAuthentication(const std::string &authenticationKey, const std::string &authenticationValue);
/**
* Modifies the ClientOptions object to set the scripting language for the session.
* @param sessionType The scripting language for the session, such as "groovy" or "python".
* @return *this, so that methods can be chained.
*/
ClientOptions &setSessionType(const std::string &sessionType);

private:
std::string authorizationValue_;
std::string sessionType_;

friend class Client;
};

/**
* The main class for interacting with Deephaven. Start here to connect with
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
* Copyright (c) 2016-2022 Deephaven Data Labs and Patent Pending
*/
#pragma once

#include <string>
#include <utility> // std::pair
#include <vector>

namespace deephaven::client {

class Client;

/**
* The ClientOptions object is intended to be passed to Client::connect(). For convenience, the mutating methods can be
* chained.
* @example auto client = Client::connect("localhost:10000", ClientOptions().setBasicAuthentication("foo", "bar").setSessionType("groovy")
*/
class ClientOptions {
public:
typedef std::vector<std::pair<std::string, int>> int_options_t;
typedef std::vector<std::pair<std::string, std::string>> string_options_t;
typedef std::vector<std::pair<std::string, std::string>> extra_headers_t;

/*
* Default constructor. Creates a default ClientOptions object with default authentication and Python scripting.
*/
ClientOptions();
/**
* Move constructor
*/
ClientOptions(ClientOptions &&other) noexcept;
/**
* Move assigment operator.
*/
ClientOptions &operator=(ClientOptions &&other) noexcept;
/**
* Destructor
*/
~ClientOptions();

/**
* Modifies the ClientOptions object to set the default authentication scheme.
* @return *this, so that methods can be chained.
*/
ClientOptions &setDefaultAuthentication();
/**
* Modifies the ClientOptions object to set the basic authentication scheme.
* @return *this, so that methods can be chained.
*/
ClientOptions &setBasicAuthentication(const std::string &username, const std::string &password);
/**
* Modifies the ClientOptions object to set a custom authentication scheme.
* @return *this, so that methods can be chained.
*/
ClientOptions &setCustomAuthentication(const std::string &authenticationKey, const std::string &authenticationValue);
/**
* Modifies the ClientOptions object to set the scripting language for the session.
* @param sessionType The scripting language for the session, such as "groovy" or "python".
* @return *this, so that methods can be chained.
*/
ClientOptions &setSessionType(std::string sessionType);
/**
* Configure whether to set server connections as TLS
*
* @param useTls true if server connections should be TLS/SSL, false for insecure.
* @return *this, to be used for chaining
*/
ClientOptions &setUseTls(bool useTls);
/**
* Sets a PEM-encoded certificate root for server connections. The empty string
* means use system defaults.
*
* @param pem a PEM encoded certificate chain.
* @return *this, to be used for chaining
*/
ClientOptions &setTlsRootCerts(std::string tlsRootCerts);
/**
* Adds an int-valued option for the configuration of the underlying gRPC channels.
* See https://grpc.github.io/grpc/cpp/group__grpc__arg__keys.html for a list of available options.
*
* @example copt.setIntOption("grpc.min_reconnect_backoff_ms", 2000)
* @param opt The option key.
* @param val The option valiue.
* @return *this, to be used for chaining
*/
/**
* Sets a PEM-encoded certificate for the client and use mutual TLS.
* The empty string means don't use mutual TLS.
*
* @param pem a PEM encoded certificate chain, or empty for no mutual TLS.
* @return *this, to be used for chaining
*/
ClientOptions &setClientCertChain(std::string clientCertChain);
/**
* Sets a PEM-encoded private key for the client certificate chain when using
* mutual TLS.
*
* @param pem a PEM encoded private key.
* @return *this, to be used for chaining
*/
ClientOptions &setClientPrivateKey(std::string clientCertChain);
ClientOptions &addIntOption(std::string opt, int val);
/**
* Adds a string-valued option for the configuration of the underlying gRPC channels.
* See https://grpc.github.io/grpc/cpp/group__grpc__arg__keys.html for a list of available options.
*
* @example copt.setStringOption("grpc.target_name_override", "idonthaveadnsforthishost")
* @param opt The option key.
* @param val The option valiue.
* @return *this, to be used for chaining
*/
ClientOptions &addStringOption(std::string opt, std::string val);
/**
* Adds an extra header with a constant name and value to be sent with every outgoing server request.
*
* @param header_name The header name
* @param header_value The header value
* @return *this, to be used for chaining
*/
ClientOptions &addExtraHeader(std::string header_name, std::string header_value);
/**
* Returns the value for the authorization header that will be sent to the server
* on the first request; this value is a function of the
* authentication method selected.
*
* @return A string value for the authorization header
*/
const std::string &authorizationValue() const {
return authorizationValue_;
}

/**
* Returns true if server connections should be configured for TLS/SSL.
*
* @return true if this connection should be TLS/SSL, false for insecure.
*/
bool useTls() const { return useTls_; }
/**
* The PEM-encoded certificate root for server connections, or the empty string
* if using system defaults.
*
* @return A PEM-encoded certificate chain, or empty.
*/
const std::string &tlsRootCerts() const { return tlsRootCerts_; }
/**
* The PEM-encoded certificate chain to use for the client
* when using mutual TLS, or the empty string for no mutual TLS.
*
* @return A PEM-encoded certificate chain, or empty.
*/
const std::string &clientCertChain() const { return clientCertChain_; }
/**
* The PEM-encoded client private key to use for mutual TLS.
*
* @return A PEM-encoded private key, or empty.
*/
const std::string &clientPrivateKey() const { return clientPrivateKey_; }
/**
* Integer-valued channel options set for server connections.
*
* @return A vector of pairs of string option name and integer option value
*/
const int_options_t &intOptions() const { return intOptions_; }
/**
* String-valued channel options set for server connections.
*
* @return A vector of pairs of string option name and string option value
*/
const string_options_t &stringOptions() const { return stringOptions_; }
/**
* Extra headers that should be sent with each outgoing server request.
*
* @return A vector of pairs of string header name and string header value
*/
const extra_headers_t &extraHeaders() const { return extraHeaders_; }

private:
std::string authorizationValue_;
std::string sessionType_;
bool useTls_ = false;
std::string tlsRootCerts_;
std::string clientCertChain_;
std::string clientPrivateKey_;
int_options_t intOptions_;
string_options_t stringOptions_;
extra_headers_t extraHeaders_;

friend class Client;
};

} // namespace deephaven::client
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ class FlightWrapper {
const TableHandle &table) const;

/**
* Add Deephaven authentication headers to Arrow FlightCallOptions.
* Add Deephaven authentication headers, and any other extra headers
* request at session creation, to Arrow FlightCallOptions.
*
* This is a bit of a hack, and is used in the scenario where the caller is rolling
* their own Arrow Flight `DoPut` operation. Example code might look like this:
* @code
Expand All @@ -44,14 +46,14 @@ class FlightWrapper {
* // Empty FlightCallOptions
* arrow::flight::FlightCallOptions options;
* // add Deephaven auth headers to the FlightCallOptions
* wrapper.addAuthHeaders(&options);
* wrapper.addHeaders(&options);
* std::unique_ptr<arrow::flight::FlightStreamWriter> fsw;
* std::unique_ptr<arrow::flight::FlightMetadataReader> fmr;
* auto status = wrapper.flightClient()->DoPut(options, fd, schema, &fsw, &fmr);
* @endcode
* @param options Destination object where the authentication headers should be written.
*/
void addAuthHeaders(arrow::flight::FlightCallOptions *options) const;
void addHeaders(arrow::flight::FlightCallOptions *options) const;

/**
* Gets the underlying FlightClient
Expand Down
Loading

0 comments on commit 6bbe135

Please sign in to comment.