1
0
mirror of https://github.com/apple/foundationdb.git synced 2025-05-28 10:52:03 +08:00

Implement certificate refreshing

This commit is contained in:
Alex Miller 2020-03-05 17:25:33 -08:00
parent f657ca069e
commit 2d95a1e64d
3 changed files with 107 additions and 146 deletions

@ -38,6 +38,10 @@
#include "flow/Profiler.h"
#include "flow/ProtocolVersion.h"
#include "flow/TLSConfig.actor.h"
#include "flow/genericactors.actor.h"
// See the comment in TLSConfig.actor.h for the explanation of why this module breaking include was done.
#include "fdbrpc/IAsyncFile.h"
#ifdef WIN32
#include <mmsystem.h>
@ -157,16 +161,12 @@ public:
ASIOReactor reactor;
#ifndef TLS_DISABLED
boost::asio::ssl::context sslContext;
AsyncVar<Reference<ReferencedObject<boost::asio::ssl::context>>> sslContextVar;
#endif
TLSConfig tlsConfig;
std::string tlsPassword;
Future<Void> backgroundCertRefresh;
bool tlsInitialized;
std::string get_password() const {
return tlsPassword;
}
INetworkConnections *network; // initially this, but can be changed
int64_t tsc_begin, tsc_end;
@ -505,13 +505,13 @@ public:
closeSocket();
}
explicit SSLConnection( boost::asio::io_service& io_service, boost::asio::ssl::context& context )
: id(nondeterministicRandom()->randomUniqueID()), socket(io_service), ssl_sock(socket, context)
explicit SSLConnection( boost::asio::io_service& io_service, Reference<ReferencedObject<boost::asio::ssl::context>> context )
: id(nondeterministicRandom()->randomUniqueID()), socket(io_service), ssl_sock(socket, context->mutate()), sslContext(context)
{
}
// This is not part of the IConnection interface, because it is wrapped by INetwork::connect()
ACTOR static Future<Reference<IConnection>> connect( boost::asio::io_service* ios, boost::asio::ssl::context* context, NetworkAddress addr ) {
ACTOR static Future<Reference<IConnection>> connect( boost::asio::io_service* ios, Reference<ReferencedObject<boost::asio::ssl::context>> context, NetworkAddress addr ) {
std::pair<IPAddress,uint16_t> peerIP = std::make_pair(addr.ip, addr.port);
auto iter(g_network->networkInfo.serverTLSConnectionThrottler.find(peerIP));
if(iter != g_network->networkInfo.serverTLSConnectionThrottler.end()) {
@ -526,7 +526,7 @@ public:
}
}
state Reference<SSLConnection> self( new SSLConnection(*ios, *context) );
state Reference<SSLConnection> self( new SSLConnection(*ios, context) );
self->peer_address = addr;
try {
@ -729,6 +729,7 @@ private:
tcp::socket socket;
ssl_socket ssl_sock;
NetworkAddress peer_address;
Reference<ReferencedObject<boost::asio::ssl::context>> sslContext;
struct SendBufferIterator {
typedef boost::asio::const_buffer value_type;
@ -789,11 +790,11 @@ class SSLListener : public IListener, ReferenceCounted<SSLListener> {
boost::asio::io_context& io_service;
NetworkAddress listenAddress;
tcp::acceptor acceptor;
boost::asio::ssl::context* context;
AsyncVar<Reference<ReferencedObject<boost::asio::ssl::context>>> *contextVar;
public:
SSLListener( boost::asio::io_context& io_service, boost::asio::ssl::context* context, NetworkAddress listenAddress )
: io_service(io_service), listenAddress(listenAddress), acceptor( io_service, tcpEndpoint( listenAddress ) ), context(context)
SSLListener( boost::asio::io_context& io_service, AsyncVar<Reference<ReferencedObject<boost::asio::ssl::context>>>* contextVar, NetworkAddress listenAddress )
: io_service(io_service), listenAddress(listenAddress), acceptor( io_service, tcpEndpoint( listenAddress ) ), contextVar(contextVar)
{
platform::setCloseOnExec(acceptor.native_handle());
}
@ -810,7 +811,7 @@ public:
private:
ACTOR static Future<Reference<IConnection>> doAccept( SSLListener* self ) {
state Reference<SSLConnection> conn( new SSLConnection( self->io_service, *self->context) );
state Reference<SSLConnection> conn( new SSLConnection( self->io_service, self->contextVar->get() ) );
state tcp::acceptor::endpoint_type peer_endpoint;
try {
BindPromise p("N2_AcceptError", UID());
@ -862,7 +863,7 @@ Net2::Net2(const TLSConfig& tlsConfig, bool useThreadPool, bool useMetrics)
tlsInitialized(false),
tlsConfig(tlsConfig)
#ifndef TLS_DISABLED
,sslContext(boost::asio::ssl::context(boost::asio::ssl::context::tls))
,sslContextVar({ReferencedObject<boost::asio::ssl::context>::from(boost::asio::ssl::context(boost::asio::ssl::context::tls))})
#endif
{
@ -889,103 +890,92 @@ Net2::Net2(const TLSConfig& tlsConfig, bool useThreadPool, bool useMetrics)
}
/*
ACTOR static Future<Void> watchFileForChanges( std::string filename, AsyncVar<Standalone<StringRef>> *contents_var ) {
void ConfigureSSLContext( const LoadedTLSConfig& loaded, boost::asio::ssl::context* context ) {
context->set_options(boost::asio::ssl::context::default_workarounds);
context->set_verify_mode(boost::asio::ssl::context::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert);
if (loaded.isTLSEnabled()) {
Reference<TLSPolicy> tlsPolicy = Reference<TLSPolicy>(new TLSPolicy(loaded.getEndpointType()));
tlsPolicy->set_verify_peers({ loaded.getVerifyPeers() });
context->set_verify_callback([policy=tlsPolicy](bool preverified, boost::asio::ssl::verify_context& ctx) {
return policy->verify_peer(preverified, ctx.native_handle());
});
} else {
context->set_verify_callback(boost::bind(&insecurely_always_accept, _1, _2));
}
context->set_password_callback(
[password=loaded.getPassword()](size_t, boost::asio::ssl::context::password_purpose) {
return password;
});
const std::string& certBytes = loaded.getCertificateBytes();
if ( certBytes.size() ) {
context->use_certificate_chain(boost::asio::buffer(certBytes.data(), certBytes.size()));
}
const std::string& CABytes = loaded.getCABytes();
if ( CABytes.size() ) {
context->add_certificate_authority(boost::asio::buffer(CABytes.data(), CABytes.size()));
}
const std::string& keyBytes = loaded.getKeyBytes();
if (keyBytes.size()) {
context->use_private_key(boost::asio::buffer(keyBytes.data(), keyBytes.size()), boost::asio::ssl::context::pem);
}
}
ACTOR static Future<Void> watchFileForChanges( std::string filename, AsyncTrigger* fileChanged ) {
if (filename == "") {
return Never();
}
state std::time_t lastModTime = wait(IAsyncFileSystem::filesystem()->lastWriteTime(filename));
loop {
wait(delay(FLOW_KNOBS->TLS_CERT_REFRESH_DELAY_SECONDS));
std::time_t modtime = wait(IAsyncFileSystem::filesystem()->lastWriteTime(filename));
if (lastModTime != modtime) {
lastModTime = modtime;
ErrorOr<Standalone<StringRef>> contents = wait(readEntireFile(filename));
if (contents.present()) {
contents_var->set(contents.get());
}
fileChanged->trigger();
}
}
}
ACTOR static Future<Void> reloadConfigurationOnChange( TLSOptions::PolicyInfo *pci, Reference<ITLSPlugin> plugin, AsyncVar<Reference<ITLSPolicy>> *realVerifyPeersPolicy, AsyncVar<Reference<ITLSPolicy>> *realNoVerifyPeersPolicy ) {
if (FLOW_KNOBS->TLS_CERT_REFRESH_DELAY_SECONDS <= 0) {
return Void();
return Void();
}
loop {
// Early in bootup, the filesystem might not be initialized yet. Wait until it is.
if (IAsyncFileSystem::filesystem() != nullptr) {
break;
}
wait(delay(1.0));
}
state int mismatches = 0;
state AsyncVar<Standalone<StringRef>> ca_var;
state AsyncVar<Standalone<StringRef>> key_var;
state AsyncVar<Standalone<StringRef>> cert_var;
state std::vector<Future<Void>> lifetimes;
if (!pci->ca_path.empty()) lifetimes.push_back(watchFileForChanges(pci->ca_path, &ca_var));
if (!pci->key_path.empty()) lifetimes.push_back(watchFileForChanges(pci->key_path, &key_var));
if (!pci->cert_path.empty()) lifetimes.push_back(watchFileForChanges(pci->cert_path, &cert_var));
loop {
state Future<Void> ca_changed = ca_var.onChange();
state Future<Void> key_changed = key_var.onChange();
state Future<Void> cert_changed = cert_var.onChange();
wait( ca_changed || key_changed || cert_changed );
if (ca_changed.isReady()) {
TraceEvent(SevInfo, "TLSRefreshCAChanged").detail("path", pci->ca_path).detail("length", ca_var.get().size());
pci->ca_contents = ca_var.get();
}
if (key_changed.isReady()) {
TraceEvent(SevInfo, "TLSRefreshKeyChanged").detail("path", pci->key_path).detail("length", key_var.get().size());
pci->key_contents = key_var.get();
}
if (cert_changed.isReady()) {
TraceEvent(SevInfo, "TLSRefreshCertChanged").detail("path", pci->cert_path).detail("length", cert_var.get().size());
pci->cert_contents = cert_var.get();
}
bool rc = true;
Reference<ITLSPolicy> verifypeers = Reference<ITLSPolicy>(plugin->create_policy());
Reference<ITLSPolicy> noverifypeers = Reference<ITLSPolicy>(plugin->create_policy());
loop {
// Don't actually loop. We're just using loop/break as a `goto err`.
// This loop always ends with an unconditional break.
rc = verifypeers->set_ca_data(pci->ca_contents.begin(), pci->ca_contents.size());
if (!rc) break;
rc = verifypeers->set_key_data(pci->key_contents.begin(), pci->key_contents.size(), pci->keyPassword.c_str());
if (!rc) break;
rc = verifypeers->set_cert_data(pci->cert_contents.begin(), pci->cert_contents.size());
if (!rc) break;
{
std::unique_ptr<const uint8_t *[]> verify_peers_arr(new const uint8_t*[pci->verify_peers.size()]);
std::unique_ptr<int[]> verify_peers_len(new int[pci->verify_peers.size()]);
for (int i = 0; i < pci->verify_peers.size(); i++) {
verify_peers_arr[i] = (const uint8_t *)&pci->verify_peers[i][0];
verify_peers_len[i] = pci->verify_peers[i].size();
}
rc = verifypeers->set_verify_peers(pci->verify_peers.size(), verify_peers_arr.get(), verify_peers_len.get());
if (!rc) break;
}
rc = noverifypeers->set_ca_data(pci->ca_contents.begin(), pci->ca_contents.size());
if (!rc) break;
rc = noverifypeers->set_key_data(pci->key_contents.begin(), pci->key_contents.size(), pci->keyPassword.c_str());
if (!rc) break;
rc = noverifypeers->set_cert_data(pci->cert_contents.begin(), pci->cert_contents.size());
if (!rc) break;
break;
}
ACTOR static Future<Void> reloadCertificatesOnChange( TLSConfig config, AsyncVar<Reference<ReferencedObject<boost::asio::ssl::context>>>* contextVar ) {
if (FLOW_KNOBS->TLS_CERT_REFRESH_DELAY_SECONDS <= 0) {
return Void();
}
loop {
// Early in bootup, the filesystem might not be initialized yet. Wait until it is.
if (IAsyncFileSystem::filesystem() != nullptr) {
break;
}
wait(delay(1.0));
}
state int mismatches = 0;
state AsyncTrigger fileChanged;
state std::vector<Future<Void>> lifetimes;
lifetimes.push_back(watchFileForChanges(config.getCertificatePathSync(), &fileChanged));
lifetimes.push_back(watchFileForChanges(config.getKeyPathSync(), &fileChanged));
lifetimes.push_back(watchFileForChanges(config.getCAPathSync(), &fileChanged));
loop {
wait( fileChanged.onTrigger() );
TraceEvent("TLSCertificateRefreshBegin");
if (rc) {
TraceEvent(SevInfo, "TLSCertificateRefreshSucceeded");
realVerifyPeersPolicy->set(verifypeers);
realNoVerifyPeersPolicy->set(noverifypeers);
mismatches = 0;
} else {
// Some files didn't match up, they should in the future, and we'll retry then.
mismatches++;
TraceEvent(SevWarn, "TLSCertificateRefreshMismatch").detail("mismatches", mismatches);
}
}
try {
LoadedTLSConfig loaded = wait( config.loadAsync() );
boost::asio::ssl::context context(boost::asio::ssl::context::tls);
ConfigureSSLContext(loaded, &context);
TraceEvent(SevInfo, "TLSCertificateRefreshSucceeded");
mismatches = 0;
contextVar->set(ReferencedObject<boost::asio::ssl::context>::from(std::move(context)));
} catch (Error &e) {
// Some files didn't match up, they should in the future, and we'll retry then.
mismatches++;
TraceEvent(SevWarn, "TLSCertificateRefreshMismatch").detail("mismatches", mismatches);
}
}
}
*/
void Net2::initTLS() {
if(tlsInitialized) {
@ -993,39 +983,10 @@ void Net2::initTLS() {
}
#ifndef TLS_DISABLED
try {
LoadedTLSConfig loaded = tlsConfig.loadSync();
sslContext.set_options(boost::asio::ssl::context::default_workarounds);
sslContext.set_verify_mode(boost::asio::ssl::context::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert);
if (loaded.isTLSEnabled()) {
Reference<TLSPolicy> tlsPolicy = Reference<TLSPolicy>(new TLSPolicy(loaded.getEndpointType()));
tlsPolicy->set_verify_peers({ loaded.getVerifyPeers() });
sslContext.set_verify_callback([policy=tlsPolicy](bool preverified, boost::asio::ssl::verify_context& ctx) {
return policy->verify_peer(preverified, ctx.native_handle());
});
} else {
sslContext.set_verify_callback(boost::bind(&insecurely_always_accept, _1, _2));
}
tlsPassword = loaded.getPassword();
sslContext.set_password_callback(std::bind(&Net2::get_password, this));
const std::string& certBytes = loaded.getCertificateBytes();
if ( certBytes.size() ) {
sslContext.use_certificate_chain(boost::asio::buffer(certBytes.data(), certBytes.size()));
}
const std::string& CABytes = loaded.getCABytes();
if ( CABytes.size() ) {
sslContext.add_certificate_authority(boost::asio::buffer(CABytes.data(), CABytes.size()));
}
const std::string& keyBytes = loaded.getKeyBytes();
if (keyBytes.size()) {
sslContext.use_private_key(boost::asio::buffer(keyBytes.data(), keyBytes.size()), boost::asio::ssl::context::pem);
}
boost::asio::ssl::context newContext(boost::asio::ssl::context::tls);
ConfigureSSLContext( tlsConfig.loadSync(), &newContext );
sslContextVar.set(ReferencedObject<boost::asio::ssl::context>::from(std::move(newContext)));
backgroundCertRefresh = reloadCertificatesOnChange( tlsConfig, &sslContextVar );
} catch(boost::system::system_error e) {
TraceEvent("Net2TLSInitError").detail("Message", e.what());
throw tls_error();
@ -1392,7 +1353,7 @@ Future< Reference<IConnection> > Net2::connect( NetworkAddress toAddr, std::stri
#ifndef TLS_DISABLED
initTLS();
if ( toAddr.isTLS() ) {
return SSLConnection::connect(&this->reactor.ios, &this->sslContext, toAddr);
return SSLConnection::connect(&this->reactor.ios, this->sslContextVar.get(), toAddr);
}
#endif
@ -1472,7 +1433,7 @@ Reference<IListener> Net2::listen( NetworkAddress localAddr ) {
#ifndef TLS_DISABLED
initTLS();
if ( localAddr.isTLS() ) {
return Reference<IListener>(new SSLListener( reactor.ios, &this->sslContext, localAddr ));
return Reference<IListener>(new SSLListener( reactor.ios, &this->sslContextVar, localAddr ));
}
#endif
return Reference<IListener>( new Listener( reactor.ios, localAddr ) );

@ -194,7 +194,6 @@ public:
return loadAsync(this);
}
PRIVATE_EXCEPT_FOR_TLSCONFIG_CPP:
// Return the explicitly set path.
// If one was not set, return the path from the environment.
// (Cert and Key only) If neither exist, check for fdb.pem in cwd
@ -206,6 +205,7 @@ PRIVATE_EXCEPT_FOR_TLSCONFIG_CPP:
std::string getKeyPathSync() const;
std::string getCAPathSync() const;
PRIVATE_EXCEPT_FOR_TLSCONFIG_CPP:
ACTOR static Future<LoadedTLSConfig> loadAsync(const TLSConfig* self);
template <typename T>
friend class LoadAsyncActorState;
@ -217,15 +217,6 @@ PRIVATE_EXCEPT_FOR_TLSCONFIG_CPP:
TLSEndpointType endpointType = TLSEndpointType::UNSET;
};
namespace boost {
namespace asio {
namespace ssl {
struct context;
}
}
}
void ConfigureSSLContext( boost::asio::ssl::context *context, const LoadedTLSConfig& config );
class TLSPolicy : ReferenceCounted<TLSPolicy> {
public:

@ -652,6 +652,7 @@ class ReferencedObject : NonCopyable, public ReferenceCounted<ReferencedObject<V
public:
ReferencedObject() : value() {}
ReferencedObject(V const& v) : value(v) {}
ReferencedObject(V&& v) : value(std::move(v)) {}
ReferencedObject(ReferencedObject&& r) : value(std::move(r.value)) {}
void operator=(ReferencedObject&& r) {
@ -662,7 +663,7 @@ class ReferencedObject : NonCopyable, public ReferenceCounted<ReferencedObject<V
return value;
}
V& mutate() const {
V& mutate() {
return value;
}
@ -670,10 +671,18 @@ class ReferencedObject : NonCopyable, public ReferenceCounted<ReferencedObject<V
value = v;
}
void set(V&& v) {
value = std::move(v);
}
static Reference<ReferencedObject<V>> from(V const& v) {
return Reference<ReferencedObject<V>>(new ReferencedObject<V>(v));
}
static Reference<ReferencedObject<V>> from(V&& v) {
return Reference<ReferencedObject<V>>(new ReferencedObject<V>(std::move(v)));
}
private:
V value;
};