Add ReconnectSSL option to enforce SSL options in struct filtering rules

The ReconnectSSL option allows rule developers to write struct filtering
rules using SNI and CN SSL specifications to override the SSL
configuration of a connection.

Otherwise, without this new option, filtering rules cannot change SSL
options using SSL filtering fields to match connections (the SSL config
in the rule would not have any effect on the server side of the matching
connection). Without ReconnectSSL, only DstIP and DstPort fields can be
used to override the SSL config of a connection.

If the ReconnectSSL option in a struct filtering rule is set, we
disconnect and free the server side of the matching SSL connection, and
reconnect it with the SSL options in the matching struct filtering rule.
This enforces the SSL config in the rule.

Do not use the ReconnectSSL option if server disconnect is not desirable
or acceptable in your case.
pull/48/head
Soner Tari 3 years ago
parent f744c2c77a
commit 8f63ec7f82

@ -23,6 +23,8 @@
# Multiple LogAction lines allowed
LogAction ([!]connect|[!]master|[!]cert|[!]content|[!]pcap|[!]mirror|$macro|[!]*)
ReconnectSSL (yes|no)
# Connection options
DenyOCSP (yes|no)
Passthrough (yes|no)

@ -338,6 +338,8 @@ The syntax of structured filtering rules is as follows:
# Multiple LogAction lines allowed
LogAction ([!]connect|[!]master|[!]cert|[!]content|[!]pcap|[!]mirror|$macro|[!]*)
ReconnectSSL (yes|no)
# Connection options
DenyOCSP (yes|no)
Passthrough (yes|no)
@ -467,11 +469,16 @@ rules should enable them specifically.
Connection options specified in a structured filtering rule can have any
effect only if the rule matches the connection before proxyspec or global
options are applied. Otherwise, proxyspec or global connection options already
applied to a connection cannot be overriden by the connection option specified
in the matching structured filtering rule. For example, SSL/TLS options of a
connection cannot be changed after the SSL/TLS connection is established. So,
SSL type of rules cannot modify SSL/TLS options of a connection.
options are applied. Otherwise, the proxyspec or global connection options
already applied to a connection cannot be overriden by the connection options
specified in the matching structured filtering rule. For example, SSL/TLS
options of a connection cannot be changed after the SSL/TLS connection is
established. So, normally SSL type of rules cannot modify SSL/TLS options of a
connection, but you can use the ReconnectSSL option to reconnect the server
side of an SSL connection to enforce the SSL/TLS options in the SSL type of
filtering rules. In other words, the ReconnectSSL option allows for using the
SNI and CN fields in stuctured filtering rules to match connections and change
their SSL configuration.
Macro expansion is supported. The `Define` option can be used for defining
macros to be used in filtering rules. Macro names must start with a `$` char.

@ -2416,7 +2416,11 @@ filter_rule_struct_translate(filter_rule_t *rule, UNUSED conn_opts_t *conn_opts,
fprintf(stderr, "Error in conf: Unknown LogAction '%s' on line %d\n", value, line_num);
return -1;
}
} else {
}
else if (equal(name, "ReconnectSSL")) {
// Already processed by the parser
}
else {
// This should have been handled by the parser, but in case
fprintf(stderr, "Error in conf: Unknown option '%s' on line %d\n", name, line_num);
return -1;
@ -2626,6 +2630,15 @@ filter_rule_struct_parse(name_value_lines_t nvls[], int *nvls_size, conn_opts_t
else if (equal(name, "LogAction")) {
// LogAction can be used more than once to define multiple log actions, if not using macros
}
else if (equal(name, "ReconnectSSL")) {
int yes = check_value_yesno(value, "ReconnectSSL", line_num);
if (yes == -1)
return -1;
conn_opts->reconnect_ssl = yes;
#ifdef DEBUG_OPTS
log_dbg_printf("ReconnectSSL: %u\n", conn_opts->reconnect_ssl);
#endif /* DEBUG_OPTS */
}
else {
fprintf(stderr, "Error in conf: Unknown option '%s' on line %d\n", name, line_num);
return -1;

@ -586,6 +586,7 @@ conn_opts_copy(conn_opts_t *conn_opts, const char *argv0, tmp_opts_t *tmp_opts)
cops->user_timeout = conn_opts->user_timeout;
#endif /* !WITHOUT_USERAUTH */
cops->validate_proto = conn_opts->validate_proto;
cops->reconnect_ssl = conn_opts->reconnect_ssl;
cops->max_http_header_size = conn_opts->max_http_header_size;
// Pass NULL as tmp_opts param, so we don't reassign the var to itself
@ -2512,7 +2513,7 @@ is_yesno(const char *value)
return -1;
}
static int
int
check_value_yesno(const char *value, const char *name, int line_num)
{
int rv;

@ -133,6 +133,8 @@ typedef struct conn_opts {
unsigned int user_timeout;
#endif /* !WITHOUT_USERAUTH */
unsigned int validate_proto : 1;
// Used with struct filtering rules only
unsigned int reconnect_ssl : 1;
unsigned int max_http_header_size;
} conn_opts_t;
@ -347,6 +349,7 @@ int global_set_debug_level(const char *) NONNULL(1) WUNRES;
void global_set_statslog(global_t *) NONNULL(1);
int is_yesno(const char *) WUNRES;
int check_value_yesno(const char *, const char *, int) WUNRES;
int get_name_value(char *, char **, const char, int) WUNRES;
int global_set_option(global_t *, const char *, const char *, char **, tmp_opts_t *) NONNULL(1,2,3,5) WUNRES;
int global_set_leafkey(global_t *, const char *, const char *) NONNULL(1,2,3) WUNRES;

@ -749,6 +749,32 @@ protossl_filter(pxy_conn_ctx_t *ctx, filter_list_t *list)
return NULL;
}
static void
protossl_reconnect_srvdst(pxy_conn_ctx_t *ctx)
{
log_fine("ENTER");
// Reconnect only once
ctx->sslctx->reconnected_ssl = 1;
ctx->srvdst.free(ctx->srvdst.bev, ctx);
ctx->srvdst.bev = NULL;
ctx->srvdst.ssl = NULL;
ctx->connected = 0;
if (protossl_conn_connect(ctx) == -1) {
if (ctx->term || ctx->enomem) {
pxy_conn_free(ctx, ctx->term ? ctx->term_requestor : 1);
return;
}
}
if (bufferevent_socket_connect(ctx->srvdst.bev, (struct sockaddr *)&ctx->dstaddr, ctx->dstaddrlen) == -1) {
log_err_level(LOG_CRIT, "bufferevent_socket_connect for srvdst failed");
pxy_conn_free(ctx, ctx->term ? ctx->term_requestor : 1);
}
}
static int
protossl_apply_filter(pxy_conn_ctx_t *ctx)
{
@ -809,8 +835,20 @@ protossl_apply_filter(pxy_conn_ctx_t *ctx)
ctx->log_mirror = 0;
#endif /* !WITHOUT_MIRROR */
if (a->conn_opts)
if (a->conn_opts) {
ctx->conn_opts = a->conn_opts;
if (ctx->conn_opts->reconnect_ssl) {
// Reconnect srvdst only once, if ReconnectSSL set in the rule
if (!ctx->sslctx->reconnected_ssl) {
protossl_reconnect_srvdst(ctx);
// Return immediately to avoid applying deferred pass action
return 1;
} else {
log_finest("Already reconnected once, will not reconnect again");
}
}
}
}
// Cannot defer pass action any longer
@ -1538,6 +1576,9 @@ protossl_setup_src_ssl(pxy_conn_ctx_t *ctx)
// report protocol change by returning 1
return 1;
}
else if (ctx->sslctx->reconnected_ssl) {
return -1;
}
pxy_conn_term(ctx, 1);
return -1;
}

@ -131,6 +131,8 @@ struct ssl_ctx {
unsigned int immutable_cert : 1; /* 1 if the cert cannot be changed */
unsigned int generated_cert : 1; /* 1 if we generated a new cert */
unsigned int have_sslerr : 1; /* 1 if we have an ssl error */
// Set after reconnecting srvdst to enforce the SSL options in matching struct filtering rule
unsigned int reconnected_ssl : 1; /* 1 if we have reconnected srvdst */
/* server name indicated by client in SNI TLS extension */
char *sni;

@ -351,6 +351,8 @@ FilterRule {
# Multiple LogAction lines allowed
LogAction ([!]connect|[!]master|[!]cert|[!]content|[!]pcap|[!]mirror|$macro|[!]*)
ReconnectSSL (yes|no)
# Connection options
DenyOCSP (yes|no)
Passthrough (yes|no)
@ -481,11 +483,16 @@ rules should enable them specifically.
.LP
Connection options specified in a structured filtering rule can have any
effect only if the rule matches the connection before proxyspec or global
options are applied. Otherwise, proxyspec or global connection options already
applied to a connection cannot be overriden by the connection option specified
in the matching structured filtering rule. For example, SSL/TLS options of a
connection cannot be changed after the SSL/TLS connection is established. So,
SSL type of rules cannot modify SSL/TLS options of a connection.
options are applied. Otherwise, the proxyspec or global connection options
already applied to a connection cannot be overriden by the connection options
specified in the matching structured filtering rule. For example, SSL/TLS
options of a connection cannot be changed after the SSL/TLS connection is
established. So, normally SSL type of rules cannot modify SSL/TLS options of a
connection, but you can use the ReconnectSSL option to reconnect the server
side of an SSL connection to enforce the SSL/TLS options in the SSL type of
filtering rules. In other words, the ReconnectSSL option allows for using the
SNI and CN fields in stuctured filtering rules to match connections and change
their SSL configuration.
.LP
Macro expansion is supported. The Define option can be used for defining
macros to be used in filtering rules. Macro names must start with a $ char.

@ -340,6 +340,8 @@ PassUsers admin
# # Multiple LogAction lines allowed
# LogAction ([!]connect|[!]master|[!]cert|[!]content|[!]pcap|[!]mirror|$macro|[!]*)
#
# ReconnectSSL (yes|no)
#
# # Connection options
# DenyOCSP (yes|no)
# Passthrough (yes|no)

@ -414,6 +414,8 @@ DstPort
.br
LogAction
.br
ReconnectSSL
.br
DenyOCSP
.br
Passthrough

@ -0,0 +1,203 @@
{
"comment": "Tests for Divert struct filtering rules with ReconnectSSL, HTTP request headers: SSLproxy, Connection, Upgrade, Keep-Alive, Accept-Encoding, Via, X-Forwarded-For, and Referer",
"configs": {
"1": {
"proto": {
"proto": "ssl",
"crt": "server.crt",
"key": "server.key"
},
"client": {
"ip": "127.0.0.1",
"port": "8213"
},
"server": {
"ip": "127.0.0.1",
"port": "9213"
}
}
},
"tests": {
"1": {
"comment": "Divert struct filtering rule removes any extra SSLproxy line, and appends Connection: close",
"states": {
"1": {
"testend": "server",
"cmd": "reconnect",
"payload": "",
"comment": "ReconnectSSL rules cause sslproxy to disconnect/reconnect to the server, so the reconnect cmd instructs the server to allow it"
},
"2": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nSSLproxy: sslproxy\r\n\r\n"
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
}
}
},
"2": {
"comment": "Divert struct filtering rule removes all extra SSLproxy lines",
"states": {
"1": {
"testend": "server",
"cmd": "reconnect",
"payload": ""
},
"2": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nSSLproxy: sslproxy\r\nSSLproxy: sslproxy\r\n\r\n"
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
}
}
},
"3": {
"comment": "Divert struct filtering rule changes Connection header to close",
"states": {
"1": {
"testend": "server",
"cmd": "reconnect",
"payload": ""
},
"2": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: Keep-Alive\r\n\r\n"
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
}
}
},
"4": {
"comment": "Divert struct filtering rule suppresses upgrading to SSL/TLS, WebSockets or HTTP/2",
"states": {
"1": {
"testend": "server",
"cmd": "reconnect",
"payload": ""
},
"2": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nUpgrade: websocket\r\n\r\n"
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
}
}
},
"5": {
"comment": "Divert struct filtering rule removes Keep-Alive",
"states": {
"1": {
"testend": "server",
"cmd": "reconnect",
"payload": ""
},
"2": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nKeep-Alive: keep-alive\r\n\r\n"
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
}
}
},
"6": {
"comment": "Divert struct filtering rule removes Accept-Encoding",
"states": {
"1": {
"testend": "server",
"cmd": "reconnect",
"payload": ""
},
"2": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nAccept-Encoding: encoding\r\n\r\n"
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
}
}
},
"7": {
"comment": "Divert struct filtering rule removes Via",
"states": {
"1": {
"testend": "server",
"cmd": "reconnect",
"payload": ""
},
"2": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nVia: via\r\n\r\n"
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
}
}
},
"8": {
"comment": "Divert struct filtering rule removes X-Forwarded-For",
"states": {
"1": {
"testend": "server",
"cmd": "reconnect",
"payload": ""
},
"2": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nX-Forwarded-For: x-forwarded-for\r\n\r\n"
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
}
}
},
"9": {
"comment": "Divert struct filtering rule removes Referer",
"states": {
"1": {
"testend": "server",
"cmd": "reconnect",
"payload": ""
},
"2": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nReferer: referer\r\n\r\n"
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
}
}
}
}
}

@ -1676,3 +1676,78 @@ ProxySpec {
MaxHTTPHeaderSize 8192
}
}
ProxySpec {
Proto https
Addr 127.0.0.1
Port 8213
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9213
Divert yes
# FilterRule below should override these options
DenyOCSP no
Passthrough yes
CACert ca2.crt
CAKey ca2.key
#ClientCert /etc/sslproxy/client.crt
#ClientKey /etc/sslproxy/client.key
#CAChain /etc/sslproxy/chain.crt
#LeafCRLURL http://example.com/example.crl
#DHGroupParams /etc/sslproxy/dh.pem
#ECDHCurve prime256v1
SSLCompression yes
ForceSSLProto tls12
DisableSSLProto tls13
MinSSLProto tls11
MaxSSLProto tls12
Ciphers MEDIUM:HIGH
CipherSuites TLS_AES_128_CCM_SHA256
RemoveHTTPAcceptEncoding no
RemoveHTTPReferer no
#VerifyPeer yes
AllowWrongHost yes
#UserAuth yes
#UserTimeout 300
#UserAuthURL https://192.168.0.1/userdblogin.php
ValidateProto no
MaxHTTPHeaderSize 2048
FilterRule {
Action Match
SrcIp 127.0.0.1
CN comixwall.org
DstPort 9213
LogAction connect
# Reconnect srvdst to apply the SSL config in this rule
ReconnectSSL yes
DenyOCSP yes
Passthrough no
CACert ca.crt
CAKey ca.key
#ClientCert /etc/sslproxy/client.crt
#ClientKey /etc/sslproxy/client.key
#CAChain /etc/sslproxy/chain.crt
#LeafCRLURL http://example.com/example.crl
#DHGroupParams /etc/sslproxy/dh.pem
ECDHCurve prime256v1
SSLCompression no
ForceSSLProto tls13
EnableSSLProto tls13
MinSSLProto tls10
MaxSSLProto tls13
Ciphers MEDIUM:HIGH
CipherSuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
RemoveHTTPAcceptEncoding yes
RemoveHTTPReferer yes
VerifyPeer no
AllowWrongHost no
UserAuth no
#UserTimeout 300
#UserAuthURL https://192.168.0.1/userdblogin.php
ValidateProto yes
MaxHTTPHeaderSize 8192
}
}

@ -58,7 +58,8 @@
"10": "filter_host_testset_2.json",
"11": "filter_uri_testset_1.json",
"12": "filter_uri_testset_2.json",
"13": "filter_struct_testset_1.json"
"13": "filter_struct_testset_1.json",
"14": "filter_struct_reconnect_testset_1.json"
}
}
}

Loading…
Cancel
Save