Repository: kudu Updated Branches: refs/heads/master a9137c0d2 -> 000cf8286
client: support exporting/importing authentication data This adds the C++ side of being able to export and import tokens. This will be useful for Impala in particular -- the front-end can acquire a token and pass it as part of the plan fragment descriptor to the backends, so that the backends don't need to use Kerberos to authenticate. Change-Id: I846c47feab9a7be0dce625079193146bfc2dac7b Reviewed-on: http://gerrit.cloudera.org:8080/6086 Tested-by: Kudu Jenkins Reviewed-by: Todd Lipcon <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/kudu/repo Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/000cf828 Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/000cf828 Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/000cf828 Branch: refs/heads/master Commit: 000cf8286890fa023dc39bf96bd8a461c4e77f74 Parents: a9137c0 Author: Todd Lipcon <[email protected]> Authored: Mon Feb 20 14:28:57 2017 -0800 Committer: Todd Lipcon <[email protected]> Committed: Thu Feb 23 02:51:07 2017 +0000 ---------------------------------------------------------------------- src/kudu/client/client-test.cc | 21 +++++++- src/kudu/client/client.cc | 68 ++++++++++++++++++++++++-- src/kudu/client/client.h | 20 ++++++++ src/kudu/client/client_builder-internal.h | 1 + src/kudu/security/tls_context.cc | 25 +++++++++- src/kudu/security/tls_context.h | 5 ++ 6 files changed, 135 insertions(+), 5 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/kudu/blob/000cf828/src/kudu/client/client-test.cc ---------------------------------------------------------------------- diff --git a/src/kudu/client/client-test.cc b/src/kudu/client/client-test.cc index 2b0f339..bc57d6d 100644 --- a/src/kudu/client/client-test.cc +++ b/src/kudu/client/client-test.cc @@ -4652,11 +4652,30 @@ TEST_F(ClientTest, TestConnectToClusterCompatibility) { } // Test that, when the client connects to a cluster, that it gets the relevant -// certificate authority and authentication token from the master. +// certificate authority and authentication token from the master. Also checks that +// the data can be exported and re-imported into a new client. TEST_F(ClientTest, TestGetSecurityInfoFromMaster) { // Client is already connected when the test starts. ASSERT_TRUE(client_->data_->messenger_->authn_token() != boost::none); ASSERT_EQ(1, client_->data_->messenger_->tls_context().trusted_cert_count_for_tests()); + + string authn_creds; + ASSERT_OK(client_->ExportAuthenticationCredentials(&authn_creds)); + + // Creating a new client by importing the authentication data should result in the + // having the same token. + shared_ptr<KuduClient> new_client; + ASSERT_OK(KuduClientBuilder() + .add_master_server_addr(cluster_->mini_master()->bound_rpc_addr().ToString()) + .import_authentication_credentials(authn_creds) + .Build(&new_client)); + ASSERT_EQ(client_->data_->messenger_->authn_token()->ShortDebugString(), + new_client->data_->messenger_->authn_token()->ShortDebugString()); + + // The new client should yield the same authentication data as the original. + string new_authn_creds; + ASSERT_OK(new_client->ExportAuthenticationCredentials(&new_authn_creds)); + ASSERT_EQ(authn_creds, new_authn_creds); } struct ServiceUnavailableRetryParams { http://git-wip-us.apache.org/repos/asf/kudu/blob/000cf828/src/kudu/client/client.cc ---------------------------------------------------------------------- diff --git a/src/kudu/client/client.cc b/src/kudu/client/client.cc index 720d6ef..b356227 100644 --- a/src/kudu/client/client.cc +++ b/src/kudu/client/client.cc @@ -60,7 +60,9 @@ #include "kudu/rpc/messenger.h" #include "kudu/rpc/request_tracker.h" #include "kudu/rpc/sasl_common.h" +#include "kudu/security/cert.h" #include "kudu/security/openssl_util.h" +#include "kudu/security/tls_context.h" #include "kudu/util/init.h" #include "kudu/util/logging.h" #include "kudu/util/net/dns_resolver.h" @@ -233,15 +235,53 @@ KuduClientBuilder& KuduClientBuilder::default_rpc_timeout(const MonoDelta& timeo return *this; } +KuduClientBuilder& KuduClientBuilder::import_authentication_credentials(string authn_creds) { + data_->authn_creds_ = std::move(authn_creds); + return *this; +} + +namespace { +Status ImportAuthnCredsToMessenger(const string& authn_creds, + Messenger* messenger) { + AuthenticationCredentialsPB pb; + if (!pb.ParseFromString(authn_creds)) { + return Status::InvalidArgument("invalid authentication data"); + } + if (pb.has_authn_token()) { + const auto& tok = pb.authn_token(); + if (!tok.has_token_data() || + !tok.has_signature() || + !tok.has_signing_key_seq_num()) { + return Status::InvalidArgument("invalid authentication token"); + } + messenger->set_authn_token(tok); + } + for (const string& cert_der : pb.ca_cert_ders()) { + security::Cert cert; + RETURN_NOT_OK_PREPEND(cert.FromString(cert_der, security::DataFormat::DER), + "could not import CA cert"); + RETURN_NOT_OK_PREPEND(messenger->mutable_tls_context()->AddTrustedCertificate(cert), + "could not trust CA cert"); + } + return Status::OK(); +} +} // anonymous namespace + Status KuduClientBuilder::Build(shared_ptr<KuduClient>* client) { RETURN_NOT_OK(CheckCPUFlags()); - shared_ptr<KuduClient> c(new KuduClient()); - // Init messenger. MessengerBuilder builder("client"); - RETURN_NOT_OK(builder.Build(&c->data_->messenger_)); + std::shared_ptr<Messenger> messenger; + RETURN_NOT_OK(builder.Build(&messenger)); + // Parse and import the provided authn data, if any. + if (!data_->authn_creds_.empty()) { + RETURN_NOT_OK(ImportAuthnCredsToMessenger(data_->authn_creds_, messenger.get())); + } + + shared_ptr<KuduClient> c(new KuduClient()); + c->data_->messenger_ = std::move(messenger); c->data_->master_server_addrs_ = data_->master_server_addrs_; c->data_->default_admin_operation_timeout_ = data_->default_admin_operation_timeout_; c->data_->default_rpc_timeout_ = data_->default_rpc_timeout_; @@ -488,6 +528,28 @@ void KuduClient::SetLatestObservedTimestamp(uint64_t ht_timestamp) { data_->UpdateLatestObservedTimestamp(ht_timestamp); } +Status KuduClient::ExportAuthenticationCredentials(string* authn_creds) const { + AuthenticationCredentialsPB pb; + + boost::optional<security::SignedTokenPB> tok = data_->messenger_->authn_token(); + if (tok) { + pb.mutable_authn_token()->CopyFrom(*tok); + } + + vector<string> cert_ders; + RETURN_NOT_OK_PREPEND(data_->messenger_->tls_context().DumpTrustedCerts(&cert_ders), + "could not export trusted certs"); + for (auto& der : cert_ders) { + pb.add_ca_cert_ders()->assign(std::move(der)); + } + + if (!pb.SerializeToString(authn_creds)) { + return Status::RuntimeError("could not serialize authentication data"); + } + + return Status::OK(); +} + //////////////////////////////////////////////////////////// // KuduTableCreator //////////////////////////////////////////////////////////// http://git-wip-us.apache.org/repos/asf/kudu/blob/000cf828/src/kudu/client/client.h ---------------------------------------------------------------------- diff --git a/src/kudu/client/client.h b/src/kudu/client/client.h index f8e0c73..d8f2610 100644 --- a/src/kudu/client/client.h +++ b/src/kudu/client/client.h @@ -224,6 +224,14 @@ class KUDU_EXPORT KuduClientBuilder { /// @return Reference to the updated object. KuduClientBuilder& default_rpc_timeout(const MonoDelta& timeout); + /// Import serialized authentication credentials from another client. + /// + /// @param [in] authn_creds + /// The serialized authentication credentials, provided by a call to + /// @c KuduClient.ExportAuthenticationCredentials in the C++ client or + /// @c KuduClient#exportAuthenticationCredentials in the Java client. + KuduClientBuilder& import_authentication_credentials(std::string authn_creds); + /// Create a client object. /// /// @note KuduClients objects are shared amongst multiple threads and, @@ -477,6 +485,18 @@ class KUDU_EXPORT KuduClient : public sp::enable_shared_from_this<KuduClient> { /// Timestamp encoded in HybridTime format. void SetLatestObservedTimestamp(uint64_t ht_timestamp); + /// Export the current authentication credentials from this client. This includes + /// the necessary credentials to authenticate to the cluster, as well as to + /// authenticate the cluster to the client. + /// + /// The resulting binary string may be passed into a new C++ client via the + /// @c KuduClientBuilder::import_authentication_credentials method, or into a new + /// Java client via @c KuduClient#importAuthenticationCredentials. + /// + /// @param [out] authn_creds + /// The resulting binary authentication credsentials. + Status ExportAuthenticationCredentials(std::string* authn_creds) const; + private: class KUDU_NO_EXPORT Data; http://git-wip-us.apache.org/repos/asf/kudu/blob/000cf828/src/kudu/client/client_builder-internal.h ---------------------------------------------------------------------- diff --git a/src/kudu/client/client_builder-internal.h b/src/kudu/client/client_builder-internal.h index 3136b79..070c244 100644 --- a/src/kudu/client/client_builder-internal.h +++ b/src/kudu/client/client_builder-internal.h @@ -34,6 +34,7 @@ class KuduClientBuilder::Data { std::vector<std::string> master_server_addrs_; MonoDelta default_admin_operation_timeout_; MonoDelta default_rpc_timeout_; + std::string authn_creds_; DISALLOW_COPY_AND_ASSIGN(Data); }; http://git-wip-us.apache.org/repos/asf/kudu/blob/000cf828/src/kudu/security/tls_context.cc ---------------------------------------------------------------------- diff --git a/src/kudu/security/tls_context.cc b/src/kudu/security/tls_context.cc index 6106d9e..be298a4 100644 --- a/src/kudu/security/tls_context.cc +++ b/src/kudu/security/tls_context.cc @@ -18,6 +18,7 @@ #include "kudu/security/tls_context.h" #include <string> +#include <vector> #include <boost/algorithm/string/predicate.hpp> #include <gflags/gflags.h> @@ -35,6 +36,7 @@ #include "kudu/security/tls_handshake.h" #include "kudu/util/flag_tags.h" #include "kudu/util/net/net_util.h" +#include "kudu/util/scoped_cleanup.h" #include "kudu/util/status.h" #include "kudu/util/user.h" @@ -236,6 +238,28 @@ Status TlsContext::AddTrustedCertificate(const Cert& cert) { return Status::OK(); } +Status TlsContext::DumpTrustedCerts(vector<string>* cert_ders) const { + vector<string> ret; + auto* cert_store = SSL_CTX_get_cert_store(ctx_.get()); + + CRYPTO_w_lock(CRYPTO_LOCK_X509_STORE); + auto unlock = MakeScopedCleanup([&]() { + CRYPTO_w_unlock(CRYPTO_LOCK_X509_STORE); + }); + for (int i = 0; i < sk_X509_OBJECT_num(cert_store->objs); i++) { + X509_OBJECT* obj = sk_X509_OBJECT_value(cert_store->objs, i); + if (obj->type != X509_LU_X509) continue; + Cert c; + c.AdoptAndAddRefRawData(obj->data.x509); + string der; + RETURN_NOT_OK(c.ToString(&der, DataFormat::DER)); + ret.emplace_back(std::move(der)); + } + + cert_ders->swap(ret); + return Status::OK(); +} + namespace { Status SetCertAttributes(CertRequestGenerator::Config* config) { RETURN_NOT_OK_PREPEND(GetFQDN(&config->cn), "could not determine FQDN for CSR"); @@ -263,7 +287,6 @@ Status SetCertAttributes(CertRequestGenerator::Config* config) { Status TlsContext::GenerateSelfSignedCertAndKey() { CertRequestGenerator::Config config; RETURN_NOT_OK(SetCertAttributes(&config)); - // Step 1: generate the private key to be self signed. PrivateKey key; RETURN_NOT_OK_PREPEND(GeneratePrivateKey(FLAGS_ipki_server_key_size, http://git-wip-us.apache.org/repos/asf/kudu/blob/000cf828/src/kudu/security/tls_context.h ---------------------------------------------------------------------- diff --git a/src/kudu/security/tls_context.h b/src/kudu/security/tls_context.h index f92199d..27b9baa 100644 --- a/src/kudu/security/tls_context.h +++ b/src/kudu/security/tls_context.h @@ -19,6 +19,7 @@ #include <functional> #include <string> +#include <vector> #include <boost/optional.hpp> @@ -101,6 +102,10 @@ class TlsContext { // If this cert has already been marked as trusted, this has no effect. Status AddTrustedCertificate(const Cert& cert); + // Dump all of the certs that are currently trusted by this context, in DER + // form, into 'cert_ders'. + Status DumpTrustedCerts(std::vector<std::string>* cert_ders) const; + // Uses 'cert' and 'key' as the cert and key for use with TLS connections. // // Checks that the CA that issued the signature on 'cert' is already trusted
