MINOR: ssl/ech: add logging and sample fetches for ECH status and outer SNI

This patch adds functions to expose Encrypted Client Hello (ECH) status
and outer SNI information for logging and sample fetching.

Two new helper functions are introduced in ech.c:
 - conn_get_ech_status() places the ECH processing status string into a
   buffer.
 - conn_get_ech_outer_sni() retrieves the outer SNI value if ECH
   succeeded.

Two new sample fetch keywords are added:
 - "ssl_fc_ech_status" returns the ECH status string.
 - "ssl_fc_ech_outer_sni" returns the outer SNI value seen during ECH.

These allow ECH information to be used in HAProxy logs, ACLs, and
captures.
This commit is contained in:
sftcd 2025-09-26 22:17:13 +01:00 committed by William Lallemand
parent dba4fd248a
commit 23f5cbb411
3 changed files with 104 additions and 0 deletions

View File

@ -6,6 +6,8 @@
#include <openssl/ech.h> #include <openssl/ech.h>
int load_echkeys(SSL_CTX *ctx, char *dirname, int *loaded); int load_echkeys(SSL_CTX *ctx, char *dirname, int *loaded);
int conn_get_ech_status(struct connection *conn, struct buffer *buf);
int conn_get_ech_outer_sni(struct connection *conn, struct buffer *buf);
# endif /* USE_ECH */ # endif /* USE_ECH */
#endif /* _HAPROXY_ECH_H */ #endif /* _HAPROXY_ECH_H */

View File

@ -74,6 +74,58 @@ end:
return rv; return rv;
} }
/*
* Place an ECH status string into a trash buffer
* ECH status string examples:
* SSL_ECH_STATUS_GREASE
* SSL_ECH_STATUS_NOT_TRIED
* SSL_ECH_STATUS_SUCCESS
* The status values are those defined in <openssl/ech.h>
* as the define'd returns from `SSL_ech_get1_status()`
*/
int conn_get_ech_status(struct connection *conn, struct buffer *buf)
{
struct ssl_sock_ctx *ctx = conn_get_ssl_sock_ctx(conn);
char *sni_ech = NULL;
char *sni_clr = NULL;
const char *lstr = NULL;
if (!ctx)
return 0;
#define s(x) #x
switch (SSL_ech_get1_status(ctx->ssl, &sni_ech, &sni_clr)) {
case SSL_ECH_STATUS_SUCCESS: lstr = s(SSL_ECH_STATUS_SUCCESS); break;
case SSL_ECH_STATUS_NOT_TRIED: lstr = s(SSL_ECH_STATUS_NOT_TRIED); break;
case SSL_ECH_STATUS_FAILED: lstr = s(SSL_ECH_STATUS_FAILED); break;
case SSL_ECH_STATUS_BAD_NAME: lstr = s(SSL_ECH_STATUS_BAD_NAME); break;
case SSL_ECH_STATUS_BAD_CALL: lstr = s(SSL_ECH_STATUS_BAD_CALL); break;
case SSL_ECH_STATUS_GREASE: lstr = s(SSL_ECH_STATUS_GREASE); break;
case SSL_ECH_STATUS_BACKEND: lstr = s(SSL_ECH_STATUS_BACKEND); break;
default: lstr = ""; break;
}
#undef s
chunk_printf(buf, "%s", lstr);
OPENSSL_free(sni_ech);
OPENSSL_free(sni_clr);
return 1;
}
/* If ECH succeeded, return the outer SNI value seen */
int conn_get_ech_outer_sni(struct connection *conn, struct buffer *buf)
{
struct ssl_sock_ctx *ctx = conn_get_ssl_sock_ctx(conn);
char *sni_ech = NULL;
char *sni_clr = NULL;
if (!ctx)
return 0;
if (SSL_ech_get1_status(ctx->ssl, &sni_ech, &sni_clr)
== SSL_ECH_STATUS_SUCCESS && sni_clr != NULL)
chunk_printf(buf, "%s", sni_clr);
OPENSSL_free(sni_ech);
OPENSSL_free(sni_clr);
return 1;
}
static int bind_parse_ech(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) static int bind_parse_ech(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
{ {
@ -97,4 +149,5 @@ static struct bind_kw_list bind_kws = { "SSL", { }, {
INITCALL1(STG_REGISTER, bind_register_keywords, &bind_kws); INITCALL1(STG_REGISTER, bind_register_keywords, &bind_kws);
#endif #endif

View File

@ -33,6 +33,9 @@
#include <haproxy/stconn.h> #include <haproxy/stconn.h>
#include <haproxy/tools.h> #include <haproxy/tools.h>
#include <haproxy/vars.h> #include <haproxy/vars.h>
#ifdef USE_ECH
#include <haproxy/ech.h>
#endif
/***** Below are some sample fetching functions for ACL/patterns *****/ /***** Below are some sample fetching functions for ACL/patterns *****/
@ -1880,6 +1883,48 @@ smp_fetch_ssl_fc_sni(const struct arg *args, struct sample *smp, const char *kw,
#endif #endif
} }
#ifdef USE_ECH
static int
smp_fetch_ssl_fc_ech_status(const struct arg *args, struct sample *smp,
const char *kw, void *private)
{
struct buffer *smp_trash;
struct connection *conn;
smp->flags = SMP_F_VOL_SESS | SMP_F_CONST;
smp->data.type = SMP_T_STR;
conn = objt_conn(smp->sess->origin);
if (!conn)
return 0;
smp_trash = get_trash_chunk();
if (conn_get_ech_status(conn, smp_trash) == 1) {
smp->data.u.str.area = smp_trash->area;
smp->data.u.str.data = smp_trash->data;
}
return 1;
}
static int
smp_fetch_ssl_fc_ech_outer_sni(const struct arg *args, struct sample *smp,
const char *kw, void *private)
{
struct buffer *smp_trash;
struct connection *conn;
smp->flags = SMP_F_VOL_SESS | SMP_F_CONST;
smp->data.type = SMP_T_STR;
conn = objt_conn(smp->sess->origin);
if (!conn)
return 0;
smp_trash = get_trash_chunk();
if (conn_get_ech_outer_sni(conn, smp_trash) == 1) {
smp->data.u.str.area = smp_trash->area;
smp->data.u.str.data = smp_trash->data;
}
return 1;
}
#endif
/* binary, returns tls client hello cipher list. /* binary, returns tls client hello cipher list.
* Arguments: filter_option (0,1) * Arguments: filter_option (0,1)
*/ */
@ -2572,6 +2617,10 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
#endif #endif
{ "ssl_fc_sni", smp_fetch_ssl_fc_sni, 0, NULL, SMP_T_STR, SMP_USE_L5CLI }, { "ssl_fc_sni", smp_fetch_ssl_fc_sni, 0, NULL, SMP_T_STR, SMP_USE_L5CLI },
#ifdef USE_ECH
{ "ssl_fc_ech_status", smp_fetch_ssl_fc_ech_status, 0, NULL, SMP_T_STR, SMP_USE_L5CLI },
{ "ssl_fc_ech_outer_sni", smp_fetch_ssl_fc_ech_outer_sni, 0, NULL, SMP_T_STR, SMP_USE_L5CLI },
#endif
{ "ssl_fc_cipherlist_bin", smp_fetch_ssl_fc_cl_bin, ARG1(0,SINT), NULL, SMP_T_STR, SMP_USE_L5CLI }, { "ssl_fc_cipherlist_bin", smp_fetch_ssl_fc_cl_bin, ARG1(0,SINT), NULL, SMP_T_STR, SMP_USE_L5CLI },
{ "ssl_fc_cipherlist_hex", smp_fetch_ssl_fc_cl_hex, ARG1(0,SINT), NULL, SMP_T_BIN, SMP_USE_L5CLI }, { "ssl_fc_cipherlist_hex", smp_fetch_ssl_fc_cl_hex, ARG1(0,SINT), NULL, SMP_T_BIN, SMP_USE_L5CLI },
{ "ssl_fc_cipherlist_str", smp_fetch_ssl_fc_cl_str, ARG1(0,SINT), NULL, SMP_T_STR, SMP_USE_L5CLI }, { "ssl_fc_cipherlist_str", smp_fetch_ssl_fc_cl_str, ARG1(0,SINT), NULL, SMP_T_STR, SMP_USE_L5CLI },