]> granicus.if.org Git - icinga2/commitdiff
ApiListener: perform TLS handshake
authorAlexander A. Klimov <alexander.klimov@icinga.com>
Fri, 8 Feb 2019 17:00:53 +0000 (18:00 +0100)
committerAlexander A. Klimov <alexander.klimov@icinga.com>
Mon, 1 Apr 2019 09:40:14 +0000 (11:40 +0200)
lib/remote/apilistener.cpp
lib/remote/apilistener.hpp

index d680cc76a34a8ee13d7be9161c0cf9a59f2c8343..578e1a68241acfcee720b2d8564cb6b6f1777a01 100644 (file)
 #include <boost/asio/ip/v6_only.hpp>
 #include <boost/asio/spawn.hpp>
 #include <boost/asio/ssl/context.hpp>
+#include <boost/asio/ssl/stream.hpp>
+#include <boost/asio/ssl/verify_mode.hpp>
 #include <climits>
 #include <fstream>
 #include <memory>
+#include <openssl/tls1.h>
+#include <sstream>
 
 using namespace icinga;
 
@@ -373,15 +377,36 @@ bool ApiListener::AddListener(const String& node, const String& service)
        Log(LogInformation, "ApiListener")
                << "Started new listener on '[" << localEndpoint.address() << "]:" << localEndpoint.port() << "'";
 
-       asio::spawn(io, [acceptor](asio::yield_context yc) {
-               // TODO
-       });
+       asio::spawn(io, [this, acceptor, sslContext](asio::yield_context yc) { ListenerCoroutineProc(yc, acceptor, sslContext); });
 
        UpdateStatusFile(localEndpoint);
 
        return true;
 }
 
+void ApiListener::ListenerCoroutineProc(boost::asio::yield_context yc, const std::shared_ptr<boost::asio::ip::tcp::acceptor>& server, const std::shared_ptr<boost::asio::ssl::context>& sslContext)
+{
+       namespace asio = boost::asio;
+       namespace ssl = asio::ssl;
+       using asio::ip::tcp;
+
+       auto& io (server->get_io_service());
+       auto sslConn (std::make_shared<ssl::stream<tcp::socket>>(io, *sslContext));
+
+       for (;;) {
+               try {
+                       server->async_accept(sslConn->lowest_layer(), yc);
+               } catch (const std::exception& ex) {
+                       Log(LogCritical, "ApiListener") << "Cannot accept new connection: " << DiagnosticInformation(ex, false);
+                       continue;
+               }
+
+               asio::spawn(io, [this, sslConn](asio::yield_context yc) { NewClientHandler(yc, sslConn, String(), RoleServer); });
+
+               sslConn = std::make_shared<ssl::stream<tcp::socket>>(io, *sslContext);
+       }
+}
+
 /**
  * Creates a new JSON-RPC client and connects to the specified endpoint.
  *
@@ -601,6 +626,70 @@ void ApiListener::NewClientHandlerInternal(const Socket::Ptr& client, const Stri
        }
 }
 
+void ApiListener::NewClientHandler(boost::asio::yield_context yc, const std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>& client, const String& hostname, ConnectionRole role)
+{
+       try {
+               NewClientHandlerInternal(yc, client, hostname, role);
+       } catch (const std::exception& ex) {
+               Log(LogCritical, "ApiListener")
+                       << "Exception while handling new API client connection: " << DiagnosticInformation(ex, false);
+
+               Log(LogDebug, "ApiListener")
+                       << "Exception while handling new API client connection: " << DiagnosticInformation(ex);
+       }
+}
+
+/**
+ * Processes a new client connection.
+ *
+ * @param client The new client.
+ */
+void ApiListener::NewClientHandlerInternal(boost::asio::yield_context yc, const std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>& client, const String& hostname, ConnectionRole role)
+{
+       namespace ssl = boost::asio::ssl;
+
+       String conninfo;
+
+       {
+               std::ostringstream conninfo_;
+
+               if (role == RoleClient) {
+                       conninfo_ << "to";
+               } else {
+                       conninfo_ << "from";
+               }
+
+               auto endpoint (client->lowest_layer().remote_endpoint());
+
+               conninfo_ << " [" << endpoint.address() << "]:" << endpoint.port();
+
+               conninfo = conninfo_.str();
+       }
+
+       client->set_verify_mode(ssl::verify_peer | ssl::verify_client_once);
+
+       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(client->native_handle(), serverName.CStr());
+               }
+#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */
+       }
+
+       try {
+               client->async_handshake(role == RoleClient ? client->client : client->server, yc);
+       } catch (const std::exception& ex) {
+               Log(LogCritical, "ApiListener")
+                       << "Client TLS handshake failed (" << conninfo << "): " << DiagnosticInformation(ex, false);
+       }
+}
+
 void ApiListener::SyncClient(const JsonRpcConnection::Ptr& aclient, const Endpoint::Ptr& endpoint, bool needSync)
 {
        Zone::Ptr eZone = endpoint->GetZone();
index 1de66ed4fc015bb49e47ee64eb936111f1a7fd19..e8b578aba7253da2dca5b3b4e3e4677c623528b7 100644 (file)
@@ -15,7 +15,9 @@
 #include "base/tlsstream.hpp"
 #include "base/threadpool.hpp"
 #include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/spawn.hpp>
 #include <boost/asio/ssl/context.hpp>
+#include <boost/asio/ssl/stream.hpp>
 #include <set>
 
 namespace icinga
@@ -132,6 +134,10 @@ private:
        void NewClientHandler(const Socket::Ptr& client, const String& hostname, ConnectionRole role);
        void NewClientHandlerInternal(const Socket::Ptr& client, const String& hostname, ConnectionRole role);
 
+       void NewClientHandler(boost::asio::yield_context yc, const std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>& client, const String& hostname, ConnectionRole role);
+       void NewClientHandlerInternal(boost::asio::yield_context yc, const std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>& client, const String& hostname, ConnectionRole role);
+       void ListenerCoroutineProc(boost::asio::yield_context yc, const std::shared_ptr<boost::asio::ip::tcp::acceptor>& server, const std::shared_ptr<boost::asio::ssl::context>& sslContext);
+
        static ThreadPool& GetTP();
        static void EnqueueAsyncCallback(const std::function<void ()>& callback, SchedulerPolicy policy = DefaultScheduler);