aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Atlas2026-06-04 10:24:38 +0200
committerGravatar GitHub2026-06-04 09:24:38 +0100
commit515e4556ce8850447aaecd26be6d0cd125f2f83b (patch)
treea5e0a69615e5cf925ac14b82a494f845c94b753a
parentWarn about some potentially problematic <wsorigin> values. (diff)
Add support for HAProxy certificate fingerprint in PROXY protocol.
-rw-r--r--src/modules/m_haproxy.cpp99
1 files changed, 86 insertions, 13 deletions
diff --git a/src/modules/m_haproxy.cpp b/src/modules/m_haproxy.cpp
index 101204328..1dea83f28 100644
--- a/src/modules/m_haproxy.cpp
+++ b/src/modules/m_haproxy.cpp
@@ -58,6 +58,9 @@ enum
// The identifier for a TLS TLV entry.
PP2_TYPE_SSL = 0x20,
+ // The identifier for a TLS certificate fingerprint TLV entry.
+ PP2_TYPE_CERTFP = 0xE0,
+
// The minimum length of a PP2_TYPE_SSL TLV entry.
PP2_TYPE_SSL_LENGTH = 5,
@@ -150,6 +153,12 @@ private:
// The API for interacting with user TLS internals.
UserCertificateAPI& sslapi;
+ // The fingerprint forwarded by the proxy for the client certificate.
+ std::string certificate_fingerprint;
+
+ // The fake certificate we attach for this connection, if any.
+ reference<ssl_cert> certificate;
+
// The current state of the PROXY parser.
HAProxyState state = HPS_WAITING_FOR_HEADER;
@@ -172,17 +181,58 @@ private:
}
// What type of TLV are we parsing?
- switch (recvq[start_index])
+ switch (static_cast<uint8_t>(recvq[start_index]))
{
case PP2_TYPE_SSL:
if (!ReadProxyTLVSSL(sock, start_index + PP2_TLV_LENGTH, length))
return 0;
break;
+
+ case PP2_TYPE_CERTFP:
+ if (!ReadProxyTLVCertFP(sock, start_index + PP2_TLV_LENGTH, length))
+ return 0;
+ break;
}
return PP2_TLV_LENGTH + length;
}
+ bool ReadProxyTLVCertFP(StreamSocket* sock, size_t start_index, uint16_t buffer_length)
+ {
+ // If the socket is not a user socket we don't have to do
+ // anything with this TLV's information.
+ if (sock->type != StreamSocket::SS_USER)
+ return true;
+
+ // The fingerprint must be a non-empty hex-encoded digest of a plausible size.
+ // A hex string must have an even length and at most 128 chars (64-byte digest).
+ if (buffer_length == 0 || buffer_length % 2 != 0 || buffer_length > 128)
+ {
+ ServerInstance->Logs.Debug(MODNAME, "Ignoring PP2_TYPE_CERTFP TLV with unexpected length {}", buffer_length);
+ return true;
+ }
+
+ std::string& recvq = GetRecvQ();
+ certificate_fingerprint.assign(&recvq[start_index], buffer_length);
+
+ // Verify every character is a valid hexadecimal digit.
+ if (!std::all_of(certificate_fingerprint.begin(), certificate_fingerprint.end(), [](unsigned char c) { return std::isxdigit(c); }))
+ {
+ ServerInstance->Logs.Debug(MODNAME, "Ignoring PP2_TYPE_CERTFP TLV with non-hex content");
+ return true;
+ }
+
+ ServerInstance->Logs.Debug(MODNAME, "Received certificate fingerprint from HAProxy: {}", certificate_fingerprint);
+
+ // If the SSL TLV was already processed, patch the existing certificate
+ // with the fingerprint now that we have it.
+ if (certificate)
+ {
+ SetCertificateFingerprint(certificate);
+ }
+ return true;
+ }
+
bool ReadProxyTLVSSL(StreamSocket* sock, size_t start_index, uint16_t buffer_length)
{
// A TLS TLV must at least consist of client info (uint8_t) and verification info (uint32_t).
@@ -207,19 +257,32 @@ private:
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
- // TLS client certificate here but as of 2018-10-16 this is not forwarded
- // by HAProxy.
- auto* cert = new ssl_cert();
- cert->error = "HAProxy does not forward client TLS certificates";
- cert->invalid = true;
- cert->revoked = true;
- cert->trusted = false;
- cert->unknownsigner = true;
+ // Create a fake ssl_cert for the user. If the proxy already forwarded a
+ // certificate fingerprint via PP2_TYPE_CERTFP we use it; otherwise we only
+ // know the client was connected over TLS.
+ if (!certificate)
+ {
+ auto* cert = new ssl_cert();
+ cert->dn = "(unknown)";
+ cert->issuer = "(unknown)";
+
+ if (!certificate_fingerprint.empty())
+ {
+ SetCertificateFingerprint(cert);
+ }
+ else
+ {
+ cert->error = "HAProxy does not forward client TLS 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);
+ LocalUser* luser = static_cast<UserIOHandler*>(sock)->user;
+ sslapi->SetCertificate(luser, cert);
+ certificate = cert;
+ }
return true;
}
@@ -376,6 +439,16 @@ private:
return ReadProxyAddress(sock, destrecvq);
}
+ void SetCertificateFingerprint(ssl_cert* cert)
+ {
+ cert->fingerprints.push_back(std::move(certificate_fingerprint));
+ cert->invalid = false;
+ cert->trusted = true;
+ cert->unknownsigner = false;
+ cert->revoked = false;
+ cert->error.clear();
+ }
+
public:
HAProxyHook(const std::shared_ptr<IOHookProvider>& Prov, StreamSocket* sock, UserCertificateAPI& api)
: IOHookMiddle(Prov)