mirror of https://github.com/Icinga/icinga2.git
Move some TCP/TLS logic out of ApiListener
... for re-using it
This commit is contained in:
parent
79220ee647
commit
e6d78bf361
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
#include "base/i2-base.hpp"
|
#include "base/i2-base.hpp"
|
||||||
#include "base/socket.hpp"
|
#include "base/socket.hpp"
|
||||||
|
#include <boost/asio/ip/tcp.hpp>
|
||||||
|
#include <boost/asio/spawn.hpp>
|
||||||
|
|
||||||
namespace icinga
|
namespace icinga
|
||||||
{
|
{
|
||||||
|
@ -25,6 +27,35 @@ public:
|
||||||
void Connect(const String& node, const String& service);
|
void Connect(const String& node, const String& service);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<class Socket>
|
||||||
|
void Connect(Socket& socket, const String& node, const String& service, boost::asio::yield_context yc)
|
||||||
|
{
|
||||||
|
using boost::asio::ip::tcp;
|
||||||
|
|
||||||
|
tcp::resolver resolver (socket.get_io_service());
|
||||||
|
tcp::resolver::query query (node, service);
|
||||||
|
auto result (resolver.async_resolve(query, yc));
|
||||||
|
auto current (result.begin());
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
try {
|
||||||
|
socket.open(current->endpoint().protocol());
|
||||||
|
socket.set_option(tcp::socket::keep_alive(true));
|
||||||
|
socket.async_connect(current->endpoint(), yc);
|
||||||
|
|
||||||
|
break;
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
if (++current == result.end()) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socket.is_open()) {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* TCPSOCKET_H */
|
#endif /* TCPSOCKET_H */
|
||||||
|
|
|
@ -7,7 +7,13 @@
|
||||||
#include "base/configuration.hpp"
|
#include "base/configuration.hpp"
|
||||||
#include "base/convert.hpp"
|
#include "base/convert.hpp"
|
||||||
#include <boost/asio/ssl/context.hpp>
|
#include <boost/asio/ssl/context.hpp>
|
||||||
|
#include <boost/asio/ssl/verify_context.hpp>
|
||||||
|
#include <boost/asio/ssl/verify_mode.hpp>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/tls1.h>
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
# include <poll.h>
|
# include <poll.h>
|
||||||
|
@ -447,3 +453,46 @@ Socket::Ptr TlsStream::GetSocket() const
|
||||||
{
|
{
|
||||||
return m_Socket;
|
return m_Socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UnbufferedAsioTlsStream::IsVerifyOK() const
|
||||||
|
{
|
||||||
|
return m_VerifyOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
String UnbufferedAsioTlsStream::GetVerifyError() const
|
||||||
|
{
|
||||||
|
return m_VerifyError;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnbufferedAsioTlsStream::BeforeHandshake(handshake_type type)
|
||||||
|
{
|
||||||
|
namespace ssl = boost::asio::ssl;
|
||||||
|
|
||||||
|
set_verify_mode(ssl::verify_peer | ssl::verify_client_once);
|
||||||
|
|
||||||
|
set_verify_callback([this](bool preverified, ssl::verify_context& ctx) {
|
||||||
|
if (!preverified) {
|
||||||
|
m_VerifyOK = false;
|
||||||
|
|
||||||
|
std::ostringstream msgbuf;
|
||||||
|
int err = X509_STORE_CTX_get_error(ctx.native_handle());
|
||||||
|
|
||||||
|
msgbuf << "code " << err << ": " << X509_verify_cert_error_string(err);
|
||||||
|
m_VerifyError = msgbuf.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||||
|
if (type == client && !m_Hostname.IsEmpty()) {
|
||||||
|
String environmentName = Application::GetAppEnvironment();
|
||||||
|
String serverName = m_Hostname;
|
||||||
|
|
||||||
|
if (!environmentName.IsEmpty())
|
||||||
|
serverName += ":" + environmentName;
|
||||||
|
|
||||||
|
SSL_set_tlsext_host_name(native_handle(), serverName.CStr());
|
||||||
|
}
|
||||||
|
#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */
|
||||||
|
}
|
||||||
|
|
|
@ -99,28 +99,66 @@ private:
|
||||||
void CloseInternal(bool inDestructor);
|
void CloseInternal(bool inDestructor);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct UnbufferedAsioTlsStreamParams
|
||||||
|
{
|
||||||
|
boost::asio::io_service& IoService;
|
||||||
|
boost::asio::ssl::context& SslContext;
|
||||||
|
const String& Hostname;
|
||||||
|
};
|
||||||
|
|
||||||
class UnbufferedAsioTlsStream : public boost::asio::ssl::stream<boost::asio::ip::tcp::socket>
|
class UnbufferedAsioTlsStream : public boost::asio::ssl::stream<boost::asio::ip::tcp::socket>
|
||||||
{
|
{
|
||||||
|
private:
|
||||||
|
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> Parent;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline
|
inline
|
||||||
UnbufferedAsioTlsStream(std::pair<boost::asio::io_service*, boost::asio::ssl::context*>& init)
|
UnbufferedAsioTlsStream(UnbufferedAsioTlsStreamParams& init)
|
||||||
: stream(*init.first, *init.second)
|
: stream(init.IoService, init.SslContext), m_VerifyOK(true), m_Hostname(init.Hostname)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsVerifyOK() const;
|
||||||
|
String GetVerifyError() const;
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
inline
|
||||||
|
auto async_handshake(handshake_type type, Args&&... args) -> decltype(Parent::async_handshake(type, std::forward<Args>(args)...))
|
||||||
|
{
|
||||||
|
BeforeHandshake(type);
|
||||||
|
|
||||||
|
return Parent::async_handshake(type, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
inline
|
||||||
|
auto handshake(handshake_type type, Args&&... args) -> decltype(Parent::handshake(type, std::forward<Args>(args)...))
|
||||||
|
{
|
||||||
|
BeforeHandshake(type);
|
||||||
|
|
||||||
|
return Parent::handshake(type, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_VerifyOK;
|
||||||
|
String m_VerifyError;
|
||||||
|
String m_Hostname;
|
||||||
|
|
||||||
|
void BeforeHandshake(handshake_type type);
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsioTlsStream : public boost::asio::buffered_stream<UnbufferedAsioTlsStream>
|
class AsioTlsStream : public boost::asio::buffered_stream<UnbufferedAsioTlsStream>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
inline
|
inline
|
||||||
AsioTlsStream(boost::asio::io_service& ioService, boost::asio::ssl::context& sslContext)
|
AsioTlsStream(boost::asio::io_service& ioService, boost::asio::ssl::context& sslContext, const String& hostname = String())
|
||||||
: AsioTlsStream(std::make_pair(&ioService, &sslContext))
|
: AsioTlsStream(UnbufferedAsioTlsStreamParams{ioService, sslContext, hostname})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline
|
inline
|
||||||
AsioTlsStream(std::pair<boost::asio::io_service*, boost::asio::ssl::context*> init)
|
AsioTlsStream(UnbufferedAsioTlsStreamParams init)
|
||||||
: buffered_stream(init)
|
: buffered_stream(init)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,11 @@
|
||||||
#include "base/context.hpp"
|
#include "base/context.hpp"
|
||||||
#include "base/statsfunction.hpp"
|
#include "base/statsfunction.hpp"
|
||||||
#include "base/exception.hpp"
|
#include "base/exception.hpp"
|
||||||
|
#include "base/tcpsocket.hpp"
|
||||||
#include <boost/asio/buffer.hpp>
|
#include <boost/asio/buffer.hpp>
|
||||||
#include <boost/asio/ip/tcp.hpp>
|
#include <boost/asio/ip/tcp.hpp>
|
||||||
#include <boost/asio/spawn.hpp>
|
#include <boost/asio/spawn.hpp>
|
||||||
#include <boost/asio/ssl/context.hpp>
|
#include <boost/asio/ssl/context.hpp>
|
||||||
#include <boost/asio/ssl/verify_context.hpp>
|
|
||||||
#include <boost/asio/ssl/verify_mode.hpp>
|
|
||||||
#include <boost/system/error_code.hpp>
|
#include <boost/system/error_code.hpp>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
@ -462,34 +461,9 @@ void ApiListener::AddConnection(const Endpoint::Ptr& endpoint)
|
||||||
<< "Reconnecting to endpoint '" << endpoint->GetName() << "' via host '" << host << "' and port '" << port << "'";
|
<< "Reconnecting to endpoint '" << endpoint->GetName() << "' via host '" << host << "' and port '" << port << "'";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto sslConn (std::make_shared<AsioTlsStream>(io, *sslContext));
|
auto sslConn (std::make_shared<AsioTlsStream>(io, *sslContext, endpoint->GetName()));
|
||||||
|
|
||||||
{
|
Connect(sslConn->lowest_layer(), host, port, yc);
|
||||||
tcp::resolver resolver (io);
|
|
||||||
tcp::resolver::query query (host, port);
|
|
||||||
auto result (resolver.async_resolve(query, yc));
|
|
||||||
auto current (result.begin());
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
auto& tcpConn (sslConn->lowest_layer());
|
|
||||||
|
|
||||||
try {
|
|
||||||
tcpConn.open(current->endpoint().protocol());
|
|
||||||
tcpConn.set_option(tcp::socket::keep_alive(true));
|
|
||||||
tcpConn.async_connect(current->endpoint(), yc);
|
|
||||||
|
|
||||||
break;
|
|
||||||
} catch (const std::exception&) {
|
|
||||||
if (++current == result.end()) {
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tcpConn.is_open()) {
|
|
||||||
tcpConn.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NewClientHandler(yc, sslConn, endpoint->GetName(), RoleClient);
|
NewClientHandler(yc, sslConn, endpoint->GetName(), RoleClient);
|
||||||
|
|
||||||
|
@ -551,39 +525,6 @@ void ApiListener::NewClientHandlerInternal(boost::asio::yield_context yc, const
|
||||||
|
|
||||||
auto& sslConn (client->next_layer());
|
auto& sslConn (client->next_layer());
|
||||||
|
|
||||||
sslConn.set_verify_mode(ssl::verify_peer | ssl::verify_client_once);
|
|
||||||
|
|
||||||
bool verify_ok = true;
|
|
||||||
String verifyError;
|
|
||||||
|
|
||||||
sslConn.set_verify_callback([&verify_ok, &verifyError](bool preverified, ssl::verify_context& ctx) {
|
|
||||||
if (!preverified) {
|
|
||||||
verify_ok = false;
|
|
||||||
|
|
||||||
std::ostringstream msgbuf;
|
|
||||||
int err = X509_STORE_CTX_get_error(ctx.native_handle());
|
|
||||||
|
|
||||||
msgbuf << "code " << err << ": " << X509_verify_cert_error_string(err);
|
|
||||||
verifyError = msgbuf.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (role == RoleClient) {
|
|
||||||
String environmentName = Application::GetAppEnvironment();
|
|
||||||
String serverName = hostname;
|
|
||||||
|
|
||||||
if (!environmentName.IsEmpty())
|
|
||||||
serverName += ":" + environmentName;
|
|
||||||
|
|
||||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
|
||||||
if (!hostname.IsEmpty()) {
|
|
||||||
SSL_set_tlsext_host_name(sslConn.native_handle(), serverName.CStr());
|
|
||||||
}
|
|
||||||
#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sslConn.async_handshake(role == RoleClient ? sslConn.client : sslConn.server, yc);
|
sslConn.async_handshake(role == RoleClient ? sslConn.client : sslConn.server, yc);
|
||||||
} catch (const std::exception& ex) {
|
} catch (const std::exception& ex) {
|
||||||
|
@ -601,10 +542,15 @@ void ApiListener::NewClientHandlerInternal(boost::asio::yield_context yc, const
|
||||||
});
|
});
|
||||||
|
|
||||||
std::shared_ptr<X509> cert (SSL_get_peer_certificate(sslConn.native_handle()), X509_free);
|
std::shared_ptr<X509> cert (SSL_get_peer_certificate(sslConn.native_handle()), X509_free);
|
||||||
|
bool verify_ok = false;
|
||||||
String identity;
|
String identity;
|
||||||
Endpoint::Ptr endpoint;
|
Endpoint::Ptr endpoint;
|
||||||
|
|
||||||
if (cert) {
|
if (cert) {
|
||||||
|
verify_ok = sslConn.IsVerifyOK();
|
||||||
|
|
||||||
|
String verifyError = sslConn.GetVerifyError();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
identity = GetCertificateCN(cert);
|
identity = GetCertificateCN(cert);
|
||||||
} catch (const std::exception&) {
|
} catch (const std::exception&) {
|
||||||
|
@ -640,8 +586,6 @@ void ApiListener::NewClientHandlerInternal(boost::asio::yield_context yc, const
|
||||||
log << " (no Endpoint object found for identity)";
|
log << " (no Endpoint object found for identity)";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
verify_ok = false;
|
|
||||||
|
|
||||||
Log(LogInformation, "ApiListener")
|
Log(LogInformation, "ApiListener")
|
||||||
<< "New client connection " << conninfo << " (no client certificate)";
|
<< "New client connection " << conninfo << " (no client certificate)";
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue