Stream: ssl_verify for PP2_TYPE_SSL and duplicate TLV detection.

Add "ssl_verify" as a named alias in proxy_protocol_tlv.  It sets
the 4-byte verify field in the PP2_TYPE_SSL (0x20) wrapper TLV.
The value is evaluated as a complex expression (may contain
variables) and parsed as a decimal integer at request time.
When ssl_verify is configured without any ssl_* sub-TLVs a
minimal 0x20 TLV containing only the SSL header is emitted.
Without ssl_verify the verify field defaults to 0xFFFFFFFF.

Add duplicate TLV type detection at config time: a second
proxy_protocol_tlv directive with the same type (or the same
ssl sub-type, or a second ssl_verify) is now rejected with
an EMERG error.
This commit is contained in:
Vadim Zhestikov 2026-04-16 13:40:38 -07:00
parent 31e2e582c4
commit 7ca2f1a0e9

View file

@ -22,6 +22,7 @@ typedef struct {
typedef struct {
ngx_uint_t type;
ngx_uint_t is_ssl_sub;
ngx_uint_t is_ssl_verify;
ngx_stream_complex_value_t *value;
} ngx_stream_proxy_protocol_tlv_t;
@ -864,7 +865,9 @@ ngx_stream_proxy_build_tlvs(ngx_stream_session_t *s, ngx_array_t *conf_tlvs,
size_t *sizep)
{
u_char *blob, *q;
ngx_uint_t i, n_ssl;
ngx_int_t verify_i;
ngx_uint_t i, n_ssl, has_ssl_verify;
uint32_t verify;
size_t ssl_sub_total;
ngx_str_t *vals;
ngx_array_t *tlvs;
@ -885,31 +888,45 @@ ngx_stream_proxy_build_tlvs(ngx_stream_session_t *s, ngx_array_t *conf_tlvs,
*sizep = 0;
n_ssl = 0;
has_ssl_verify = 0;
verify = 0xFFFFFFFF; /* default: not verified */
ssl_sub_total = 0;
ctlv = conf_tlvs->elts;
/* evaluate all TLV values; tally regular and ssl sub-TLVs separately */
/* evaluate all TLV values; tally regular, ssl sub-TLVs, and ssl_verify */
for (i = 0; i < conf_tlvs->nelts; i++) {
if (ngx_stream_complex_value(s, ctlv[i].value, &vals[i]) != NGX_OK) {
return NULL;
}
if (ctlv[i].is_ssl_sub) {
if (ctlv[i].is_ssl_verify) {
verify_i = ngx_atoi(vals[i].data, vals[i].len);
if (verify_i == NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"invalid PROXY protocol ssl_verify value \"%V\"",
&vals[i]);
return NULL;
}
verify = (uint32_t) verify_i;
has_ssl_verify = 1;
} else if (ctlv[i].is_ssl_sub) {
n_ssl++;
ssl_sub_total += 3 + vals[i].len; /* sub-TLV: type(1)+len(2)+val */
} else {
*sizep += 3 + vals[i].len; /* regular TLV wire size */
}
}
/* outer 0x20 TLV: type(1)+len(2)+client(1)+verify(4)+sub-TLVs */
if (n_ssl) {
if (n_ssl || has_ssl_verify) {
*sizep += 3 + 5 + ssl_sub_total;
}
/* emit regular TLVs in configured order */
for (i = 0; i < conf_tlvs->nelts; i++) {
if (ctlv[i].is_ssl_sub) {
if (ctlv[i].is_ssl_sub || ctlv[i].is_ssl_verify) {
continue;
}
@ -922,8 +939,8 @@ ngx_stream_proxy_build_tlvs(ngx_stream_session_t *s, ngx_array_t *conf_tlvs,
tlv->value = vals[i];
}
/* assemble compound PP2_TYPE_SSL (0x20) TLV if any ssl sub-TLVs present */
if (n_ssl) {
/* assemble compound PP2_TYPE_SSL (0x20) TLV */
if (n_ssl || has_ssl_verify) {
blob = ngx_palloc(s->connection->pool, 5 + ssl_sub_total);
if (blob == NULL) {
return NULL;
@ -934,11 +951,11 @@ ngx_stream_proxy_build_tlvs(ngx_stream_session_t *s, ngx_array_t *conf_tlvs,
/* PP2_CLIENT_SSL (0x01): client connected over SSL/TLS */
*q++ = 0x01;
/* verify = 0xFFFFFFFF: certificate not verified */
*q++ = 0xff;
*q++ = 0xff;
*q++ = 0xff;
*q++ = 0xff;
/* verify field in network byte order */
*q++ = (u_char) (verify >> 24);
*q++ = (u_char) (verify >> 16);
*q++ = (u_char) (verify >> 8);
*q++ = (u_char) verify;
for (i = 0; i < conf_tlvs->nelts; i++) {
if (!ctlv[i].is_ssl_sub) {
@ -3078,20 +3095,22 @@ typedef struct {
ngx_str_t name;
ngx_uint_t type;
ngx_uint_t is_ssl_sub;
ngx_uint_t is_ssl_verify;
} ngx_stream_proxy_tlv_name_t;
static ngx_stream_proxy_tlv_name_t ngx_stream_proxy_tlv_names[] = {
{ ngx_string("alpn"), 0x01, 0 },
{ ngx_string("authority"), 0x02, 0 },
{ ngx_string("unique_id"), 0x05, 0 },
{ ngx_string("ssl_version"), 0x21, 1 },
{ ngx_string("ssl_cn"), 0x22, 1 },
{ ngx_string("ssl_cipher"), 0x23, 1 },
{ ngx_string("ssl_sig_alg"), 0x24, 1 },
{ ngx_string("ssl_key_alg"), 0x25, 1 },
{ ngx_string("netns"), 0x30, 0 },
{ ngx_null_string, 0x00, 0 }
{ ngx_string("alpn"), 0x01, 0, 0 },
{ ngx_string("authority"), 0x02, 0, 0 },
{ ngx_string("unique_id"), 0x05, 0, 0 },
{ ngx_string("ssl_verify"), 0x00, 0, 1 },
{ ngx_string("ssl_version"), 0x21, 1, 0 },
{ ngx_string("ssl_cn"), 0x22, 1, 0 },
{ ngx_string("ssl_cipher"), 0x23, 1, 0 },
{ ngx_string("ssl_sig_alg"), 0x24, 1, 0 },
{ ngx_string("ssl_key_alg"), 0x25, 1, 0 },
{ ngx_string("netns"), 0x30, 0, 0 },
{ ngx_null_string, 0x00, 0, 0 }
};
@ -3100,17 +3119,19 @@ ngx_stream_proxy_protocol_tlv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_stream_proxy_srv_conf_t *pscf = conf;
ngx_uint_t j;
ngx_int_t type;
ngx_uint_t is_ssl_sub;
ngx_uint_t is_ssl_sub, is_ssl_verify;
ngx_str_t *value;
ngx_stream_compile_complex_value_t ccv;
ngx_stream_proxy_protocol_tlv_t *tlv;
ngx_stream_proxy_protocol_tlv_t *tlv, *existing;
ngx_stream_proxy_tlv_name_t *nm;
value = cf->args->elts;
type = -1;
is_ssl_sub = 0;
is_ssl_verify = 0;
/* check named aliases first */
for (nm = ngx_stream_proxy_tlv_names; nm->name.len; nm++) {
@ -3119,6 +3140,7 @@ ngx_stream_proxy_protocol_tlv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
type = (ngx_int_t) nm->type;
is_ssl_sub = nm->is_ssl_sub;
is_ssl_verify = nm->is_ssl_verify;
break;
}
}
@ -3142,7 +3164,7 @@ ngx_stream_proxy_protocol_tlv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
}
}
if (type == 0x03) {
if (!is_ssl_verify && type == 0x03) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"TLV type 0x03 is reserved for CRC32c checksum, "
"use the \"proxy_protocol_crc32c\" directive");
@ -3155,6 +3177,28 @@ ngx_stream_proxy_protocol_tlv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
if (pscf->proxy_protocol_tlvs == NULL) {
return NGX_CONF_ERROR;
}
} else {
/* duplicate detection */
existing = pscf->proxy_protocol_tlvs->elts;
for (j = 0; j < pscf->proxy_protocol_tlvs->nelts; j++) {
if (is_ssl_verify) {
if (existing[j].is_ssl_verify) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"duplicate PROXY protocol TLV "
"\"ssl_verify\"");
return NGX_CONF_ERROR;
}
} else if (!existing[j].is_ssl_verify
&& existing[j].is_ssl_sub == is_ssl_sub
&& existing[j].type == (ngx_uint_t) type)
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"duplicate PROXY protocol TLV type \"%V\"",
&value[1]);
return NGX_CONF_ERROR;
}
}
}
tlv = ngx_array_push(pscf->proxy_protocol_tlvs);
@ -3164,6 +3208,7 @@ ngx_stream_proxy_protocol_tlv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
tlv->type = (ngx_uint_t) type;
tlv->is_ssl_sub = is_ssl_sub;
tlv->is_ssl_verify = is_ssl_verify;
tlv->value = ngx_palloc(cf->pool, sizeof(ngx_stream_complex_value_t));
if (tlv->value == NULL) {