security: generate certs on the tserver, sign them on the master This adds a bit of plumbing for the self-hosted PKI:
* Servers (both TS and Master) have a new ServerCertManager instance which generate a private key on startup. They also generate a CSR and adopt a signed cert once provided. This is also a convenient place to stash the set of CA certs and plumb them through to the SSL library, though that isn't implemented yet. * Similarly, the master has a MasterCertAuthority instance which generates a key and self-signed CA cert on startup. It can then sign certs provided by other servers. This may change a bit in the future as the CA cert will have to be loaded from the system tablet if it's available, rather than generated on startup. * When the TS heartbeats, it checks if the cert manager has a signed cert yet. If not, it sends the CSR to the master in DER format. * If the master gets a heartbeat with a CSR, it signs it and returns the signed cert in the heartbeat response. The tablet server then adopts this as its cert. A number of items are left as follow-ons. I noted them with "TODO(PKI)" so that they'll be easy to grep for before we call this feature done. In particular: * Currently the master doesn't yet sign its own cert. This is going to have some interaction with the storage of certs in the catalog table, so want to wait until that code is integrated before figuring out where to plug this in. * The built-in PKI stuff should have a flag to disable it. Again I wasn't sure the best place to put it for now, and it's nice to get the test coverage of this new code all the time. We can add this flag at the same point when we add the appropriate flags to configure your own PKI. * Various other questions and vague thoughts that we can address as we go. Note that this doesn't add any actual functionality, since the resulting certs aren't actually attached to the RPC system in any way. Change-Id: I3eb8ab4edc17e2fa1a54e0123a06dabc59a0489b Reviewed-on: http://gerrit.cloudera.org:8080/5766 Reviewed-by: Dan Burkert <[email protected]> Tested-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/1ee00311 Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/1ee00311 Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/1ee00311 Branch: refs/heads/master Commit: 1ee003113ce32fa1379e7e0656516f41b839dcd3 Parents: 5d12bdb Author: Todd Lipcon <[email protected]> Authored: Sun Jan 22 16:07:42 2017 -0800 Committer: Todd Lipcon <[email protected]> Committed: Thu Jan 26 21:44:36 2017 +0000 ---------------------------------------------------------------------- src/kudu/integration-tests/registration-test.cc | 14 +- src/kudu/master/CMakeLists.txt | 16 ++- src/kudu/master/master.cc | 9 ++ src/kudu/master/master.h | 6 +- src/kudu/master/master.proto | 8 ++ src/kudu/master/master_cert_authority.cc | 128 +++++++++++++++++++ src/kudu/master/master_cert_authority.h | 75 +++++++++++ src/kudu/master/master_service.cc | 16 +++ src/kudu/security/CMakeLists.txt | 1 + src/kudu/security/server_cert_manager.cc | 115 +++++++++++++++++ src/kudu/security/server_cert_manager.h | 94 ++++++++++++++ src/kudu/server/server_base.cc | 6 + src/kudu/server/server_base.h | 13 +- src/kudu/tserver/CMakeLists.txt | 1 + src/kudu/tserver/heartbeater.cc | 20 +++ 15 files changed, 511 insertions(+), 11 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/integration-tests/registration-test.cc ---------------------------------------------------------------------- diff --git a/src/kudu/integration-tests/registration-test.cc b/src/kudu/integration-tests/registration-test.cc index 12ea54a..52a0272 100644 --- a/src/kudu/integration-tests/registration-test.cc +++ b/src/kudu/integration-tests/registration-test.cc @@ -25,11 +25,12 @@ #include "kudu/fs/fs_manager.h" #include "kudu/gutil/gscoped_ptr.h" #include "kudu/integration-tests/mini_cluster.h" -#include "kudu/master/mini_master.h" +#include "kudu/master/master-test-util.h" #include "kudu/master/master.h" #include "kudu/master/master.pb.h" -#include "kudu/master/master-test-util.h" +#include "kudu/master/mini_master.h" #include "kudu/master/ts_descriptor.h" +#include "kudu/security/server_cert_manager.h" #include "kudu/tserver/mini_tablet_server.h" #include "kudu/tserver/tablet_server.h" #include "kudu/util/curl_util.h" @@ -198,4 +199,13 @@ TEST_F(RegistrationTest, TestTabletReports) { // the TS, and verifies that the master notices the issue. } +// Check that after the tablet server registers, it gets a signed cert +// from the master. +TEST_F(RegistrationTest, TestTSGetsSignedX509Certificate) { + MiniTabletServer* ts = cluster_->mini_tablet_server(0); + AssertEventually([&](){ + ASSERT_TRUE(ts->server()->cert_manager()->has_signed_cert()); + }, MonoDelta::FromSeconds(10)); +} + } // namespace kudu http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/master/CMakeLists.txt ---------------------------------------------------------------------- diff --git a/src/kudu/master/CMakeLists.txt b/src/kudu/master/CMakeLists.txt index f97140d..3d3e18d 100644 --- a/src/kudu/master/CMakeLists.txt +++ b/src/kudu/master/CMakeLists.txt @@ -35,6 +35,7 @@ ADD_EXPORTABLE_LIBRARY(master_proto set(MASTER_SRCS catalog_manager.cc master.cc + master_cert_authority.cc master_options.cc master_service.cc master-path-handlers.cc @@ -46,17 +47,18 @@ set(MASTER_SRCS add_library(master ${MASTER_SRCS}) target_link_libraries(master + gutil + krpc kudu_common - tablet + kudu_util + master_proto + rpc_header_proto + security server_common server_process - krpc - gutil - kudu_util + tablet tserver - tserver_service_proto - master_proto - rpc_header_proto) + tserver_service_proto) set(MASTER_RPC_SRCS master_rpc.cc) http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/master/master.cc ---------------------------------------------------------------------- diff --git a/src/kudu/master/master.cc b/src/kudu/master/master.cc index 8d592ce..93873fc 100644 --- a/src/kudu/master/master.cc +++ b/src/kudu/master/master.cc @@ -28,6 +28,7 @@ #include "kudu/common/wire_protocol.h" #include "kudu/gutil/strings/substitute.h" #include "kudu/master/catalog_manager.h" +#include "kudu/master/master_cert_authority.h" #include "kudu/master/master_service.h" #include "kudu/master/master.proxy.h" #include "kudu/master/master-path-handlers.h" @@ -94,6 +95,14 @@ Status Master::Init() { RETURN_NOT_OK(ServerBase::Init()); + // TODO(PKI): the CA should not be initialized if built-in PKI is disabled + // by some flag. + cert_authority_.reset(new MasterCertAuthority(fs_manager_->uuid())); + // TODO(PKI): master should sign its own cert, but it has to be done + // after the catalog manager is loaded. Not clear the right place to + // put it, so punting on it at the moment. + RETURN_NOT_OK(cert_authority_->Init()); + RETURN_NOT_OK(path_handlers_->Register(web_server_.get())); state_ = kInitialized; http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/master/master.h ---------------------------------------------------------------------- diff --git a/src/kudu/master/master.h b/src/kudu/master/master.h index 753bea8..0c1fceb 100644 --- a/src/kudu/master/master.h +++ b/src/kudu/master/master.h @@ -47,8 +47,9 @@ class ServicePool; namespace master { class CatalogManager; -class TSManager; +class MasterCertAuthority; class MasterPathHandlers; +class TSManager; class Master : public server::ServerBase { public: @@ -74,6 +75,8 @@ class Master : public server::ServerBase { std::string ToString() const; + MasterCertAuthority* cert_authority() { return cert_authority_.get(); } + TSManager* ts_manager() { return ts_manager_.get(); } CatalogManager* catalog_manager() { return catalog_manager_.get(); } @@ -118,6 +121,7 @@ class Master : public server::ServerBase { MasterState state_; + std::unique_ptr<MasterCertAuthority> cert_authority_; gscoped_ptr<TSManager> ts_manager_; gscoped_ptr<CatalogManager> catalog_manager_; gscoped_ptr<MasterPathHandlers> path_handlers_; http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/master/master.proto ---------------------------------------------------------------------- diff --git a/src/kudu/master/master.proto b/src/kudu/master/master.proto index 25672ef..e17e73c 100644 --- a/src/kudu/master/master.proto +++ b/src/kudu/master/master.proto @@ -251,6 +251,10 @@ message TSHeartbeatRequestPB { // The number of tablets that are BOOTSTRAPPING or RUNNING. // Used by the master to determine load when creating new tablet replicas. optional int32 num_live_tablets = 4; + + // If the tablet server needs its certificate signed, the CSR + // in DER format. + optional bytes csr_der = 5; } message TSHeartbeatResponsePB { @@ -274,6 +278,10 @@ message TSHeartbeatResponsePB { // Specify whether or not the node is the leader master. optional bool leader_master = 6; + + // If the heartbeat request had a CSR, then the successfully + // signed certificate will be returned in DER format. + optional bytes signed_cert_der = 7; } ////////////////////////////// http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/master/master_cert_authority.cc ---------------------------------------------------------------------- diff --git a/src/kudu/master/master_cert_authority.cc b/src/kudu/master/master_cert_authority.cc new file mode 100644 index 0000000..54816eb --- /dev/null +++ b/src/kudu/master/master_cert_authority.cc @@ -0,0 +1,128 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include "kudu/master/master_cert_authority.h" + +#include <gflags/gflags.h> +#include <memory> +#include <string> + +#include "kudu/security/ca/cert_management.h" +#include "kudu/security/openssl_util.h" +#include "kudu/util/flag_tags.h" + +using std::make_shared; +using std::string; + +using kudu::security::Cert; +using kudu::security::CertSignRequest; +using kudu::security::Key; +using kudu::security::ca::CaCertRequestGenerator; +using kudu::security::ca::CertSigner; + +DEFINE_int32(master_ca_rsa_key_length_bits, 2048, + "The number of bits to use for the master's self-signed " + "certificate authority private key."); +TAG_FLAG(master_ca_rsa_key_length_bits, experimental); + +namespace kudu { +namespace master { + +namespace { + +CaCertRequestGenerator::Config PrepareCaConfig(const string& server_uuid) { + // TODO(aserbin): do we actually have to set all these fields given we + // aren't using a web browser to connect? + return { + "US", // country + "CA", // state + "San Francisco", // locality + "ASF", // org + "The Kudu Project", // unit + server_uuid, // uuid + "Self-signed master CA", // comment + {}, // hostnames + {}, // ips + }; +} + +} // anonymous namespace + +MasterCertAuthority::MasterCertAuthority(string server_uuid) + : server_uuid_(std::move(server_uuid)) { +} + +MasterCertAuthority::~MasterCertAuthority() { +} + +Status MasterCertAuthority::Init() { + CHECK(!ca_private_key_); + + // Create a key for the self-signed CA. + auto key = make_shared<Key>(); + RETURN_NOT_OK(GeneratePrivateKey(FLAGS_master_ca_rsa_key_length_bits, + key.get())); + + // Generate a CSR for the CA. + CertSignRequest ca_csr; + { + CaCertRequestGenerator gen(PrepareCaConfig(server_uuid_)); + RETURN_NOT_OK(gen.Init()); + RETURN_NOT_OK(gen.GenerateRequest(*key, &ca_csr)); + } + + // Self-sign the CA's CSR. + auto ca_cert = make_shared<Cert>(); + { + CertSigner ca_signer; + RETURN_NOT_OK(ca_signer.InitForSelfSigning(key)); + RETURN_NOT_OK(ca_signer.Sign(ca_csr, ca_cert.get())); + } + + // Initialize our signer with the new CA. + auto signer = make_shared<CertSigner>(); + RETURN_NOT_OK(signer->Init(ca_cert, key)); + + cert_signer_ = std::move(signer); + ca_cert_ = std::move(ca_cert); + ca_private_key_ = std::move(key); + return Status::OK(); +} + +Status MasterCertAuthority::SignServerCSR(const string& csr_der, string* cert_der) { + CHECK(cert_signer_) << "not initialized"; + + // TODO(PKI): before signing, should we somehow verify the CSR's + // hostname/server_uuid matches what we think is the hostname? can the signer + // modify the CSR to add fields, etc, indicating when/where it was signed? + // maybe useful for debugging. + + CertSignRequest csr; + RETURN_NOT_OK_PREPEND(csr.FromString(csr_der, security::DataFormat::DER), + "could not parse CSR"); + Cert cert; + RETURN_NOT_OK_PREPEND(cert_signer_->Sign(csr, &cert), + "failed to sign cert"); + + RETURN_NOT_OK_PREPEND(cert.ToString(cert_der, security::DataFormat::DER), + "failed to signed cert as DER format"); + return Status::OK(); +} + + +} // namespace master +} // namespace kudu http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/master/master_cert_authority.h ---------------------------------------------------------------------- diff --git a/src/kudu/master/master_cert_authority.h b/src/kudu/master/master_cert_authority.h new file mode 100644 index 0000000..c3430c5 --- /dev/null +++ b/src/kudu/master/master_cert_authority.h @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +#pragma once + +#include "kudu/gutil/macros.h" +#include "kudu/util/status.h" + +#include <memory> +#include <string> + +namespace kudu { + +namespace security { +class Cert; +class Key; +namespace ca { +class CertSigner; +} // namespace ca +} // namespace security + +namespace master { + +// Implements the X509 certificate-authority functionality of the Master. +// +// This is used in the "built-in PKI" mode of operation. The master generates +// its own self-signed CA certificate, and then signs CSRs provided by tablet +// in their heartbeats. +// +// This class is thread-safe after initialization. +class MasterCertAuthority { + public: + explicit MasterCertAuthority(std::string server_uuid); + virtual ~MasterCertAuthority(); + + // Initializes the MasterCertAuthority by generating a new private key + // and self-signed CA certificate. + // + // Must be called exactly once before any other functions. + Status Init(); + + // Sign the given CSR 'csr_der' provided by a server in the cluster. + // + // The CSR should be provided in the DER format. + // The resulting certificate, also in DER format, is returned in 'cert_der'. + // + // REQUIRES: Init() must be called first. + Status SignServerCSR(const std::string& csr_der, std::string* cert_der); + + private: + // The UUID of the master. This is used as a field in the certificate. + const std::string server_uuid_; + + std::shared_ptr<security::ca::CertSigner> cert_signer_; + std::shared_ptr<security::Key> ca_private_key_; + std::shared_ptr<security::Cert> ca_cert_; + + DISALLOW_COPY_AND_ASSIGN(MasterCertAuthority); +}; + +} // namespace master +} // namespace kudu http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/master/master_service.cc ---------------------------------------------------------------------- diff --git a/src/kudu/master/master_service.cc b/src/kudu/master/master_service.cc index 9a83ff5..c6ec327 100644 --- a/src/kudu/master/master_service.cc +++ b/src/kudu/master/master_service.cc @@ -25,6 +25,7 @@ #include "kudu/common/wire_protocol.h" #include "kudu/master/catalog_manager.h" #include "kudu/master/master.h" +#include "kudu/master/master_cert_authority.h" #include "kudu/master/ts_descriptor.h" #include "kudu/master/ts_manager.h" #include "kudu/rpc/rpc_context.h" @@ -140,6 +141,21 @@ void MasterServiceImpl::TSHeartbeat(const TSHeartbeatRequestPB* req, } } + // 6. If the heartbeat has a CSR, sign their cert. + // TODO(PKI): should this be done only by leaders or all masters? + if (req->has_csr_der()) { + string cert; + Status s = server_->cert_authority()->SignServerCSR(req->csr_der(), &cert); + if (!s.ok()) { + rpc->RespondFailure(s.CloneAndPrepend("invalid CSR")); + return; + } + LOG(INFO) << "Signed X509 certificate for tserver " << rpc->requestor_string(); + resp->mutable_signed_cert_der()->swap(cert); + } + + // 7. Send any active CA certs which the TS doesn't have. + rpc->RespondSuccess(); } http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/security/CMakeLists.txt ---------------------------------------------------------------------- diff --git a/src/kudu/security/CMakeLists.txt b/src/kudu/security/CMakeLists.txt index d49f6eb..be2e320 100644 --- a/src/kudu/security/CMakeLists.txt +++ b/src/kudu/security/CMakeLists.txt @@ -34,6 +34,7 @@ set(SECURITY_SRCS init.cc openssl_util.cc ${PORTED_X509_CHECK_HOST_CC} + server_cert_manager.cc tls_context.cc tls_handshake.cc tls_socket.cc) http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/security/server_cert_manager.cc ---------------------------------------------------------------------- diff --git a/src/kudu/security/server_cert_manager.cc b/src/kudu/security/server_cert_manager.cc new file mode 100644 index 0000000..650ae47 --- /dev/null +++ b/src/kudu/security/server_cert_manager.cc @@ -0,0 +1,115 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include "kudu/security/server_cert_manager.h" + +#include <memory> +#include <string> + +#include <boost/optional/optional.hpp> +#include <gflags/gflags.h> + +#include "kudu/util/flag_tags.h" +#include "kudu/security/ca/cert_management.h" +#include "kudu/security/openssl_util.h" + +using std::string; +using std::unique_ptr; + +DEFINE_int32(server_rsa_key_length_bits, 2048, + "The number of bits to use for the server's private RSA key. This is used " + "for TLS connections to and from clients and other servers."); +TAG_FLAG(server_rsa_key_length_bits, experimental); + +namespace kudu { +namespace security { + +using ca::CertRequestGenerator; + +ServerCertManager::ServerCertManager(string server_uuid) + : server_uuid_(std::move(server_uuid)) { +} + +ServerCertManager::~ServerCertManager() { +} + +Status ServerCertManager::Init() { + MutexLock lock(lock_); + CHECK(!key_); + + unique_ptr<Key> key(new Key()); + RETURN_NOT_OK_PREPEND(GeneratePrivateKey(FLAGS_server_rsa_key_length_bits, key.get()), + "could not generate private key"); + + // TODO(aserbin): do these fields actually have to be set? + const CertRequestGenerator::Config config = { + "US", // country + "CA", // state + "San Francisco", // locality + "ASF", // org + "The Kudu Project", // unit + server_uuid_, // uuid + "", // comment + {"localhost"}, // hostnames TODO(PKI): use real hostnames + {"127.0.0.1"}, // ips + }; + + CertRequestGenerator gen(config); + CertSignRequest csr; + RETURN_NOT_OK_PREPEND(gen.Init(), "could not initialize CSR generator"); + RETURN_NOT_OK_PREPEND(gen.GenerateRequest(*key, &csr), "could not generate CSR"); + RETURN_NOT_OK_PREPEND(csr.ToString(&csr_der_, DataFormat::DER), + "unable to output DER format CSR"); + key_.swap(key); + return Status::OK(); +} + + +Status ServerCertManager::AdoptSignedCert(const string& cert_der) { + MutexLock lock(lock_); + // If we already have a cert, then no need to adopt a new one. + // We heartbeat to multiple masters in parallel, and so we might + // get multiple valid signed certs from different masters. + if (signed_cert_) { + return Status::OK(); + } + unique_ptr<Cert> new_cert(new Cert()); + RETURN_NOT_OK_PREPEND(new_cert->FromString(cert_der, DataFormat::DER), + "could not parse DER data"); + + LOG(INFO) << "Adopting new signed X509 certificate"; + signed_cert_ = std::move(new_cert); + // No longer need to get a signed cert, so we can forget our CSR. + csr_der_.clear(); + return Status::OK(); +} + +// Return a new CSR, if this tablet server's cert has not yet been +// signed. If the cert is already signed, returns null. +boost::optional<string> ServerCertManager::GetCSRIfNecessary() const { + MutexLock lock(lock_); + if (signed_cert_) return boost::none; + return csr_der_; +} + +bool ServerCertManager::has_signed_cert() const { + MutexLock lock(lock_); + return signed_cert_ != nullptr; +} + +} // namespace security +} // namespace kudu http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/security/server_cert_manager.h ---------------------------------------------------------------------- diff --git a/src/kudu/security/server_cert_manager.h b/src/kudu/security/server_cert_manager.h new file mode 100644 index 0000000..916bd2d --- /dev/null +++ b/src/kudu/security/server_cert_manager.h @@ -0,0 +1,94 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +#pragma once + +#include <memory> +#include <string> + +#include <boost/optional/optional_fwd.hpp> + +#include "kudu/gutil/macros.h" +#include "kudu/util/mutex.h" +#include "kudu/util/status.h" + +namespace kudu { +namespace security { + +class Key; +class Cert; +class CertSignRequest; + +// Manages the X509 certificate used by a server when the built-in +// PKI infrastructure is enabled. +// +// This generates an X509 certificate for the server, provides a CSR which can be +// transferred to the master, and then stores the signed certificate once provided. +// +// Note that the master, in addition to acting as a CA, also needs its own cert +// (signed by the CA cert) which it uses for its RPC server. This handles the latter. +// +// This class is thread-safe after initialization. +class ServerCertManager { + public: + // Construct a ServertCertManager. + explicit ServerCertManager(std::string server_uuid); + ~ServerCertManager(); + + // Generate a private key and CSR. + // + // This must be called exactly once before any methods below. + Status Init(); + + // Return a new CSR in DER format, if this server's cert has not yet been + // signed. If the cert is already signed, returns boost::none; + boost::optional<std::string> GetCSRIfNecessary() const; + + // Adopt the given signed cert (provided in DER format) as the cert for + // this server. + // + // This has no effect if the instance already has a signed cert. + Status AdoptSignedCert(const std::string& cert_der); + + // TODO(PKI): some code to register a callback when the cert is adopted, + // so that it can set it on the SSLFactory? + + // Return true if we currently have a signed certificate. + // TODO(PKI): should this check validity? + bool has_signed_cert() const; + + private: + const std::string server_uuid_; + + // Protects the below variables. + mutable Mutex lock_; + + // The CSR for this server. If the cert has already been + // signed, set to empty. + std::string csr_der_; + + // The signed cert for this server. Only set once the cert + // has been signed. + std::unique_ptr<Cert> signed_cert_; + + // The keypair associated with this server's cert. + std::unique_ptr<Key> key_; + + DISALLOW_COPY_AND_ASSIGN(ServerCertManager); +}; + +} // namespace security +} // namespace kudu http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/server/server_base.cc ---------------------------------------------------------------------- diff --git a/src/kudu/server/server_base.cc b/src/kudu/server/server_base.cc index 2831855..088250f 100644 --- a/src/kudu/server/server_base.cc +++ b/src/kudu/server/server_base.cc @@ -32,6 +32,7 @@ #include "kudu/gutil/walltime.h" #include "kudu/rpc/messenger.h" #include "kudu/security/init.h" +#include "kudu/security/server_cert_manager.h" #include "kudu/server/default-path-handlers.h" #include "kudu/server/generic_service.h" #include "kudu/server/glog_metrics.h" @@ -185,6 +186,11 @@ Status ServerBase::Init() { } RETURN_NOT_OK_PREPEND(s, "Failed to load FS layout"); + // Initialize the cert manager. + // TODO(PKI): make built-in PKI enabled/disabled based on a flag. + cert_manager_.reset(new security::ServerCertManager(fs_manager_->uuid())); + RETURN_NOT_OK(cert_manager_->Init()); + // Create the Messenger. rpc::MessengerBuilder builder(name_); http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/server/server_base.h ---------------------------------------------------------------------- diff --git a/src/kudu/server/server_base.h b/src/kudu/server/server_base.h index 2ad6978..57cadd2 100644 --- a/src/kudu/server/server_base.h +++ b/src/kudu/server/server_base.h @@ -46,6 +46,10 @@ class Messenger; class ServiceIf; } // namespace rpc +namespace security { +class ServerCertManager; +} // namespace security + namespace server { class Clock; @@ -71,8 +75,10 @@ class ServerBase { FsManager* fs_manager() { return fs_manager_.get(); } + security::ServerCertManager* cert_manager() { return cert_manager_.get(); } + // Return the instance identifier of this server. - // This may not be called until after the server is Initted. + // This may not be called until after the server is Started. const NodeInstancePB& instance_pb() const; const std::shared_ptr<MemTracker>& mem_tracker() const { return mem_tracker_; } @@ -107,6 +113,11 @@ class ServerBase { gscoped_ptr<FsManager> fs_manager_; gscoped_ptr<RpcServer> rpc_server_; gscoped_ptr<Webserver> web_server_; + + // Manager for the SSL certificate associated with this server, if built-in + // PKI is enabled. + std::unique_ptr<security::ServerCertManager> cert_manager_; + std::shared_ptr<rpc::Messenger> messenger_; scoped_refptr<rpc::ResultTracker> result_tracker_; bool is_first_run_; http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/tserver/CMakeLists.txt ---------------------------------------------------------------------- diff --git a/src/kudu/tserver/CMakeLists.txt b/src/kudu/tserver/CMakeLists.txt index 7cb4655..10b88ce 100644 --- a/src/kudu/tserver/CMakeLists.txt +++ b/src/kudu/tserver/CMakeLists.txt @@ -131,6 +131,7 @@ target_link_libraries(tserver log consensus krpc + security server_common server_process tablet) http://git-wip-us.apache.org/repos/asf/kudu/blob/1ee00311/src/kudu/tserver/heartbeater.cc ---------------------------------------------------------------------- diff --git a/src/kudu/tserver/heartbeater.cc b/src/kudu/tserver/heartbeater.cc index e8aca16..5edd83d 100644 --- a/src/kudu/tserver/heartbeater.cc +++ b/src/kudu/tserver/heartbeater.cc @@ -17,6 +17,7 @@ #include "kudu/tserver/heartbeater.h" +#include <boost/optional.hpp> #include <gflags/gflags.h> #include <glog/logging.h> #include <memory> @@ -29,6 +30,7 @@ #include "kudu/master/master.h" #include "kudu/master/master_rpc.h" #include "kudu/master/master.proxy.h" +#include "kudu/security/server_cert_manager.h" #include "kudu/server/webserver.h" #include "kudu/tserver/tablet_server.h" #include "kudu/tserver/tablet_server_options.h" @@ -362,6 +364,17 @@ Status Heartbeater::Thread::DoHeartbeat() { "Unable to set up registration"); } + // Check with the TS cert manager if it has a cert that needs signing. + // if so, send the CSR in the heartbeat for the master to sign. + boost::optional<string> csr = server_->cert_manager()->GetCSRIfNecessary(); + if (csr != boost::none) { + req.mutable_csr_der()->swap(*csr); + VLOG(1) << "Sending a CSR to the master in the next heartbeat"; + } + + // TODO(PKI): send the version number of the latest CA cert which we know about. + // The response should include new CA certs. + if (send_full_tablet_report_) { LOG(INFO) << Substitute( "Master $0 was elected leader, sending a full tablet report...", @@ -406,6 +419,13 @@ Status Heartbeater::Thread::DoHeartbeat() { last_hb_response_.Swap(&resp); + // If we have a new signed certificate from the master, adopt it. + if (last_hb_response_.has_signed_cert_der()) { + RETURN_NOT_OK_PREPEND( + server_->cert_manager()->AdoptSignedCert(last_hb_response_.signed_cert_der()), + "could not adopt master-signed X509 cert"); + } + MarkTabletReportAcknowledged(req.tablet_report()); return Status::OK(); }
