Skip to content

Commit

Permalink
fix #899; check for sticky cookie before passing request to cache
Browse files Browse the repository at this point in the history
  • Loading branch information
vankoven committed Sep 13, 2018
1 parent 22f5ff5 commit 3f398e7
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 58 deletions.
119 changes: 74 additions & 45 deletions tempesta_fw/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -2488,6 +2488,12 @@ tfw_http_cli_error_resp_and_log(TfwHttpReq *req, int status, const char *msg,
bool nolog;
TfwCliConn *cli_conn = (TfwCliConn *)req->conn;

/*
* A new error response is generated for the request, drop any
* previous request paired with the request.
*/
tfw_http_conn_msg_free(req->pair);

if (attack) {
reply = tfw_blk_flags & TFW_BLK_ATT_REPLY;
nolog = tfw_blk_flags & TFW_BLK_ATT_NOLOG;
Expand Down Expand Up @@ -2620,38 +2626,12 @@ tfw_http_req_cache_service(TfwHttpResp *resp)
static void
tfw_http_req_cache_cb(TfwHttpMsg *msg)
{
int r;
TfwHttpReq *req = (TfwHttpReq *)msg;
TfwSrvConn *srv_conn = NULL;
LIST_HEAD(eq);

TFW_DBG2("%s: req = %p, resp = %p\n", __func__, req, req->resp);

/*
* Sticky cookie module used for HTTP session identification may send
* a response to the client when sticky cookie presence is enforced
* and the cookie is missing from the request.
*
* HTTP session may be required for request scheduling, so obtain it
* first. However, req->sess still may be NULL if sticky cookies are
* not enabled.
*/
r = tfw_http_sess_obtain(req);
switch (r)
{
case TFW_HTTP_SESS_SUCCESS:
break;
case TFW_HTTP_SESS_REDIRECT_SENT:
/* Response sent, nothing to do. */
return;
case TFW_HTTP_SESS_VIOLATE:
goto drop_503;
case TFW_HTTP_SESS_JS_NOT_SUPPORTED:
goto send_503;
default:
goto send_500;
}

if (req->resp) {
tfw_http_req_cache_service(req->resp);
return;
Expand Down Expand Up @@ -2684,20 +2664,6 @@ tfw_http_req_cache_cb(TfwHttpMsg *msg)
tfw_http_req_zap_error(&eq);
goto conn_put;

send_503:
/*
* Requested resource can't be challenged. Don't break response-request
* queue on client side by dropping the request.
*/
tfw_http_send_resp(req, 503, "request dropped:"
" can't send JS challenge.");
TFW_INC_STAT_BH(clnt.msgs_filtout);
return;
drop_503:
tfw_srv_client_drop(req, 503, "request dropped: invalid sticky cookie "
"or js challenge");
TFW_INC_STAT_BH(clnt.msgs_filtout);
return;
send_502:
tfw_http_send_resp(req, 502, "request dropped: processing error");
TFW_INC_STAT_BH(clnt.msgs_otherr);
Expand Down Expand Up @@ -2968,10 +2934,67 @@ tfw_http_req_process(TfwConn *conn, const TfwFsmData *data)
/* The request is fully parsed, fall through and process it. */
}

/* Assign the right virtual host for current request. */
if ((req->vhost = tfw_http_tbl_vhost((TfwMsg *)req, &block)))
/*
* Sticky cookie module must be used before request can reach cache.
* Unauthorised clients mustn't be able to get any resource on
* protected service and stress cache subsystem.
* The module is also quickest way to obtain target VHost and target
* backend server connection.
*
* TODO: #685 Sticky cookie are to be configured per-vhost. Http_tables
* used to assign target vhost can be extremely long, while the client
* uses just a few cookies. Even if different sticky cookies are used
* for each vhost, the sticky module should be faster than
* tfw_http_tbl_vhost().
*/
switch (tfw_http_sess_obtain(req))
{
case TFW_HTTP_SESS_SUCCESS:
/* TODO: #1043 sticky cookie module must assign correct vhost. */
break;

case TFW_HTTP_SESS_REDIRECT_NEED:
/* Response is built and stored in @req->resp. */
/* TODO: #1043 sticky cookie module must assign correct vhost. */
/* TODO: future errors may need @req->resp */
break;

case TFW_HTTP_SESS_VIOLATE:
err.status = 503;
err.reason = "request dropped: sticky cookie challenge was "
"failed";
goto block;

case TFW_HTTP_SESS_JS_NOT_SUPPORTED:
/*
* Requested resource can't be challenged, forward all pending
* responses and close the connection to allow client to recover.
*/
TFW_INC_STAT_BH(clnt.msgs_filtout);
err.status = 503;
err.reason = "request dropped: can't send JS challenge.";
goto drop;

default:
TFW_INC_STAT_BH(clnt.msgs_otherr);
err.status = 500;
err.reason = "request dropped: internal error in Sticky module";
goto drop;
}
/*
* Assign the right virtual host for current request if it wasn't
* assigned by a sticky module. VHost is not assigned if client
* doesn't support cookies or sticky cookies are disabled.
*
* In the same time location may differ between requests, so the
* sticky module can't fill it.
*/
if (!req->vhost)
req->vhost = tfw_http_tbl_vhost((TfwMsg *)req, &block);
if (req->vhost) {
req->location = tfw_location_match(req->vhost, &req->uri_path);
if (unlikely(block)) {
}
else if (unlikely(block)) {
TFW_INC_STAT_BH(clnt.msgs_filtout);
err.status = 403;
err.reason = "request has been filtered out via http table";
Expand All @@ -2986,6 +3009,7 @@ tfw_http_req_process(TfwConn *conn, const TfwFsmData *data)
TFW_DBG3("TFW_HTTP_FSM_REQ_MSG return code %d\n", r);
if (r == TFW_BLOCK) {
err.reason = "parsed request has been filtered out";
err.status = 403;
goto block;
}

Expand Down Expand Up @@ -3075,12 +3099,18 @@ tfw_http_req_process(TfwConn *conn, const TfwFsmData *data)
*/
tfw_http_req_add_seq_queue(req);

/*
* Response is already prepared for the client.
*/
if (req->resp) {
tfw_http_resp_fwd(req->resp);
}
/*
* If no virtual host has been found for current request, there
* is no sense for its further processing, so we drop it, send
* error response to client and move on to the next request.
*/
if (unlikely(!req->vhost)) {
else if (unlikely(!req->vhost)) {
tfw_http_send_resp(req, 404,
"request dropped: can't find virtual host");
TFW_INC_STAT_BH(clnt.msgs_otherr);
Expand Down Expand Up @@ -3139,7 +3169,6 @@ tfw_http_req_process(TfwConn *conn, const TfwFsmData *data)
* and drop all pending requests.
*/
TFW_INC_STAT_BH(clnt.msgs_filtout);
err.status = 403;
tfw_client_block(req, err.status, err.reason);

return TFW_BLOCK;
Expand Down
17 changes: 10 additions & 7 deletions tempesta_fw/http_sess.c
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ tfw_http_sticky_redirect_applied(TfwHttpReq *req)
}

static int
tfw_http_sticky_send_redirect(TfwHttpReq *req, StickyVal *sv)
tfw_http_sticky_build_redirect(TfwHttpReq *req, StickyVal *sv)
{
unsigned long ts_be64 = cpu_to_be64(sv->ts);
TfwStr chunks[3], cookie = { 0 };
Expand Down Expand Up @@ -190,9 +190,12 @@ tfw_http_sticky_send_redirect(TfwHttpReq *req, StickyVal *sv)
return TFW_HTTP_SESS_FAILURE;
}

tfw_http_resp_fwd(resp);

return TFW_HTTP_SESS_REDIRECT_SENT;
/*
* Don't send @resp now: cookie check take place on very early @req
* processing stage, store @resp as @req->resp, the response will be
* sent as soon as @req will be fully processed.
*/
return TFW_HTTP_SESS_REDIRECT_NEED;
}

static int
Expand Down Expand Up @@ -388,7 +391,7 @@ tfw_http_sticky_add(TfwHttpResp *resp)
.flags = 3 << TFW_STR_CN_SHIFT
};

/* See comment from tfw_http_sticky_send_redirect(). */
/* See comment from tfw_http_sticky_build_redirect(). */
bin2hex(buf, &ts_be64, sizeof(ts_be64));
bin2hex(&buf[sizeof(ts_be64) * 2], sess->hmac, sizeof(sess->hmac));

Expand Down Expand Up @@ -429,7 +432,7 @@ tfw_http_sticky_notfound(TfwHttpReq *req)
if (tfw_http_sticky_calc(req, &sv) != 0)
return TFW_HTTP_SESS_FAILURE;

return tfw_http_sticky_send_redirect(req, &sv);
return tfw_http_sticky_build_redirect(req, &sv);
}

#define sess_warn(check, addr, fmt, ...) \
Expand Down Expand Up @@ -530,7 +533,7 @@ tfw_http_sticky_req_process(TfwHttpReq *req, StickyVal *sv)
* keep user experience intact.
*/
if (tfw_http_sticky_verify(req, &cookie_val, sv))
return tfw_http_sticky_send_redirect(req, sv);
return tfw_http_sticky_build_redirect(req, sv);
return TFW_HTTP_SESS_SUCCESS;
}
TFW_WARN("Multiple Tempesta sticky cookies found: %d\n", r);
Expand Down
2 changes: 1 addition & 1 deletion tempesta_fw/http_sess.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ enum {
/* Session successfully obtained. */
TFW_HTTP_SESS_SUCCESS = 0,
/* Can't obtain session: new client; a redirection message sent. */
TFW_HTTP_SESS_REDIRECT_SENT,
TFW_HTTP_SESS_REDIRECT_NEED,
/* Sticky cookie violated, client must be blocked. */
TFW_HTTP_SESS_VIOLATE,
/* JS challenge enabled, but request is not challengable. */
Expand Down
25 changes: 20 additions & 5 deletions tempesta_fw/t/unit/test_http_sticky.c
Original file line number Diff line number Diff line change
Expand Up @@ -285,11 +285,16 @@ TEST(http_sticky, sending_302_without_preparing)
TfwConn *c = mock.req->conn;

/* Cookie is calculated for zero HMAC. */
EXPECT_EQ(tfw_http_sticky_send_redirect(mock.req, &sv),
TFW_HTTP_SESS_REDIRECT_SENT);
EXPECT_EQ(tfw_http_sticky_build_redirect(mock.req, &sv),
TFW_HTTP_SESS_REDIRECT_NEED);
EXPECT_NOT_NULL(mock.req->resp);
if (!mock.req->resp)
goto err;
tfw_http_resp_fwd(mock.req->resp);

EXPECT_TRUE(mock.tfw_connection_send_was_called);

err:
tfw_connection_put(c);
mock.req = NULL; /* already freed */
}
Expand All @@ -311,14 +316,19 @@ TEST(http_sticky, sending_302)
mock.req->h_tbl->tbl[TFW_HTTP_HDR_HOST] = *hdr1;

EXPECT_EQ(__sticky_calc(mock.req, &sv), 0);
EXPECT_EQ(tfw_http_sticky_send_redirect(mock.req, &sv),
TFW_HTTP_SESS_REDIRECT_SENT);
EXPECT_EQ(tfw_http_sticky_build_redirect(mock.req, &sv),
TFW_HTTP_SESS_REDIRECT_NEED);
EXPECT_NOT_NULL(mock.req->resp);
if (!mock.req->resp)
goto err;
tfw_http_resp_fwd(mock.req->resp);

EXPECT_TRUE(mock.tfw_connection_send_was_called);
EXPECT_TRUE(mock.seen_set_cookie_header);
EXPECT_TRUE(mock.seen_cookie);
EXPECT_EQ(mock.http_status, 302);

err:
tfw_connection_put(c);
mock.req = NULL; /* already freed */
}
Expand Down Expand Up @@ -538,7 +548,11 @@ TEST(http_sticky, req_no_cookie_enforce)

append_string_to_msg((TfwHttpMsg *)mock.req, s_req);
EXPECT_EQ(http_parse_req_helper(), 0);
EXPECT_EQ(tfw_http_sess_obtain(mock.req), TFW_HTTP_SESS_REDIRECT_SENT);
EXPECT_EQ(tfw_http_sess_obtain(mock.req), TFW_HTTP_SESS_REDIRECT_NEED);
EXPECT_NOT_NULL(mock.req->resp);
if (!mock.req->resp)
goto err;
tfw_http_resp_fwd(mock.req->resp);

/* in enforce mode, 302 response is sent to a client by Tempesta
* before backend gets anything
Expand All @@ -547,6 +561,7 @@ TEST(http_sticky, req_no_cookie_enforce)
EXPECT_TRUE(mock.seen_set_cookie_header);
EXPECT_TRUE(mock.seen_cookie);

err:
tfw_connection_put(c);
mock.req = NULL; /* already freed */
}
Expand Down

0 comments on commit 3f398e7

Please sign in to comment.