forked from facebook/wangle
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: Example code for: - Server using wangle::Acceptor that can speak TLS and plaintext on the same port and refreshes credentials on change. - Client that uses the SSL session cache Reviewed By: anirudhvr Differential Revision: D5720113 fbshipit-source-id: e773aee43726179f14a9fede7e59a93c7825f856
- Loading branch information
1 parent
21e9e7d
commit bd24ccc
Showing
2 changed files
with
348 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
/* | ||
* Copyright 2018-present Facebook, Inc. | ||
* | ||
* Licensed 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 | ||
* | ||
* https://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 <iostream> | ||
#include <folly/init/Init.h> | ||
#include <folly/io/async/AsyncSSLSocket.h> | ||
#include <folly/io/async/SSLOptions.h> | ||
#include <folly/portability/GFlags.h> | ||
#include <folly/ssl/Init.h> | ||
#include <folly/io/async/DelayedDestruction.h> | ||
|
||
#include <wangle/bootstrap/ClientBootstrap.h> | ||
#include <wangle/channel/AsyncSocketHandler.h> | ||
#include <wangle/channel/EventBaseHandler.h> | ||
#include <wangle/client/ssl/SSLSessionPersistentCache.h> | ||
#include <wangle/client/persistence/LRUPersistentCache.h> | ||
#include <wangle/codec/LineBasedFrameDecoder.h> | ||
#include <wangle/codec/StringCodec.h> | ||
|
||
using namespace wangle; | ||
using namespace folly; | ||
|
||
DEFINE_string(ip, "::1", "Ip address to connect to"); | ||
DEFINE_uint32(port, 8080, "Port to connect to"); | ||
DEFINE_bool(ssl, true, "Whether to use SSL"); | ||
DEFINE_string(cache_file, "", "Session cache file"); | ||
DEFINE_string(cert_path, "", "Path to client cert pem"); | ||
DEFINE_string(key_path, "", "Path to client cert key pem"); | ||
DEFINE_string(ca_path, "", "Path to trusted CA file"); | ||
|
||
const std::string SVC_IDENTITY = "test_client"; | ||
|
||
/** | ||
* This is meant to be a simple client that can be configured for SSL or | ||
* plaintext. To test: | ||
* openssl s_server -key <key_path> -cert <cert_path> -accept <port> | ||
* OR: ./server --cert_path <cert_path> --key_path <key_path> | ||
* | ||
* Run ./client and enter text | ||
* | ||
* Test cert and key are available in wangle/ssl/test/certs | ||
*/ | ||
namespace { | ||
typedef Pipeline<folly::IOBufQueue&, std::string> EchoPipeline; | ||
|
||
// the handler for receiving messages back from the server | ||
class EchoHandler : public HandlerAdapter<std::string> { | ||
public: | ||
void read(Context*, std::string msg) override { | ||
std::cout << "received back: " << msg; | ||
} | ||
void readException(Context* ctx, exception_wrapper e) override { | ||
std::cout << exceptionStr(e) << std::endl; | ||
close(ctx); | ||
} | ||
void readEOF(Context* ctx) override { | ||
std::cout << "EOF received :(" << std::endl; | ||
close(ctx); | ||
} | ||
}; | ||
|
||
// chains the handlers together to define the response pipeline | ||
class EchoPipelineFactory : public PipelineFactory<EchoPipeline> { | ||
public: | ||
EchoPipeline::Ptr newPipeline( | ||
std::shared_ptr<AsyncTransportWrapper> sock) override { | ||
auto pipeline = EchoPipeline::create(); | ||
pipeline->addBack(AsyncSocketHandler(sock)); | ||
pipeline->addBack( | ||
EventBaseHandler()); // ensure we can write from any thread | ||
pipeline->addBack(LineBasedFrameDecoder(8192, false)); | ||
pipeline->addBack(StringCodec()); | ||
pipeline->addBack(EchoHandler()); | ||
pipeline->finalize(); | ||
return pipeline; | ||
} | ||
}; | ||
|
||
class EchoClientBootstrap : public ClientBootstrap<EchoPipeline> { | ||
public: | ||
virtual void makePipeline(std::shared_ptr<folly::AsyncSocket> socket) { | ||
auto sslSock = socket->getUnderlyingTransport<AsyncSSLSocket>(); | ||
if (sslSock) { | ||
// service identity ends up being the session cache key | ||
sslSock->setServiceIdentity(SVC_IDENTITY); | ||
} | ||
ClientBootstrap<EchoPipeline>::makePipeline(std::move(socket)); | ||
} | ||
}; | ||
|
||
std::shared_ptr<SSLContext> createSSLContext() { | ||
auto context = std::make_shared<SSLContext>(); | ||
if (!FLAGS_ca_path.empty()) { | ||
context->loadTrustedCertificates(FLAGS_ca_path.c_str()); | ||
// don't do peer name validation | ||
context->authenticate(true, false); | ||
// verify the server cert | ||
context->setVerificationOption(SSLContext::SSLVerifyPeerEnum::VERIFY); | ||
} | ||
if (!FLAGS_cert_path.empty() && !FLAGS_key_path.empty()) { | ||
context->loadCertificate(FLAGS_cert_path.c_str()); | ||
context->loadPrivateKey(FLAGS_key_path.c_str()); | ||
if (!context->isCertKeyPairValid()) { | ||
throw std::runtime_error("Cert and key do not match"); | ||
} | ||
} | ||
folly::ssl::setSignatureAlgorithms<folly::ssl::SSLCommonOptions>(*context); | ||
return context; | ||
} | ||
} // namespace | ||
|
||
int main(int argc, char** argv) { | ||
folly::init(&argc, &argv); | ||
folly::ssl::init(); | ||
|
||
// an in memory ssl session cache used for caching sessions | ||
std::shared_ptr<SSLSessionPersistentCache> cache; | ||
if (!FLAGS_cache_file.empty()) { | ||
cache = std::make_shared<SSLSessionPersistentCache>( | ||
FLAGS_cache_file, 100, std::chrono::seconds(60)); | ||
} | ||
|
||
EchoClientBootstrap client; | ||
client.group(std::make_shared<IOThreadPoolExecutor>(1)); | ||
client.pipelineFactory(std::make_shared<EchoPipelineFactory>()); | ||
|
||
if (FLAGS_ssl) { | ||
auto ctx = createSSLContext(); | ||
// attach the context to the cache | ||
if (cache) { | ||
// service identity acts as the session cache key | ||
wangle::SSLSessionCallbacks::attachCallbacksToContext( | ||
ctx->getSSLCtx(), cache.get()); | ||
auto session = cache->getSSLSession(SVC_IDENTITY); | ||
if (session) { | ||
VLOG(0) << "Reusing session"; | ||
client.sslSession(session.release()); | ||
} | ||
} | ||
client.sslContext(ctx); | ||
} | ||
|
||
SocketAddress addr(FLAGS_ip.c_str(), FLAGS_port); | ||
VLOG(0) << "Connecting"; | ||
auto pipeline = client.connect(addr).get(); | ||
VLOG(0) << "Connected"; | ||
try { | ||
while (true) { | ||
std::string line; | ||
std::getline(std::cin, line); | ||
if (line == "") { | ||
VLOG(0) << "End"; | ||
break; | ||
} | ||
VLOG(0) << "Sending " << line; | ||
pipeline->write(line + "\r\n").get(); | ||
if (line == "bye") { | ||
pipeline->close(); | ||
break; | ||
} | ||
} | ||
} catch (const std::exception& e) { | ||
std::cout << exceptionStr(e) << std::endl; | ||
} | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
/* | ||
* Copyright 2018-present Facebook, Inc. | ||
* | ||
* Licensed 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 | ||
* | ||
* https://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 <folly/init/Init.h> | ||
#include <folly/io/async/AsyncSSLSocket.h> | ||
#include <folly/portability/GFlags.h> | ||
#include <folly/ssl/Init.h> | ||
#include <wangle/acceptor/Acceptor.h> | ||
#include <wangle/bootstrap/ServerBootstrap.h> | ||
#include <wangle/channel/AsyncSocketHandler.h> | ||
#include <wangle/codec/LineBasedFrameDecoder.h> | ||
#include <wangle/codec/StringCodec.h> | ||
#include <wangle/ssl/TLSCredProcessor.h> | ||
|
||
DEFINE_string(cert_path, "", "Path to cert pem"); | ||
DEFINE_string(key_path, "", "Path to cert key"); | ||
DEFINE_string(ca_path, "", "Path to trusted CA file"); | ||
DEFINE_int32(port, 8080, "Listen port"); | ||
DEFINE_string(tickets_path, "", "Path for ticket seeds"); | ||
DEFINE_uint32(num_workers, 2, "Number of worker threads"); | ||
|
||
/** | ||
* This is meant to be a simple server that accepts plaintext and SSL on a | ||
* single port. To test: | ||
* plaintext: nc localhost 8080 | ||
* ssl: openssl s_client -connect localhost:8080 | ||
* | ||
* Test cert and key are available in wangle/ssl/test/certs | ||
*/ | ||
using namespace wangle; | ||
using namespace folly; | ||
|
||
typedef Pipeline<IOBufQueue&, std::string> EchoPipeline; | ||
|
||
namespace { | ||
// the main logic of our echo server; receives a string and writes it straight | ||
// back | ||
class EchoHandler : public HandlerAdapter<std::string> { | ||
public: | ||
void read(Context* ctx, std::string msg) override { | ||
std::cout << "handling " << msg << std::endl; | ||
write(ctx, msg + "\r\n"); | ||
} | ||
}; | ||
|
||
// where we define the chain of handlers for each messeage received | ||
class EchoPipelineFactory : public PipelineFactory<EchoPipeline> { | ||
public: | ||
EchoPipeline::Ptr newPipeline( | ||
std::shared_ptr<AsyncTransportWrapper> sock) override { | ||
auto pipeline = EchoPipeline::create(); | ||
pipeline->addBack(AsyncSocketHandler(sock)); | ||
pipeline->addBack(LineBasedFrameDecoder(8192)); | ||
pipeline->addBack(StringCodec()); | ||
pipeline->addBack(EchoHandler()); | ||
pipeline->finalize(); | ||
return pipeline; | ||
} | ||
}; | ||
|
||
// Init the processor callbacks. It's fine to do this | ||
// even if nothing is being watched | ||
void initCredProcessorCallbacks( | ||
ServerBootstrap<EchoPipeline>& sb, | ||
TLSCredProcessor& processor) { | ||
// set up ticket seed callback | ||
processor.addTicketCallback([&](TLSTicketKeySeeds seeds) { | ||
// update | ||
sb.forEachWorker([&](Acceptor* acceptor) { | ||
if (!acceptor) { | ||
// this condition can happen if the processor triggers before the | ||
// server is ready / listening | ||
return; | ||
} | ||
auto evb = acceptor->getEventBase(); | ||
if (!evb) { | ||
return; | ||
} | ||
evb->runInEventBaseThread([acceptor, seeds] { | ||
acceptor->setTLSTicketSecrets( | ||
seeds.oldSeeds, seeds.currentSeeds, seeds.newSeeds); | ||
}); | ||
}); | ||
}); | ||
|
||
// Reconfigure SSL when we detect cert or CA changes. | ||
processor.addCertCallback([&] { | ||
sb.forEachWorker([&](Acceptor* acceptor) { | ||
if (!acceptor) { | ||
return; | ||
} | ||
auto evb = acceptor->getEventBase(); | ||
if (!evb) { | ||
return; | ||
} | ||
evb->runInEventBaseThread([acceptor] { | ||
acceptor->resetSSLContextConfigs(); | ||
}); | ||
}); | ||
}); | ||
} | ||
} // namespace | ||
|
||
int main(int argc, char** argv) { | ||
folly::init(&argc, &argv); | ||
folly::ssl::init(); | ||
|
||
ServerSocketConfig cfg; | ||
folly::Optional<TLSTicketKeySeeds> seeds; | ||
|
||
ServerBootstrap<EchoPipeline> sb; | ||
TLSCredProcessor processor; | ||
|
||
if (!FLAGS_tickets_path.empty()) { | ||
seeds = TLSCredProcessor::processTLSTickets(FLAGS_tickets_path); | ||
if (seeds) { | ||
cfg.initialTicketSeeds = *seeds; | ||
// watch for changes | ||
processor.setTicketPathToWatch(FLAGS_tickets_path); | ||
} | ||
} | ||
|
||
if (!FLAGS_cert_path.empty() && !FLAGS_key_path.empty()) { | ||
VLOG(0) << "Configuring SSL"; | ||
SSLContextConfig sslCfg; | ||
sslCfg.addCertificate(FLAGS_cert_path, FLAGS_key_path, ""); | ||
sslCfg.clientCAFile = FLAGS_ca_path; | ||
sslCfg.isDefault = true; | ||
cfg.sslContextConfigs.push_back(sslCfg); | ||
// IMPORTANT: when allowing both plaintext and ssl on the same port, | ||
// the acceptor requires 9 bytes of data to determine what kind of | ||
// connection is coming in. If the client does not send 9 bytes the | ||
// connection will idle out before the EchoCallback receives data. | ||
cfg.allowInsecureConnectionsOnSecureServer = true; | ||
|
||
// reload ssl contexts when certs change | ||
std::set<std::string> pathsToWatch { FLAGS_cert_path, FLAGS_key_path }; | ||
if (!FLAGS_ca_path.empty()) { | ||
pathsToWatch.insert(FLAGS_ca_path); | ||
} | ||
processor.setCertPathsToWatch(std::move(pathsToWatch)); | ||
} | ||
|
||
initCredProcessorCallbacks(sb, processor); | ||
|
||
// workers | ||
auto workers = | ||
std::make_shared<folly::IOThreadPoolExecutor>(FLAGS_num_workers); | ||
|
||
// create a server | ||
sb.acceptorConfig(cfg); | ||
sb.childPipeline(std::make_shared<EchoPipelineFactory>()); | ||
sb.group(workers); | ||
|
||
sb.bind(FLAGS_port); | ||
sb.waitForStop(); | ||
return 0; | ||
} |