From c8bfd06b575ee106b52a31c7075aae200796329f Mon Sep 17 00:00:00 2001 From: William Lallemand Date: Wed, 1 Apr 2026 10:56:24 +0200 Subject: [PATCH] MINOR: ssl/log: add keylog format variables and env vars Add keylog_format_fc and keylog_format_bc global variables containing the SSLKEYLOGFILE log-format strings for the frontend (client-facing) and backend (server-facing) TLS connections respectively. These produce output compatible with the SSLKEYLOGFILE format described at: https://tlswg.org/sslkeylogfile/draft-ietf-tls-keylogfile.html Both formats are also exported as environment variables at startup: HAPROXY_KEYLOG_FC_LOG_FMT HAPROXY_KEYLOG_BC_LOG_FMT These variables contains \n so they might not be compatible with syslog servers, using them with stderr or a sink might be required. These can be referenced directly in "log-format" directives to produce SSLKEYLOGFILE-compatible output, usable by network analyzers such as Wireshark to decrypt captured TLS traffic. --- doc/configuration.txt | 53 ++++++++++++++++++++---------- examples/keylog-test.cfg | 69 ++++++++++++++++++++++++++++++++++++++++ include/haproxy/log.h | 2 ++ src/haproxy.c | 2 ++ src/log.c | 21 +++++++++++- 5 files changed, 129 insertions(+), 18 deletions(-) create mode 100644 examples/keylog-test.cfg diff --git a/doc/configuration.txt b/doc/configuration.txt index 14885de95..990edef75 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -990,23 +990,26 @@ within conditional blocks and not to reference them in the global section's The table below summaries the status of each variable for the different working modes: - +--------------------------+---------+------------+-----------+ - | variable | usable | modifiable | listed | - | +---------+------------+-----------+ - | | M | W | M | W | M | W | - +--------------------------+----+----+------+-----+-----+-----+ - | HAPROXY_STARTUP_VERSION | X | X | | | X | X | - | HAPROXY_BRANCH | X | X | | | X | X | - | HAPROXY_CFGFILES | | | | | X | X | - | HAPROXY_MWORKER | | | | | X | X | - | HAPROXY_CLI | | | | | | X | - | HAPROXY_MASTER_CLI | | | | | X | | - | HAPROXY_LOCALPEER | | X | | | | X | - | HAPROXY_HTTP_LOG_FMT | | X | | X | | | - | HAPROXY_HTTP_CLF_LOG_FMT | | X | | X | | | - | HAPROXY_HTTPS_LOG_FMT | | X | | X | | | - | HAPROXY_TCP_LOG_FMT | | X | | X | | | - +--------------------------+----+----+------+-----+-----+-----+ + +---------------------------+---------+------------+-----------+ + | variable | usable | modifiable | listed | + | +---------+------------+-----------+ + | | M | W | M | W | M | W | + +---------------------------+----+----+------+-----+-----+-----+ + | HAPROXY_STARTUP_VERSION | X | X | | | X | X | + | HAPROXY_BRANCH | X | X | | | X | X | + | HAPROXY_CFGFILES | | | | | X | X | + | HAPROXY_MWORKER | | | | | X | X | + | HAPROXY_CLI | | | | | | X | + | HAPROXY_MASTER_CLI | | | | | X | | + | HAPROXY_LOCALPEER | | X | | | | X | + | HAPROXY_HTTP_LOG_FMT | | X | | X | | | + | HAPROXY_HTTP_CLF_LOG_FMT | | X | | X | | | + | HAPROXY_HTTPS_LOG_FMT | | X | | X | | | + | HAPROXY_TCP_LOG_FMT | | X | | X | | | + | HAPROXY_TCP_CLF_LOG_FMT | | X | | X | | | + | HAPROXY_KEYLOG_FC_LOG_FMT | | X | | X | | | + | HAPROXY_KEYLOG_BC_LOG_FMT | | X | | X | | | + +---------------------------+----+----+------+-----+-----+-----+ The variables in question are the following: @@ -1039,6 +1042,16 @@ The variables in question are the following: * HAPROXY_TCP_CLF_LOG_FMT: similar to HAPROXY_HTTP_CLF_LOG_FMT but for TCP CLF log format as defined in section 8.2.2 "TCP log format". + * HAPROXY_KEYLOG_FC_LOG_FMT: contains the keylog format for the frontend + (client-facing) TLS connection, with key entries separated by newlines so + it might not be compatible with your syslog server. "tune.ssl.keylog on" is + required. + + * HAPROXY_KEYLOG_BC_LOG_FMT: similar to HAPROXY_KEYLOG_FC_LOG_FMT but for the + backend (server-facing) TLS connection. Key entries are separated by + newlines so it might not be compatible with your syslog server. + "tune.ssl.keylog on" is required. + * HAPROXY_MWORKER: In master-worker mode, this variable is set to 1. * HAPROXY_CLI: configured listeners addresses of the stats socket of every @@ -5531,6 +5544,12 @@ tune.ssl.keylog { on | off } EXPORTER_SECRET %[ssl_bc_client_random,hex] %[ssl_bc_exporter_secret]\n EARLY_EXPORTER_SECRET %[ssl_bc_client_random,hex] %[ssl_bc_early_exporter_secret]" + HAProxy also provides the above formats as predefined environment variables + that can be used directly in a "log-format" directive: + + $HAPROXY_KEYLOG_FC_LOG_FMT frontend (client-facing) connection keys + $HAPROXY_KEYLOG_BC_LOG_FMT backend (server-facing) connection keys + tune.ssl.lifetime Sets how long a cached SSL session may remain valid. This time is expressed in seconds and defaults to 300 (5 min). It is important to understand that it diff --git a/examples/keylog-test.cfg b/examples/keylog-test.cfg new file mode 100644 index 000000000..0e615d0ad --- /dev/null +++ b/examples/keylog-test.cfg @@ -0,0 +1,69 @@ +# Example: log HTTP traffic and TLS session keys to separate destinations +# +# "option httpslog" sends HTTP access logs to the /dev/log syslog server. +# TLS session keys are written to 2 ring buffers. +# +# Requirements: +# - HAProxy built with OpenSSL support +# - "tune.ssl.keylog on" in the global section +# +# Retrieve TLS session keys from the ring buffer via the CLI: +# For frontend connections: +# +# (echo "show events keylog-fc -w"; read) | socat /tmp/worker.socket - +# +# For backend connections: +# +# (echo "show events keylog-bc -w"; read) | socat /tmp/worker.socket - +# +# The result is in SSLKEYLOGFILE format and can be saved to a file and loaded +# into Wireshark to decrypt captured TLS traffic. + +global + stats socket /tmp/worker.socket mode 0660 + tune.ssl.keylog on + +# Ring buffer for TLS session keys. +# "format raw" stores only the log message text, without any syslog envelope, +# producing output in the SSLKEYLOGFILE format directly. +ring keylog-fc + description "TLS session key frontend log" + format raw + maxlen 2000 + size 1M + +ring keylog-bc + description "TLS session key backend log" + format raw + maxlen 2000 + size 1M + + +defaults + mode http + timeout client 30s + timeout server 30s + timeout connect 5s + +log-profile keylog-fc + on any format "${HAPROXY_KEYLOG_FC_LOG_FMT}" + +log-profile keylog-bc + on any format "${HAPROXY_KEYLOG_BC_LOG_FMT}" + +frontend https-in + bind :443 ssl crt "common.pem" + + option httpslog + + # HTTPs access logs sent to the syslog server + log /dev/log format raw local0 + + # TLS session keys written to the ring buffer + log ring@keylog-fc profile keylog-fc local1 + log ring@keylog-bc profile keylog-bc local1 + + default_backend be1 + +backend be1 + server s1 10.0.0.123:443 ssl verify none diff --git a/include/haproxy/log.h b/include/haproxy/log.h index aec00e6ab..8a3651802 100644 --- a/include/haproxy/log.h +++ b/include/haproxy/log.h @@ -42,6 +42,8 @@ extern char clf_tcp_log_format[]; extern char default_http_log_format[]; extern char clf_http_log_format[]; extern char default_https_log_format[]; +extern char keylog_format_fc[]; +extern char keylog_format_bc[]; extern char default_rfc5424_sd_log_format[]; diff --git a/src/haproxy.c b/src/haproxy.c index 2145cbadf..39d1a0ac6 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -1121,6 +1121,8 @@ static int read_cfg() setenv("HAPROXY_HTTPS_LOG_FMT", default_https_log_format, 1); setenv("HAPROXY_TCP_LOG_FMT", default_tcp_log_format, 1); setenv("HAPROXY_TCP_CLF_LOG_FMT", clf_tcp_log_format, 1); + setenv("HAPROXY_KEYLOG_FC_LOG_FMT", keylog_format_fc, 1); + setenv("HAPROXY_KEYLOG_BC_LOG_FMT", keylog_format_bc, 1); setenv("HAPROXY_BRANCH", PRODUCT_BRANCH, 1); list_for_each_entry(cfg, &cfg_cfgfiles, list) { int ret; diff --git a/src/log.c b/src/log.c index a544ea18c..5094c556a 100644 --- a/src/log.c +++ b/src/log.c @@ -334,6 +334,23 @@ char default_tcp_log_format[] = "%ci:%cp [%t] %ft %b/%s %Tw/%Tc/%Tt %B %ts %ac/% char clf_tcp_log_format[] = "%{+Q}o %{-Q}ci - - [%T] \"TCP \" 000 %B \"\" \"\" %cp %ms %ft %b %s %Th %Tw %Tc %Tt %U %ts-- %ac %fc %bc %sc %rc %sq %bq \"\" \"\" "; char *log_format = NULL; +char keylog_format_bc[] = "CLIENT_EARLY_TRAFFIC_SECRET %[ssl_bc_client_random,hex] %[ssl_bc_client_early_traffic_secret]\n" + "CLIENT_HANDSHAKE_TRAFFIC_SECRET %[ssl_bc_client_random,hex] %[ssl_bc_client_handshake_traffic_secret]\n" + "SERVER_HANDSHAKE_TRAFFIC_SECRET %[ssl_bc_client_random,hex] %[ssl_bc_server_handshake_traffic_secret]\n" + "CLIENT_TRAFFIC_SECRET_0 %[ssl_bc_client_random,hex] %[ssl_bc_client_traffic_secret_0]\n" + "SERVER_TRAFFIC_SECRET_0 %[ssl_bc_client_random,hex] %[ssl_bc_server_traffic_secret_0]\n" + "EXPORTER_SECRET %[ssl_bc_client_random,hex] %[ssl_bc_exporter_secret]\n" + "EARLY_EXPORTER_SECRET %[ssl_bc_client_random,hex] %[ssl_bc_early_exporter_secret]"; + +char keylog_format_fc[] = "CLIENT_EARLY_TRAFFIC_SECRET %[ssl_fc_client_random,hex] %[ssl_fc_client_early_traffic_secret]\n" + "CLIENT_HANDSHAKE_TRAFFIC_SECRET %[ssl_fc_client_random,hex] %[ssl_fc_client_handshake_traffic_secret]\n" + "SERVER_HANDSHAKE_TRAFFIC_SECRET %[ssl_fc_client_random,hex] %[ssl_fc_server_handshake_traffic_secret]\n" + "CLIENT_TRAFFIC_SECRET_0 %[ssl_fc_client_random,hex] %[ssl_fc_client_traffic_secret_0]\n" + "SERVER_TRAFFIC_SECRET_0 %[ssl_fc_client_random,hex] %[ssl_fc_server_traffic_secret_0]\n" + "EXPORTER_SECRET %[ssl_fc_client_random,hex] %[ssl_fc_exporter_secret]\n" + "EARLY_EXPORTER_SECRET %[ssl_fc_client_random,hex] %[ssl_fc_early_exporter_secret]"; + + /* Default string used for structured-data part in RFC5424 formatted * syslog messages. */ @@ -351,7 +368,9 @@ static inline int logformat_str_isdefault(const char *str) str == clf_http_log_format || str == default_tcp_log_format || str == clf_tcp_log_format || - str == default_rfc5424_sd_log_format; + str == default_rfc5424_sd_log_format || + str == keylog_format_bc || + str == keylog_format_fc; } /* free logformat str if it is not a default (static) one */