diff options
author | Peter Powell <petpow@saberuk.com> | 2018-10-16 14:57:28 +0100 |
---|---|---|
committer | Peter Powell <petpow@saberuk.com> | 2018-10-25 13:50:43 +0100 |
commit | ae0ae8ea617a1a3a3d4f89f5a5e470e8aa262c9f (patch) | |
tree | 12eef804c08f55a8dcb9ff2cf1eefbb425df48f0 | |
parent | d62c870ffb1bf02e4be65183f3ae4bbe2229fbb2 (diff) |
Allow HAProxy to specify that a client is connecting with SSL.
-rw-r--r-- | include/modules/ssl.h | 6 | ||||
-rw-r--r-- | src/modules/m_haproxy.cpp | 113 | ||||
-rw-r--r-- | src/modules/m_sslinfo.cpp | 5 |
3 files changed, 119 insertions, 5 deletions
diff --git a/include/modules/ssl.h b/include/modules/ssl.h index 930cb6dc6..edea45a10 100644 --- a/include/modules/ssl.h +++ b/include/modules/ssl.h @@ -277,6 +277,12 @@ class UserCertificateAPIBase : public DataProvider */ virtual ssl_cert* GetCertificate(User* user) = 0; + /** Set the SSL certificate of a user. + * @param user The user whose certificate to set. + * @param cert The SSL certificate to set for the user. + */ + virtual void SetCertificate(User* user, ssl_cert* cert) = 0; + /** Get the key fingerprint from a user's certificate * @param user The user whose key fingerprint to get, user may be remote * @return The key fingerprint from the user's SSL certificate or an empty string diff --git a/src/modules/m_haproxy.cpp b/src/modules/m_haproxy.cpp index 8272c2980..e92c45686 100644 --- a/src/modules/m_haproxy.cpp +++ b/src/modules/m_haproxy.cpp @@ -19,9 +19,13 @@ #include "inspircd.h" #include "iohook.h" +#include "modules/ssl.h" enum { + // The SSL TLV flag for a client being connected over SSL. + PP2_CLIENT_SSL = 0x01, + // The family for TCP over IPv4. PP2_FAMILY_IPV4 = 0x11, @@ -46,6 +50,15 @@ enum // The length of the PROXY protocol header. PP2_HEADER_LENGTH = 16, + // The minimum length of a Type-Length-Value entry. + PP2_TLV_LENGTH = 3, + + // The identifier for a SSL TLV entry. + PP2_TYPE_SSL = 0x20, + + // The minimum length of a PP2_TYPE_SSL TLV entry. + PP2_TYPE_SSL_LENGTH = 5, + // The length of the PROXY protocol signature. PP2_SIGNATURE_LENGTH = 12, @@ -94,9 +107,13 @@ struct HAProxyHeader class HAProxyHookProvider : public IOHookProvider { + private: + UserCertificateAPI sslapi; + public: HAProxyHookProvider(Module* mod) : IOHookProvider(mod, "haproxy", IOHookProvider::IOH_UNKNOWN, true) + , sslapi(mod) { } @@ -126,9 +143,82 @@ class HAProxyHook : public IOHookMiddle // The endpoint the client is connected to. irc::sockets::sockaddrs server; + // The API for interacting with user SSL internals. + UserCertificateAPI& sslapi; + // The current state of the PROXY parser. HAProxyState state; + size_t ReadProxyTLV(StreamSocket* sock, size_t start_index, uint16_t buffer_length) + { + // A TLV must at least consist of a type (uint8_t) and a length (uint16_t). + if (buffer_length < PP2_TLV_LENGTH) + { + sock->SetError("Truncated HAProxy PROXY TLV type and/or length"); + return 0; + } + + // Check that the length can actually contain the TLV value. + std::string& recvq = GetRecvQ(); + uint16_t length = ntohs(recvq[start_index + 1] | (recvq[start_index + 2] << 8)); + if (buffer_length < PP2_TLV_LENGTH + length) + { + sock->SetError("Truncated HAProxy PROXY TLV value"); + return 0; + } + + // What type of TLV are we parsing? + switch (recvq[start_index]) + { + case PP2_TYPE_SSL: + if (!ReadProxyTLVSSL(sock, start_index + PP2_TLV_LENGTH, length)) + return 0; + break; + } + + return PP2_TLV_LENGTH + length; + } + + bool ReadProxyTLVSSL(StreamSocket* sock, size_t start_index, uint16_t buffer_length) + { + // A SSL TLV must at least consist of client info (uint8_t) and verification info (uint32_t). + if (buffer_length < PP2_TYPE_SSL_LENGTH) + { + sock->SetError("Truncated HAProxy PROXY SSL TLV"); + return false; + } + + // If the socket is not a user socket we don't have to do + // anything with this TLVs information. + if (sock->type != StreamSocket::SS_USER) + return true; + + // If the sslinfo module is not loaded we can't + // do anything with this TLV. + if (!sslapi) + return true; + + // If the client is not connecting via SSL the rest of this TLV is irrelevant. + std::string& recvq = GetRecvQ(); + if ((recvq[start_index] & PP2_CLIENT_SSL) == 0) + return true; + + // Create a fake ssl_cert for the user. Ideally we should use the user's + // SSL client certificate here but as of 2018-10-16 this is not forwarded + // by HAProxy. + ssl_cert* cert = new ssl_cert; + cert->error = "HAProxy does not forward client SSL certificates"; + cert->invalid = true; + cert->revoked = true; + cert->trusted = false; + cert->unknownsigner = true; + + // Extract the user for this socket and set their certificate. + LocalUser* luser = static_cast<UserIOHandler*>(sock)->user; + sslapi->SetCertificate(luser, cert); + return true; + } + int ReadProxyAddress(StreamSocket* sock) { // Block until we have the entire address. @@ -145,6 +235,7 @@ class HAProxyHook : public IOHookMiddle case HPC_PROXY: // Store the endpoint information. + size_t tlv_index = 0; switch (client.family()) { case AF_INET: @@ -152,6 +243,7 @@ class HAProxyHook : public IOHookMiddle memcpy(&server.in4.sin_addr.s_addr, &recvq[4], 8); memcpy(&client.in4.sin_port, &recvq[8], 2); memcpy(&server.in4.sin_port, &recvq[10], 2); + tlv_index = 12; break; case AF_INET6: @@ -159,19 +251,29 @@ class HAProxyHook : public IOHookMiddle memcpy(server.in6.sin6_addr.s6_addr, &recvq[16], 16); memcpy(&client.in6.sin6_port, &recvq[32], 2); memcpy(&server.in6.sin6_port, &recvq[34], 2); + tlv_index = 36; break; case AF_UNIX: memcpy(client.un.sun_path, &recvq[0], 108); memcpy(client.un.sun_path, &recvq[108], 108); + tlv_index = 216; break; } sock->OnSetEndPoint(server, client); - // XXX: HAProxy's PROXY v2 specification defines Type-Length-Values that - // could appear here but as of 2018-07-25 it does not send anything. We - // should revisit this in the future to see if they actually send them. + // Parse any available TLVs. + while (tlv_index < address_length) + { + size_t length = ReadProxyTLV(sock, tlv_index, address_length - tlv_index); + if (!length) + return -1; + + tlv_index += length; + } + + // Erase the processed proxy information from the receive queue. recvq.erase(0, address_length); } @@ -260,8 +362,9 @@ class HAProxyHook : public IOHookMiddle } public: - HAProxyHook(IOHookProvider* Prov, StreamSocket* sock) + HAProxyHook(IOHookProvider* Prov, StreamSocket* sock, UserCertificateAPI& api) : IOHookMiddle(Prov) + , sslapi(api) , state(HPS_WAITING_FOR_HEADER) { sock->AddIOHook(this); @@ -303,7 +406,7 @@ class HAProxyHook : public IOHookMiddle void HAProxyHookProvider::OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) { - new HAProxyHook(this, sock); + new HAProxyHook(this, sock, sslapi); } class ModuleHAProxy : public Module diff --git a/src/modules/m_sslinfo.cpp b/src/modules/m_sslinfo.cpp index 6d0a84249..25415095c 100644 --- a/src/modules/m_sslinfo.cpp +++ b/src/modules/m_sslinfo.cpp @@ -147,6 +147,11 @@ class UserCertificateAPIImpl : public UserCertificateAPIBase { return ext.get(user); } + + void SetCertificate(User* user, ssl_cert* cert) CXX11_OVERRIDE + { + ext.set(user, cert); + } }; class ModuleSSLInfo : public Module, public Whois::EventListener |