diff --git a/doc/admin-guide/logging/formatting.en.rst b/doc/admin-guide/logging/formatting.en.rst index bac1fbeeb68..ca9543971e7 100644 --- a/doc/admin-guide/logging/formatting.en.rst +++ b/doc/admin-guide/logging/formatting.en.rst @@ -532,6 +532,10 @@ ppd Proxy Protocol Destination IP received via Proxy Protocol context from the Dest IP to the |TS| ppa Proxy Protocol The Authority TLV from Proxy Protocol context from the LB Authority to the |TS| +pptc Proxy Protocol The TLS cipher from Proxy Protocol context from the LB + Authority to the |TS| +pptv Proxy Protocol The TLS version from Proxy Protocol context from the LB + Authority to the |TS| ===== ============== ========================================================== .. note:: diff --git a/include/iocore/net/ProxyProtocol.h b/include/iocore/net/ProxyProtocol.h index 2cc4848dfb2..c8504f24b0f 100644 --- a/include/iocore/net/ProxyProtocol.h +++ b/include/iocore/net/ProxyProtocol.h @@ -86,6 +86,8 @@ class ProxyProtocol void set_ipv6_addrs(const in6_addr &src_addr, uint16_t src_port, const in6_addr &dst_addr, uint16_t dst_port); std::optional get_tlv(const uint8_t tlvCode) const; + std::optional get_tlv_ssl_version() const; + std::optional get_tlv_ssl_cipher() const; ProxyProtocolVersion version = ProxyProtocolVersion::UNDEFINED; uint16_t ip_family = AF_UNSPEC; @@ -134,6 +136,8 @@ class ProxyProtocol private: std::string additional_data; + + std::optional _get_tlv_ssl_subtype(int subtype) const; }; const size_t PPv1_CONNECTION_HEADER_LEN_MAX = 108; diff --git a/include/proxy/logging/LogAccess.h b/include/proxy/logging/LogAccess.h index 0cdd6e1098c..7cdf8ed48be 100644 --- a/include/proxy/logging/LogAccess.h +++ b/include/proxy/logging/LogAccess.h @@ -270,6 +270,8 @@ class LogAccess int marshal_proxy_protocol_src_ip(char *); // STR int marshal_proxy_protocol_dst_ip(char *); // STR int marshal_proxy_protocol_authority(char *); // STR + int marshal_proxy_protocol_tls_cipher(char *); // STR + int marshal_proxy_protocol_tls_version(char *); // STR // named fields from within a http header // diff --git a/src/iocore/net/ProxyProtocol.cc b/src/iocore/net/ProxyProtocol.cc index 0422e707717..afad67a7b1c 100644 --- a/src/iocore/net/ProxyProtocol.cc +++ b/src/iocore/net/ProxyProtocol.cc @@ -547,6 +547,80 @@ ProxyProtocol::get_tlv(const uint8_t tlvCode) const return std::nullopt; } +/* + * PP2_TYPE_SSL + * struct pp2_tlv_ssl { + * uint8_t client; + * uint32_t verify; + * struct pp2_tlv sub_tlv[0]; + * }; + */ + +std::optional +ProxyProtocol::_get_tlv_ssl_subtype(int subtype) const +{ + if (auto v = tlv.find(PP2_TYPE_SSL); v != tlv.end() && v->second.length() != 0) { + auto ssl = v->second; + + // Is the client connected over TLS + if ((ssl.data()[0] & 0x01) == 0) { + // Not over TLS + return std::nullopt; + } + + // Find the given subtype + uint16_t len = ssl.length(); + const char *p = ssl.data() + 5; // Skip client (uint8_t) + verify (uint32_t) + const char *end = p + len + 1; + while (p != end) { + if (end - p < 3) { + // The size of a sub TLV entry must be 3 bytes or more + Dbg(dbg_ctl_proxyprotocol_v2, "Remaining data (%ld bytes) is not enough for a sub TLV field", end - p); + return std::nullopt; + } + + // Type + uint8_t type = *p; + p += 1; + + // Length + uint16_t length = ntohs(*reinterpret_cast(p)); + p += 2; + + // Value + if (end - p < length) { + // Does not have enough data + Dbg(dbg_ctl_proxyprotocol_v2, "Remaining data (%ld bytes) is not enough for a TLV field (ID:%u LEN:%hu)", end - p, type, + length); + return std::nullopt; + } + + // Found it? + if (type == subtype) { + Dbg(dbg_ctl_proxyprotocol, "TLV: ID=%u LEN=%hu", type, length); + return std::string_view(p, length); + } + + p += length; + } + } + return std::nullopt; +} + +std::optional +ProxyProtocol::get_tlv_ssl_version() const +{ + // The specification only says "the US-ASCII string representation of the TLS version". + // HAProxy sends a string returned by SSL_get_version. + return this->_get_tlv_ssl_subtype(PP2_SUBTYPE_SSL_VERSION); +} + +std::optional +ProxyProtocol::get_tlv_ssl_cipher() const +{ + return this->_get_tlv_ssl_subtype(PP2_SUBTYPE_SSL_CIPHER); +} + int ProxyProtocol::set_additional_data(std::string_view data) { diff --git a/src/iocore/net/unit_tests/test_ProxyProtocol.cc b/src/iocore/net/unit_tests/test_ProxyProtocol.cc index 70ead688be0..f50f6839c4b 100644 --- a/src/iocore/net/unit_tests/test_ProxyProtocol.cc +++ b/src/iocore/net/unit_tests/test_ProxyProtocol.cc @@ -303,13 +303,16 @@ TEST_CASE("PROXY Protocol v2 Parser", "[ProxyProtocol][ProxyProtocolv2]") 0x55, 0x49, 0x54, 0x0A, ///< 0x21, ///< version & command 0x11, ///< protocol & family - 0x00, 0x17, ///< len + 0x00, 0x2B, ///< len 0xC0, 0x00, 0x02, 0x01, ///< src_addr 0xC6, 0x33, 0x64, 0x01, ///< dst_addr 0xC3, 0x50, ///< src_port 0x01, 0xBB, ///< dst_port 0x01, 0x00, 0x02, 0x68, 0x32, /// PP2_TYPE_ALPN (h2) - 0x02, 0x00, 0x03, 0x61, 0x62, 0x63 /// PP2_TYPE_AUTHORITY (abc) + 0x02, 0x00, 0x03, 0x61, 0x62, 0x63, /// PP2_TYPE_AUTHORITY (abc) + 0x20, 0x00, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, /// PP2_TYPE_SSL (client=0x01, verify=0) + 0x23, 0x00, 0x03, 0x58, 0x59, 0x5A, /// PP2_SUBTYPE_SSL_CIPHER (XYZ) + 0x21, 0x00, 0x03, 0x54, 0x4C, 0x53, /// PP2_SUBTYPE_SSL_VERSION (TLS) }; swoc::TextView tv(reinterpret_cast(raw_data), sizeof(raw_data)); @@ -327,6 +330,9 @@ TEST_CASE("PROXY Protocol v2 Parser", "[ProxyProtocol][ProxyProtocolv2]") CHECK(pp_info.tlv[PP2_TYPE_ALPN] == "h2"); CHECK(pp_info.tlv[PP2_TYPE_AUTHORITY] == "abc"); + + CHECK(pp_info.get_tlv_ssl_cipher() == "XYZ"); + CHECK(pp_info.get_tlv_ssl_version() == "TLS"); } SECTION("TLVs with extra data") diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc index 35d2c871874..50e975e2849 100644 --- a/src/proxy/logging/Log.cc +++ b/src/proxy/logging/Log.cc @@ -1034,6 +1034,16 @@ Log::init_fields() global_field_list.add(field, false); field_symbol_hash.emplace("ppa", field); + field = new LogField("proxy_protocol_tls_cipher", "pptc", LogField::STRING, &LogAccess::marshal_proxy_protocol_tls_cipher, + &LogAccess::unmarshal_str); + global_field_list.add(field, false); + field_symbol_hash.emplace("pptc", field); + + field = new LogField("proxy_protocol_tls_version", "pptv", LogField::STRING, &LogAccess::marshal_proxy_protocol_tls_version, + &LogAccess::unmarshal_str); + global_field_list.add(field, false); + field_symbol_hash.emplace("pptv", field); + field = new LogField("version_build_number", "vbn", LogField::STRING, &LogAccess::marshal_version_build_number, &LogAccess::unmarshal_str); global_field_list.add(field, false); diff --git a/src/proxy/logging/LogAccess.cc b/src/proxy/logging/LogAccess.cc index f043cb8514b..924615b33f6 100644 --- a/src/proxy/logging/LogAccess.cc +++ b/src/proxy/logging/LogAccess.cc @@ -1660,6 +1660,40 @@ LogAccess::marshal_proxy_protocol_authority(char *buf) return 0; } +int +LogAccess::marshal_proxy_protocol_tls_cipher(char *buf) +{ + int len = INK_MIN_ALIGN; + + if (m_http_sm) { + if (auto cipher = m_http_sm->t_state.pp_info.get_tlv_ssl_cipher(); cipher) { + len = padded_length(cipher->size() + 1); + if (buf) { + marshal_str(buf, cipher->data(), len); + buf[cipher->size()] = '\0'; + } + } + } + return len; +} + +int +LogAccess::marshal_proxy_protocol_tls_version(char *buf) +{ + int len = INK_MIN_ALIGN; + + if (m_http_sm) { + if (auto version = m_http_sm->t_state.pp_info.get_tlv_ssl_version(); version) { + len = padded_length(version->size() + 1); + if (buf) { + marshal_str(buf, version->data(), len); + buf[version->size()] = '\0'; + } + } + } + return len; +} + /*------------------------------------------------------------------------- -------------------------------------------------------------------------*/ int