Stream: support ALPN for proxy_ssl upstream.

Added the proxy_ssl_alpn directive, which sets the list of protocols
to advertise via ALPN during upstream TLS handshakes.  Each argument
is a complex value, so variables are accepted.  In particular,

    proxy_ssl_alpn $ssl_alpn_protocol;

inherits the protocol negotiated in the downstream TLS handshake.

When all evaluated values are empty or absent, no ALPN extension is
sent, equivalent to the directive not being set at all.

Closes #406 on GitHub.
This commit is contained in:
Vadim Zhestikov 2026-02-02 14:46:00 -08:00 committed by VadimZhestikov
parent d7dd7e9ae4
commit 98fc3bb78e

View file

@ -42,6 +42,7 @@ typedef struct {
ngx_str_t ssl_ciphers;
ngx_stream_complex_value_t *ssl_name;
ngx_flag_t ssl_server_name;
ngx_array_t *ssl_alpn;
ngx_flag_t ssl_verify;
ngx_uint_t ssl_verify_depth;
@ -95,6 +96,8 @@ static char *ngx_stream_proxy_bind(ngx_conf_t *cf, ngx_command_t *cmd,
#if (NGX_STREAM_SSL)
static ngx_int_t ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s);
static char *ngx_stream_proxy_ssl_alpn_set_slot(ngx_conf_t *cf,
ngx_command_t *cmd, void *conf);
static char *ngx_stream_proxy_ssl_certificate_cache(ngx_conf_t *cf,
ngx_command_t *cmd, void *conf);
static char *ngx_stream_proxy_ssl_password_file(ngx_conf_t *cf,
@ -105,6 +108,7 @@ static void ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s);
static void ngx_stream_proxy_ssl_handshake(ngx_connection_t *pc);
static void ngx_stream_proxy_ssl_save_session(ngx_connection_t *c);
static ngx_int_t ngx_stream_proxy_ssl_name(ngx_stream_session_t *s);
static ngx_int_t ngx_stream_proxy_ssl_alpn(ngx_stream_session_t *s);
static ngx_int_t ngx_stream_proxy_ssl_certificate(ngx_stream_session_t *s);
static ngx_int_t ngx_stream_proxy_merge_ssl(ngx_conf_t *cf,
ngx_stream_proxy_srv_conf_t *conf, ngx_stream_proxy_srv_conf_t *prev);
@ -304,6 +308,13 @@ static ngx_command_t ngx_stream_proxy_commands[] = {
offsetof(ngx_stream_proxy_srv_conf_t, ssl_server_name),
NULL },
{ ngx_string("proxy_ssl_alpn"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
ngx_stream_proxy_ssl_alpn_set_slot,
NGX_STREAM_SRV_CONF_OFFSET,
0,
NULL },
{ ngx_string("proxy_ssl_verify"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
@ -1043,6 +1054,61 @@ ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s)
}
static char *
ngx_stream_proxy_ssl_alpn_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf)
{
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
ngx_stream_proxy_srv_conf_t *pscf = conf;
ngx_str_t *value;
ngx_uint_t i;
ngx_stream_complex_value_t *cv;
ngx_stream_compile_complex_value_t ccv;
if (pscf->ssl_alpn != NGX_CONF_UNSET_PTR) {
return "is duplicate";
}
value = cf->args->elts;
pscf->ssl_alpn = ngx_array_create(cf->pool, cf->args->nelts - 1,
sizeof(ngx_stream_complex_value_t));
if (pscf->ssl_alpn == NULL) {
return NGX_CONF_ERROR;
}
cv = ngx_array_push_n(pscf->ssl_alpn, cf->args->nelts - 1);
for (i = 1; i < cf->args->nelts; i++) {
ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &value[i];
ccv.complex_value = &cv[i - 1];
if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}
if (cv[i - 1].lengths == NULL && value[i].len > 255) {
return "protocol too long";
}
}
return NGX_CONF_OK;
#else
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the \"proxy_ssl_alpn\" directive requires "
"OpenSSL with ALPN support");
return NGX_CONF_ERROR;
#endif
}
static char *
ngx_stream_proxy_ssl_certificate_cache(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf)
@ -1200,6 +1266,13 @@ ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s)
}
}
if (pscf->ssl_alpn) {
if (ngx_stream_proxy_ssl_alpn(s) != NGX_OK) {
ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
return;
}
}
if (pscf->ssl_certificate
&& pscf->ssl_certificate->value.len
&& (pscf->ssl_certificate->lengths
@ -1399,6 +1472,82 @@ done:
}
static ngx_int_t
ngx_stream_proxy_ssl_alpn(ngx_stream_session_t *s)
{
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
size_t len;
u_char *p, *buf;
ngx_str_t proto;
ngx_uint_t i;
ngx_connection_t *c;
ngx_stream_upstream_t *u;
ngx_stream_complex_value_t *cv;
ngx_stream_proxy_srv_conf_t *pscf;
pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);
u = s->upstream;
c = u->peer.connection;
len = 0;
cv = pscf->ssl_alpn->elts;
for (i = 0; i < pscf->ssl_alpn->nelts; i++) {
if (ngx_stream_complex_value(s, &cv[i], &proto) != NGX_OK) {
return NGX_ERROR;
}
if (proto.len == 0 || proto.len > 255) {
continue;
}
len += 1 + proto.len;
}
if (len == 0) {
return NGX_OK;
}
buf = ngx_pnalloc(c->pool, len);
if (buf == NULL) {
return NGX_ERROR;
}
p = buf;
for (i = 0; i < pscf->ssl_alpn->nelts; i++) {
if (ngx_stream_complex_value(s, &cv[i], &proto) != NGX_OK) {
return NGX_ERROR;
}
if (proto.len == 0 || proto.len > 255) {
continue;
}
ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0,
"upstream SSL ALPN: \"%V\"", &proto);
*p++ = proto.len;
p = ngx_cpymem(p, proto.data, proto.len);
}
if (SSL_set_alpn_protos(c->ssl->connection, buf, p - buf) != 0) {
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
"SSL_set_alpn_protos() failed");
return NGX_ERROR;
}
#endif
return NGX_OK;
}
static ngx_int_t
ngx_stream_proxy_ssl_certificate(ngx_stream_session_t *s)
{
@ -2225,6 +2374,7 @@ ngx_stream_proxy_create_srv_conf(ngx_conf_t *cf)
conf->ssl_session_reuse = NGX_CONF_UNSET;
conf->ssl_name = NGX_CONF_UNSET_PTR;
conf->ssl_server_name = NGX_CONF_UNSET;
conf->ssl_alpn = NGX_CONF_UNSET_PTR;
conf->ssl_verify = NGX_CONF_UNSET;
conf->ssl_verify_depth = NGX_CONF_UNSET_UINT;
conf->ssl_certificate = NGX_CONF_UNSET_PTR;
@ -2300,6 +2450,8 @@ ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_value(conf->ssl_server_name, prev->ssl_server_name, 0);
ngx_conf_merge_ptr_value(conf->ssl_alpn, prev->ssl_alpn, NULL);
ngx_conf_merge_value(conf->ssl_verify, prev->ssl_verify, 0);
ngx_conf_merge_uint_value(conf->ssl_verify_depth,