Add support for passsite substring match

Now the site field in PassSite option can have an '*' suffix to search
for a match anywhere in sni or common names. Note that this is not a
regex or wildcard search.

Previously, we only supported exact matches in sni and between slashes
in common names. This change makes it possible to cover multiple sites
in one PassSite option. In fact, without this change, certain sites
could not be added as passsite, because it was impossible to know their
subdomain names beforehand, for example *.fbcdn.net, which may have many
subdomain names in place of asterisk.

So to use substring match, append an '*' to a site name in PassSite
option (the asterisk is removed before substring search). For example,
use ".fbcdn.net*" to match all subdomains of fbcdn.net, notice the
asterisk at the end.

We also add a warning log starting with "Closing on ssl error without
passsite match" to report sites that can be added as passsite, which is
expected to help in writing PassSite rules.

Also, we now set dstaddr_str earlier in conn handling, so we can print
it in debug logs. This also helps in IDLE and EXPIRED conn logs.
pull/48/head
Soner Tari 3 years ago
parent 9a7e2c35f3
commit f2d4ef61c9

@ -305,7 +305,7 @@ PassSite.
Per-site filters can be defined using client IP addresses, users, and
description keywords. If the UserAuth option is disabled, only client IP
addresses can be used in PassSite filters. Multiple sites can be defined, one
on each line.
on each line. PassSite rules can search for exact or substring matches.
### Logging

@ -417,10 +417,6 @@ protopassthrough_bev_eventcb(struct bufferevent *bev, short events, void *arg)
if (events & BEV_EVENT_CONNECTED) {
if (ctx->connected) {
// @attention dstaddr may not have been set by the original proto.
if (pxy_set_dstaddr(ctx) == -1) {
return;
}
#ifdef HAVE_LOCAL_PROCINFO
if (protopassthrough_prepare_logging(ctx) == -1) {
return;

@ -96,6 +96,11 @@ protossl_log_ssl_error(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
ERR_GET_FUNC(sslerr), STRORDASH(ERR_func_error_string(sslerr)));
}
}
if (ctx->spec->opts->passsites && !ctx->sslctx->passsite) {
log_err_level_printf(LOG_WARNING, "Closing on ssl error without passsite match: %s:%s, %s:%s, %s, %s, %s, %s\n",
STRORDASH(ctx->srchost_str), STRORDASH(ctx->srcport_str), STRORDASH(ctx->dsthost_str), STRORDASH(ctx->dstport_str),
STRORDASH(ctx->user), STRORDASH(ctx->desc), STRORDASH(ctx->sslctx->sni), STRORDASH(ctx->sslctx->ssl_names));
}
}
int
@ -637,6 +642,13 @@ protossl_pass_site(pxy_conn_ctx_t *ctx, char *site)
return -1;
}
int any = 0;
if (_site[len - 2] == '*') {
any = 1;
_site[len - 2] = '/';
len--;
}
// Replace slash with null
_site[len - 1] = '\0';
// Skip the first slash
@ -644,10 +656,18 @@ protossl_pass_site(pxy_conn_ctx_t *ctx, char *site)
// @attention Make sure sni is not null
// SNI: "example.com"
if (ctx->sslctx->sni && !strcmp(ctx->sslctx->sni, s)) {
log_finest_va("Match with sni: %s", ctx->sslctx->sni);
rv = 1;
goto out;
if (ctx->sslctx->sni) {
if (any) {
if (strstr(ctx->sslctx->sni, s)) {
log_finest_va("Match substring in sni: %s, %s", ctx->sslctx->sni, s);
rv = 1;
goto out;
}
} else if (!strcmp(ctx->sslctx->sni, s)) {
log_finest_va("Match exact with sni: %s", ctx->sslctx->sni);
rv = 1;
goto out;
}
}
// @attention Make sure ssl_names is not null
@ -655,9 +675,17 @@ protossl_pass_site(pxy_conn_ctx_t *ctx, char *site)
goto out;
}
if (any) {
if (strstr(ctx->sslctx->ssl_names, s)) {
log_finest_va("Match substring in common names: %s, %s", ctx->sslctx->ssl_names, s);
rv = 1;
}
goto out;
}
// Single common name: "example.com"
if (!strcmp(ctx->sslctx->ssl_names, s)) {
log_finest_va("Match with single common name: %s", ctx->sslctx->ssl_names);
log_finest_va("Match exact with single common name: %s", ctx->sslctx->ssl_names);
rv = 1;
goto out;
}
@ -667,14 +695,14 @@ protossl_pass_site(pxy_conn_ctx_t *ctx, char *site)
// First common name: "example.com/"
if (strstr(ctx->sslctx->ssl_names, s) == ctx->sslctx->ssl_names) {
log_finest_va("Match with the first common name: %s, %s", ctx->sslctx->ssl_names, s);
log_finest_va("Match exact with the first common name: %s, %s", ctx->sslctx->ssl_names, s);
rv = 1;
goto out;
}
// Middle common name: "/example.com/"
if (strstr(ctx->sslctx->ssl_names, _site)) {
log_finest_va("Match with a middle common name: %s, %s", ctx->sslctx->ssl_names, _site);
log_finest_va("Match exact with a middle common name: %s, %s", ctx->sslctx->ssl_names, _site);
rv = 1;
goto out;
}
@ -684,7 +712,7 @@ protossl_pass_site(pxy_conn_ctx_t *ctx, char *site)
// Last common name: "/example.com"
if (strstr(ctx->sslctx->ssl_names, _site) == ctx->sslctx->ssl_names + strlen(ctx->sslctx->ssl_names) - strlen(_site)) {
log_finest_va("Match with the last common name: %s, %s", ctx->sslctx->ssl_names, _site);
log_finest_va("Match exact with the last common name: %s, %s", ctx->sslctx->ssl_names, _site);
rv = 1;
}
out:
@ -736,28 +764,31 @@ protossl_srcssl_create(pxy_conn_ctx_t *ctx, SSL *origssl)
}
passsite_t *passsite = ctx->spec->opts->passsites;
while (passsite) {
if (protossl_pass_user(ctx, passsite)) {
int rv = protossl_pass_site(ctx, passsite->site);
if (rv == 1) {
// Do not print the surrounding slashes
if (passsite) {
while (passsite) {
if (protossl_pass_user(ctx, passsite)) {
int rv = protossl_pass_site(ctx, passsite->site);
if (rv == 1) {
// Do not print the surrounding slashes
#ifndef WITHOUT_USERAUTH
log_err_level_printf(LOG_WARNING, "Found pass site: %.*s for user %s and keyword %s\n", (int)strlen(passsite->site) - 2, passsite->site + 1,
passsite->ip ? passsite->ip : (passsite->all ? "*" : STRORDASH(passsite->user)), STRORDASH(passsite->keyword));
log_err_level_printf(LOG_WARNING, "Found pass site: %.*s for user %s and keyword %s\n", (int)strlen(passsite->site) - 2, passsite->site + 1,
passsite->ip ? passsite->ip : (passsite->all ? "*" : STRORDASH(passsite->user)), STRORDASH(passsite->keyword));
#else /* WITHOUT_USERAUTH */
log_err_level_printf(LOG_WARNING, "Found pass site: %.*s for ip %s\n", (int)strlen(passsite->site) - 2, passsite->site + 1, STRORDASH(passsite->ip));
log_err_level_printf(LOG_WARNING, "Found pass site: %.*s for ip %s\n", (int)strlen(passsite->site) - 2, passsite->site + 1, STRORDASH(passsite->ip));
#endif /* WITHOUT_USERAUTH */
// Differentiate passsite from passthrough option by raising the passsite flag
ctx->sslctx->passsite = 1;
cert_free(cert);
return NULL;
} else if (rv == -1) {
// enomem
cert_free(cert);
return NULL;
// Differentiate passsite from passthrough option by raising the passsite flag
ctx->sslctx->passsite = 1;
cert_free(cert);
return NULL;
} else if (rv == -1) {
// enomem
cert_free(cert);
return NULL;
}
}
passsite = passsite->next;
}
passsite = passsite->next;
log_finest_va("No passsite match with sni or common name: %s:%s, %s:%s, %s, %s, %s, %s", STRORDASH(ctx->srchost_str), STRORDASH(ctx->srcport_str), STRORDASH(ctx->dsthost_str), STRORDASH(ctx->dstport_str), STRORDASH(ctx->user), STRORDASH(ctx->desc), STRORDASH(ctx->sslctx->sni), STRORDASH(ctx->sslctx->ssl_names));
}
SSL_CTX *sslctx = protossl_srcsslctx_create(ctx, cert->crt, cert->chain,

@ -1189,11 +1189,6 @@ pxy_opensock_child(pxy_conn_ctx_t *ctx)
int
pxy_setup_child_listener(pxy_conn_ctx_t *ctx)
{
// Set dstaddr here for split mode, otherwise dstaddr cannot be displayed in the logs
if (pxy_set_dstaddr(ctx) == -1) {
return -1;
}
if (!ctx->spec->opts->divert) {
// split mode
return 0;
@ -1372,7 +1367,7 @@ pxy_try_consume_last_input_child(struct bufferevent *bev, pxy_conn_child_ctx_t *
return 0;
}
int
static int NONNULL(1)
pxy_set_dstaddr(pxy_conn_ctx_t *ctx)
{
if (sys_sockaddr_str((struct sockaddr *)&ctx->dstaddr, ctx->dstaddrlen, &ctx->dsthost_str, &ctx->dstport_str) != 0) {
@ -1642,15 +1637,14 @@ pxy_conn_connect(pxy_conn_ctx_t *ctx)
return;
}
// This function may be called more than once for the same conn
// So, set the dstaddr only once
if (!ctx->dsthost_str && (pxy_set_dstaddr(ctx) == -1)) {
return;
}
if (OPTS_DEBUG(ctx->global)) {
char *host, *port;
if (sys_sockaddr_str((struct sockaddr *)&ctx->dstaddr, ctx->dstaddrlen, &host, &port) == 0) {
log_dbg_printf("Connecting to [%s]:%s\n", host, port);
free(host);
free(port);
} else {
log_dbg_printf("Connecting to [?]:?\n");
}
log_dbg_printf("Connecting to [%s]:%s\n", ctx->dsthost_str, ctx->dstport_str);
}
if (ctx->protoctx->connectcb(ctx) == -1) {

@ -376,8 +376,6 @@ void pxy_log_dbg_evbuf_info(pxy_conn_ctx_t *, pxy_conn_desc_t *, pxy_conn_desc_t
unsigned char *pxy_malloc_packet(size_t, pxy_conn_ctx_t *) MALLOC NONNULL(2) WUNRES;
int pxy_set_dstaddr(pxy_conn_ctx_t *) NONNULL(1);
int pxy_try_prepend_sslproxy_header(pxy_conn_ctx_t *ctx, struct evbuffer *, struct evbuffer *) NONNULL(1,2,3);
void pxy_try_remove_sslproxy_header(pxy_conn_child_ctx_t *, unsigned char *, size_t *) NONNULL(1,2,3);

@ -315,7 +315,7 @@ PassSite.
Per-site filters can be defined using client IP addresses, users, and
description keywords. If the UserAuth option is disabled, only client IP
addresses can be used in PassSite filters. Multiple sites can be defined, one
on each line.
on each line. PassSite rules can search for exact or substring matches.
.SH Logging
Logging options include traditional SSLproxy connect and content log files as
well as PCAP files and mirroring decrypted traffic to a network interface.

@ -255,11 +255,12 @@ PassUsers admin
#OpenFilesLimit 1024
# Passthrough sites
# site [(clientaddr|(user|*) [description keyword])]
# site[*] [(clientaddr|(user|*) [description keyword])]
#PassSite example.com
#PassSite example.com 192.168.0.1
#PassSite example.com soner
#PassSite *.google.com * android
#PassSite .fbcdn.net* soner android
# Set divert or split mode of operation
# If set to no, equivalent to -n command line option for split mode.

@ -105,13 +105,16 @@ auth or no matching cert and no CA. Equivalent to -P command line option.
Default: drop
.TP
\fBPassSite STRING\fR
Passthrough site: site [(clientaddr|(user|*) [description keyword])]. If the
site matches SNI or common names in the SSL certificate, the connection is
Passthrough site: site[*] [(clientaddr|(user|*) [description keyword])]. If
the site matches SNI or common names in the SSL certificate, the connection is
passed through the proxy. Per site filters can be defined using client IP
addresses, users, and description keywords. '*' matches all users. User auth
should be enabled for user and description keyword filtering to work.
Case is ignored while matching description keywords. Multiple sites are
allowed, one on each line.
allowed, one on each line. PassSite rules can search for exact or substring
matches. Append an asterisk to the site field to search for substring match.
Note that the substring search is not a regex or wildcard search, and that the
asterisk at the end is removed before search.
.TP
\fBDHGroupParams STRING\fR
Use DH group params from pemfile. Equivalent to -g command line option.

Loading…
Cancel
Save