1
0
Fork 0
arangodb/arangod/HttpServer/ApplicationEndpointServer.cpp

699 lines
20 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// 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
///
/// 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.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Dr. Frank Celler
////////////////////////////////////////////////////////////////////////////////
#include "ApplicationEndpointServer.h"
#include <openssl/err.h>
#include "Basics/FileUtils.h"
#include "Basics/RandomGenerator.h"
#include "Basics/ReadLocker.h"
#include "Basics/VelocyPackHelper.h"
#include "Basics/WriteLocker.h"
#include "Basics/ssl-helper.h"
#include "Dispatcher/ApplicationDispatcher.h"
#include "HttpServer/HttpHandlerFactory.h"
#include "HttpServer/HttpServer.h"
#include "HttpServer/HttpsServer.h"
#include "Logger/Logger.h"
#include "Rest/Version.h"
#include "Scheduler/ApplicationScheduler.h"
#include <velocypack/Iterator.h>
#include <velocypack/velocypack-aliases.h>
using namespace arangodb::basics;
using namespace arangodb::rest;
namespace {
class BIOGuard {
public:
explicit BIOGuard(BIO* bio) : _bio(bio) {}
~BIOGuard() { BIO_free(_bio); }
public:
BIO* _bio;
};
}
ApplicationEndpointServer::ApplicationEndpointServer(
ApplicationServer* applicationServer,
ApplicationScheduler* applicationScheduler,
ApplicationDispatcher* applicationDispatcher, AsyncJobManager* jobManager,
std::string const& authenticationRealm,
HttpHandlerFactory::context_fptr setContext, void* contextData)
: ApplicationFeature("EndpointServer"),
_applicationServer(applicationServer),
_applicationScheduler(applicationScheduler),
_applicationDispatcher(applicationDispatcher),
_jobManager(jobManager),
_authenticationRealm(authenticationRealm),
_setContext(setContext),
_contextData(contextData),
_handlerFactory(nullptr),
_servers(),
_basePath(),
_endpointList(),
_httpPort(),
_endpoints(),
_reuseAddress(true),
_keepAliveTimeout(300.0),
_defaultApiCompatibility(0),
_allowMethodOverride(false),
_backlogSize(64),
_httpsKeyfile(),
_cafile(),
_sslProtocol(TLS_V1),
_sslCache(false),
_sslOptions(
(long)(SSL_OP_TLS_ROLLBACK_BUG | SSL_OP_CIPHER_SERVER_PREFERENCE)),
_sslEcdhCurve("prime256v1"),
_sslCipherList(""),
_sslContext(nullptr),
_rctx() {
// if our default value is too high, we'll use half of the max value provided
// by the system
if (_backlogSize > SOMAXCONN) {
_backlogSize = SOMAXCONN / 2;
}
_defaultApiCompatibility = Version::getNumericServerVersion();
}
ApplicationEndpointServer::~ApplicationEndpointServer() {
// ..........................................................................
// Where ever possible we should EXPLICITLY write down the type used in
// a templated class/method etc. This makes it a lot easier to debug the
// code. Granted however, that explicitly writing down the type for an
// overloaded class operator is a little unwieldy.
// ..........................................................................
for (auto& it : _servers) {
delete it;
}
_servers.clear();
delete _handlerFactory;
if (_sslContext != nullptr) {
SSL_CTX_free(_sslContext);
_sslContext = nullptr;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief builds the endpoint servers
////////////////////////////////////////////////////////////////////////////////
bool ApplicationEndpointServer::buildServers() {
TRI_ASSERT(_handlerFactory != nullptr);
TRI_ASSERT(_applicationScheduler->scheduler() != nullptr);
HttpServer* server;
// unencrypted endpoints
server = new HttpServer(_applicationScheduler->scheduler(),
_applicationDispatcher->dispatcher(), _handlerFactory,
_jobManager, _keepAliveTimeout);
server->setEndpointList(&_endpointList);
_servers.push_back(server);
// ssl endpoints
if (_endpointList.hasSsl()) {
// check the ssl context
if (_sslContext == nullptr) {
LOG(INFO) << "please use the --server.keyfile option";
LOG(FATAL) << "no ssl context is known, cannot create https server";
FATAL_ERROR_EXIT();
}
// https
server =
new HttpsServer(_applicationScheduler->scheduler(),
_applicationDispatcher->dispatcher(), _handlerFactory,
_jobManager, _keepAliveTimeout, _sslContext);
server->setEndpointList(&_endpointList);
_servers.push_back(server);
}
return true;
}
void ApplicationEndpointServer::setupOptions(
std::map<std::string, ProgramOptionsDescription>& options) {
// issue #175: add deprecated hidden option for downwards compatibility
options["Hidden Options"]("server.http-port", &_httpPort,
"http port for client requests (deprecated)");
options["Server Options:help-default"]("server.endpoint", &_endpoints,
"endpoint for client requests (e.g. "
"\"tcp://127.0.0.1:8529\", or "
"\"ssl://192.168.1.1:8529\")");
options["Server Options:help-admin"](
"server.allow-method-override", &_allowMethodOverride,
"allow HTTP method override using special headers")(
"server.backlog-size", &_backlogSize, "listen backlog size")(
"server.default-api-compatibility", &_defaultApiCompatibility,
"default API compatibility version")("server.keep-alive-timeout",
&_keepAliveTimeout,
"keep-alive timeout in seconds")(
"server.reuse-address", &_reuseAddress, "try to reuse address");
options["SSL Options:help-ssl"]("server.keyfile", &_httpsKeyfile,
"keyfile for SSL connections")(
"server.cafile", &_cafile,
"file containing the CA certificates of clients")(
"server.ssl-protocol", &_sslProtocol,
"1 = SSLv2, 2 = SSLv23, 3 = SSLv3, 4 = TLSv1, 5 = TLSV1.2 (recommended)")(
"server.ssl-cache", &_sslCache, "use SSL session caching")(
"server.ssl-options", &_sslOptions,
"SSL options, see OpenSSL documentation")(
"server.ssl-ecdh-curve", &_sslEcdhCurve,
"SSL ECDH Curve, see the output of \"openssl ecparam -list_curves\"")(
"server.ssl-cipher-list", &_sslCipherList,
"SSL cipher list, see OpenSSL documentation");
}
bool ApplicationEndpointServer::afterOptionParsing(ProgramOptions& options) {
// create the ssl context (if possible)
bool ok = createSslContext();
if (!ok) {
return false;
}
if (_backlogSize <= 0) {
LOG(FATAL) << "invalid value for --server.backlog-size. expecting a "
"positive value";
FATAL_ERROR_EXIT();
}
if (_backlogSize > SOMAXCONN) {
LOG(WARN) << "value for --server.backlog-size exceeds default system "
"header SOMAXCONN value "
<< SOMAXCONN << ". trying to use " << SOMAXCONN << " anyway";
}
if (!_httpPort.empty()) {
// issue #175: add hidden option --server.http-port for
// downwards-compatibility
std::string httpEndpoint("tcp://" + _httpPort);
_endpoints.push_back(httpEndpoint);
}
// add & validate endpoints
for (std::vector<std::string>::const_iterator i = _endpoints.begin();
i != _endpoints.end(); ++i) {
bool ok = _endpointList.add((*i), _backlogSize, _reuseAddress);
if (!ok) {
LOG(FATAL) << "invalid endpoint '" << (*i) << "'";
FATAL_ERROR_EXIT();
}
}
if (_defaultApiCompatibility < GeneralRequest::MIN_COMPATIBILITY) {
LOG(FATAL) << "invalid value for --server.default-api-compatibility. "
"minimum allowed value is "
<< GeneralRequest::MIN_COMPATIBILITY;
FATAL_ERROR_EXIT();
}
// and return
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return a list of all endpoints
////////////////////////////////////////////////////////////////////////////////
std::vector<std::string> ApplicationEndpointServer::getEndpoints() {
return _endpointList.all();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the list of databases for an endpoint
////////////////////////////////////////////////////////////////////////////////
bool ApplicationEndpointServer::prepare() {
if (_disabled) {
return true;
}
if (_endpointList.empty()) {
LOG(INFO) << "please use the '--server.endpoint' option";
LOG(FATAL) << "no endpoints have been specified, giving up";
FATAL_ERROR_EXIT();
}
// dump all endpoints for user information
_endpointList.dump();
_handlerFactory =
new HttpHandlerFactory(_authenticationRealm, _defaultApiCompatibility,
_allowMethodOverride, _setContext, _contextData);
LOG(DEBUG) << "using default API compatibility: "
<< (long int)_defaultApiCompatibility;
return true;
}
bool ApplicationEndpointServer::open() {
if (_disabled) {
return true;
}
for (auto& server : _servers) {
server->startListening();
}
return true;
}
void ApplicationEndpointServer::close() {
if (_disabled) {
return;
}
// close all listen sockets
for (auto& server : _servers) {
server->stopListening();
}
}
void ApplicationEndpointServer::stop() {
if (_disabled) {
return;
}
for (auto& server : _servers) {
server->stop();
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief creates an ssl context
////////////////////////////////////////////////////////////////////////////////
bool ApplicationEndpointServer::createSslContext() {
// check keyfile
if (_httpsKeyfile.empty()) {
return true;
}
// validate protocol
if (_sslProtocol <= SSL_UNKNOWN || _sslProtocol >= SSL_LAST) {
LOG(ERR) << "invalid SSL protocol version specified. Please use a valid "
"value for --server.ssl-protocol.";
return false;
}
LOG(DEBUG) << "using SSL protocol version '"
<< protocolName((protocol_e)_sslProtocol) << "'";
if (!FileUtils::exists(_httpsKeyfile)) {
LOG(FATAL) << "unable to find SSL keyfile '" << _httpsKeyfile << "'";
FATAL_ERROR_EXIT();
}
// create context
_sslContext = sslContext(protocol_e(_sslProtocol), _httpsKeyfile);
if (_sslContext == nullptr) {
LOG(ERR) << "failed to create SSL context, cannot create HTTPS server";
return false;
}
// set cache mode
SSL_CTX_set_session_cache_mode(
_sslContext, _sslCache ? SSL_SESS_CACHE_SERVER : SSL_SESS_CACHE_OFF);
if (_sslCache) {
LOG(TRACE) << "using SSL session caching";
}
// set options
SSL_CTX_set_options(_sslContext, (long)_sslOptions);
std::string sslOptions = stringifySslOptions(_sslOptions);
LOG(INFO) << "using SSL options: " << sslOptions;
if (!_sslCipherList.empty()) {
if (SSL_CTX_set_cipher_list(_sslContext, _sslCipherList.c_str()) != 1) {
LOG(ERR) << "SSL error: " << lastSSLError();
LOG(FATAL) << "cannot set SSL cipher list '" << _sslCipherList << "'";
FATAL_ERROR_EXIT();
} else {
LOG(INFO) << "using SSL cipher-list '" << _sslCipherList << "'";
}
}
#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
int sslEcdhNid;
EC_KEY *ecdhKey;
sslEcdhNid = OBJ_sn2nid(_sslEcdhCurve.c_str());
if (sslEcdhNid == 0) {
LOG(ERR) << "SSL error: " << lastSSLError() <<" Unknown curve name: " << _sslEcdhCurve;
FATAL_ERROR_EXIT();
}
// https://www.openssl.org/docs/manmaster/apps/ecparam.html
ecdhKey = EC_KEY_new_by_curve_name(sslEcdhNid);
if (ecdhKey == nullptr) {
LOG(ERR) << "SSL error: " << lastSSLError() <<" Unable to create curve by name: " << _sslEcdhCurve;
FATAL_ERROR_EXIT();
}
SSL_CTX_set_tmp_ecdh(_sslContext, ecdhKey);
SSL_CTX_set_options(_sslContext, SSL_OP_SINGLE_ECDH_USE);
EC_KEY_free(ecdhKey);
#endif
// set ssl context
Random::UniformCharacter r(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
_rctx = r.random(SSL_MAX_SSL_SESSION_ID_LENGTH);
int res = SSL_CTX_set_session_id_context(
_sslContext, (unsigned char const*)_rctx.c_str(), (int)_rctx.size());
if (res != 1) {
LOG(ERR) << "SSL error: " << lastSSLError();
LOG(FATAL) << "cannot set SSL session id context '" << _rctx << "'";
FATAL_ERROR_EXIT();
}
// check CA
if (!_cafile.empty()) {
LOG(TRACE) << "trying to load CA certificates from '" << _cafile << "'";
int res = SSL_CTX_load_verify_locations(_sslContext, _cafile.c_str(), 0);
if (res == 0) {
LOG(ERR) << "SSL error: " << lastSSLError();
LOG(FATAL) << "cannot load CA certificates from '" << _cafile << "'";
FATAL_ERROR_EXIT();
}
STACK_OF(X509_NAME) * certNames;
certNames = SSL_load_client_CA_file(_cafile.c_str());
if (certNames == nullptr) {
LOG(ERR) << "ssl error: " << lastSSLError();
LOG(FATAL) << "cannot load CA certificates from '" << _cafile << "'";
FATAL_ERROR_EXIT();
}
if (Logger::logLevel() == arangodb::LogLevel::TRACE) {
for (int i = 0; i < sk_X509_NAME_num(certNames); ++i) {
X509_NAME* cert = sk_X509_NAME_value(certNames, i);
if (cert) {
BIOGuard bout(BIO_new(BIO_s_mem()));
X509_NAME_print_ex(bout._bio, cert, 0,
(XN_FLAG_SEP_COMMA_PLUS | XN_FLAG_DN_REV |
ASN1_STRFLGS_UTF8_CONVERT) &
~ASN1_STRFLGS_ESC_MSB);
char* r;
long len = BIO_get_mem_data(bout._bio, &r);
LOG(TRACE) << "name: " << std::string(r, len);
}
}
}
SSL_CTX_set_client_CA_list(_sslContext, certNames);
}
return true;
}
std::string ApplicationEndpointServer::stringifySslOptions(uint64_t opts) const {
std::string result;
#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
if (opts & SSL_OP_MICROSOFT_SESS_ID_BUG) {
result.append(", SSL_OP_MICROSOFT_SESS_ID_BUG");
}
#endif
#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
if (opts & SSL_OP_NETSCAPE_CHALLENGE_BUG) {
result.append(", SSL_OP_NETSCAPE_CHALLENGE_BUG");
}
#endif
#ifdef SSL_OP_LEGACY_SERVER_CONNECT
if (opts & SSL_OP_LEGACY_SERVER_CONNECT) {
result.append(", SSL_OP_LEGACY_SERVER_CONNECT");
}
#endif
#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
if (opts & SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG) {
result.append(", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG");
}
#endif
#ifdef SSL_OP_TLSEXT_PADDING
if (opts & SSL_OP_TLSEXT_PADDING) {
result.append(", SSL_OP_TLSEXT_PADDING");
}
#endif
#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
if (opts & SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER) {
result.append(", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER");
}
#endif
#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
if (opts & SSL_OP_SAFARI_ECDHE_ECDSA_BUG) {
result.append(", SSL_OP_SAFARI_ECDHE_ECDSA_BUG");
}
#endif
#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
if (opts & SSL_OP_SSLEAY_080_CLIENT_DH_BUG) {
result.append(", SSL_OP_SSLEAY_080_CLIENT_DH_BUG");
}
#endif
#ifdef SSL_OP_TLS_D5_BUG
if (opts & SSL_OP_TLS_D5_BUG) {
result.append(", SSL_OP_TLS_D5_BUG");
}
#endif
#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
if (opts & SSL_OP_TLS_BLOCK_PADDING_BUG) {
result.append(", SSL_OP_TLS_BLOCK_PADDING_BUG");
}
#endif
#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
if (opts & SSL_OP_MSIE_SSLV2_RSA_PADDING) {
result.append(", SSL_OP_MSIE_SSLV2_RSA_PADDING");
}
#endif
#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
if (opts & SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG) {
result.append(", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG");
}
#endif
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
if (opts & SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) {
result.append(", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS");
}
#endif
#ifdef SSL_OP_NO_QUERY_MTU
if (opts & SSL_OP_NO_QUERY_MTU) {
result.append(", SSL_OP_NO_QUERY_MTU");
}
#endif
#ifdef SSL_OP_COOKIE_EXCHANGE
if (opts & SSL_OP_COOKIE_EXCHANGE) {
result.append(", SSL_OP_COOKIE_EXCHANGE");
}
#endif
#ifdef SSL_OP_NO_TICKET
if (opts & SSL_OP_NO_TICKET) {
result.append(", SSL_OP_NO_TICKET");
}
#endif
#ifdef SSL_OP_CISCO_ANYCONNECT
if (opts & SSL_OP_CISCO_ANYCONNECT) {
result.append(", SSL_OP_CISCO_ANYCONNECT");
}
#endif
#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
if (opts & SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION) {
result.append(", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION");
}
#endif
#ifdef SSL_OP_NO_COMPRESSION
if (opts & SSL_OP_NO_COMPRESSION) {
result.append(", SSL_OP_NO_COMPRESSION");
}
#endif
#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
if (opts & SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION) {
result.append(", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION");
}
#endif
#ifdef SSL_OP_SINGLE_ECDH_USE
if (opts & SSL_OP_SINGLE_ECDH_USE) {
result.append(", SSL_OP_SINGLE_ECDH_USE");
}
#endif
#ifdef SSL_OP_SINGLE_DH_USE
if (opts & SSL_OP_SINGLE_DH_USE) {
result.append(", SSL_OP_SINGLE_DH_USE");
}
#endif
#ifdef SSL_OP_EPHEMERAL_RSA
if (opts & SSL_OP_EPHEMERAL_RSA) {
result.append(", SSL_OP_EPHEMERAL_RSA");
}
#endif
#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
if (opts & SSL_OP_CIPHER_SERVER_PREFERENCE) {
result.append(", SSL_OP_CIPHER_SERVER_PREFERENCE");
}
#endif
#ifdef SSL_OP_TLS_ROLLBACK_BUG
if (opts & SSL_OP_TLS_ROLLBACK_BUG) {
result.append(", SSL_OP_TLS_ROLLBACK_BUG");
}
#endif
#ifdef SSL_OP_NO_SSLv2
if (opts & SSL_OP_NO_SSLv2) {
result.append(", SSL_OP_NO_SSLv2");
}
#endif
#ifdef SSL_OP_NO_SSLv3
if (opts & SSL_OP_NO_SSLv3) {
result.append(", SSL_OP_NO_SSLv3");
}
#endif
#ifdef SSL_OP_NO_TLSv1
if (opts & SSL_OP_NO_TLSv1) {
result.append(", SSL_OP_NO_TLSv1");
}
#endif
#ifdef SSL_OP_NO_TLSv1_2
if (opts & SSL_OP_NO_TLSv1_2) {
result.append(", SSL_OP_NO_TLSv1_2");
}
#endif
#ifdef SSL_OP_NO_TLSv1_1
if (opts & SSL_OP_NO_TLSv1_1) {
result.append(", SSL_OP_NO_TLSv1_1");
}
#endif
#ifdef SSL_OP_NO_DTLSv1
if (opts & SSL_OP_NO_DTLSv1) {
result.append(", SSL_OP_NO_DTLSv1");
}
#endif
#ifdef SSL_OP_NO_DTLSv1_2
if (opts & SSL_OP_NO_DTLSv1_2) {
result.append(", SSL_OP_NO_DTLSv1_2");
}
#endif
#ifdef SSL_OP_NO_SSL_MASK
if (opts & SSL_OP_NO_SSL_MASK) {
result.append(", SSL_OP_NO_SSL_MASK");
}
#endif
#ifdef SSL_OP_PKCS1_CHECK_1
if (opts & SSL_OP_PKCS1_CHECK_1) {
result.append(", SSL_OP_PKCS1_CHECK_1");
}
#endif
#ifdef SSL_OP_PKCS1_CHECK_2
if (opts & SSL_OP_PKCS1_CHECK_2) {
result.append(", SSL_OP_PKCS1_CHECK_2");
}
#endif
#ifdef SSL_OP_NETSCAPE_CA_DN_BUG
if (opts & SSL_OP_NETSCAPE_CA_DN_BUG) {
result.append(", SSL_OP_NETSCAPE_CA_DN_BUG");
}
#endif
#ifdef SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG
if (opts & SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG) {
result.append(", SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG");
}
#endif
#ifdef SSL_OP_CRYPTOPRO_TLSEXT_BUG
if (opts & SSL_OP_CRYPTOPRO_TLSEXT_BUG) {
result.append(", SSL_OP_CRYPTOPRO_TLSEXT_BUG");
}
#endif
if (result.empty()) {
return result;
}
// strip initial comma
return result.substr(2);
}