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 Dec 22, 2018
1 parent bca3d92 commit e8eb431
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 70 deletions.
143 changes: 92 additions & 51 deletions tempesta_fw/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -2436,6 +2436,14 @@ tfw_http_cli_error_resp_and_log(TfwHttpReq *req, int status, const char *msg,
bool nolog;
TfwCliConn *cli_conn = (TfwCliConn *)req->conn;

/*
* Error was happened and request should be dropped or blocked,
* but other modules (e.g. sticky cookie module) may have a response
* prepared for this request. A new error response is to be 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 @@ -2569,38 +2577,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 @@ -2633,20 +2615,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_http_req_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 @@ -2870,27 +2838,94 @@ tfw_http_req_process(TfwConn *conn, const TfwFsmData *data)
&& (req->content_length != req->body.len));
}

/* 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 since it allows to
* avoid expensive tables lookups.
*
* TODO: #685 Sticky cookie are to be configured per-vhost.
* Http_tables used to assign target vhost can be extremely
* long, while the client may use just a few cookie variants.
* Even if different sticky cookies are used for each vhost,
* the sticky module should be faster than
* tfw_http_tbl_vhost().
*
* TODO: #1043 sticky cookie module must assign correct vhost
* when it returns TFW_HTTP_SESS_SUCCESS or
* TFW_HTTP_SESS_REDIRECT_NEED;
*/
switch (tfw_http_sess_obtain(req))
{
case TFW_HTTP_SESS_SUCCESS:
break;

case TFW_HTTP_SESS_REDIRECT_NEED:
/* Response is built and stored in @req->resp. */
break;

case TFW_HTTP_SESS_VIOLATE:
TFW_INC_STAT_BH(clnt.msgs_filtout);
tfw_http_req_parse_block(
req, 503,
"request dropped: sticky cookie challenge was "
"failed");
return TFW_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);
tfw_http_req_parse_block(
req, 503,
"request dropped: can't send JS challenge");
return TFW_BLOCK;

default:
TFW_INC_STAT_BH(clnt.msgs_otherr);
tfw_http_req_parse_block(
req, 500,
"request dropped: internal error in Sticky "
"module");
return TFW_BLOCK;
}

/*
* 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);
r = tfw_gfsm_move(&conn->state, TFW_HTTP_FSM_REQ_MSG,
&data_up);
TFW_DBG3("TFW_HTTP_FSM_REQ_MSG return code %d\n", r);
/* Don't accept any following requests from the peer. */
if (r == TFW_BLOCK) {
else if (block) {
TFW_INC_STAT_BH(clnt.msgs_filtout);
tfw_http_req_parse_block(
req, 403,
"parsed request has been filtered out");
"request has been filtered out via http table");
return TFW_BLOCK;
}

if (block) {
r = tfw_gfsm_move(&conn->state, TFW_HTTP_FSM_REQ_MSG,
&data_up);
TFW_DBG3("TFW_HTTP_FSM_REQ_MSG return code %d\n", r);
/* Don't accept any following requests from the peer. */
if (r == TFW_BLOCK) {
TFW_INC_STAT_BH(clnt.msgs_filtout);
tfw_http_req_parse_block(
req, 403,
"request has been filtered out via http table");
"parsed request has been filtered out");
return TFW_BLOCK;
}

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

/*
* Response is already prepared for the client by sticky module.
*/
if (unlikely(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 (!req->vhost) {
else if (!req->vhost) {
tfw_http_send_resp(req, 404, "request dropped: cannot"
" find appropriate virtual host");
TFW_INC_STAT_BH(clnt.msgs_otherr);
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 @@ -203,7 +203,7 @@ tfw_http_redir_mark_prepare(RedirMarkVal *mv, char *buf, unsigned int buf_len,
}

static int
tfw_http_sticky_send_redirect(TfwHttpReq *req, StickyVal *sv, RedirMarkVal *mv)
tfw_http_sticky_build_redirect(TfwHttpReq *req, StickyVal *sv, RedirMarkVal *mv)
{
unsigned long ts_be64 = cpu_to_be64(sv->ts);
TfwStr c_chunks[3], m_chunks[4], cookie = { 0 }, rmark = { 0 };
Expand Down Expand Up @@ -260,9 +260,12 @@ tfw_http_sticky_send_redirect(TfwHttpReq *req, StickyVal *sv, RedirMarkVal *mv)
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 @@ -434,7 +437,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 @@ -684,7 +687,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, mvp);
return tfw_http_sticky_build_redirect(req, &sv, mvp);
}

/**
Expand Down Expand Up @@ -763,7 +766,7 @@ tfw_http_sticky_req_process(TfwHttpReq *req, StickyVal *sv)
if ((r = tfw_http_sess_check_redir_mark(req, mvp)))
return r;
}
return tfw_http_sticky_send_redirect(req, sv, mvp);
return tfw_http_sticky_build_redirect(req, sv, mvp);
}
return TFW_HTTP_SESS_SUCCESS;
}
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
51 changes: 40 additions & 11 deletions tempesta_fw/t/unit/test_http_sticky.c
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ http_sticky_suite_setup(void)

tfw_connection_init(&mock.conn_req);
tfw_connection_init(&mock.conn_resp);
TFW_CONN_TYPE(&mock.conn_req) |= Conn_Clnt;
TFW_CONN_TYPE(&mock.conn_resp) |= Conn_Srv;

cli_conn = (TfwCliConn *)&mock.conn_req;
INIT_LIST_HEAD(&cli_conn->seq_queue);
Expand Down Expand Up @@ -406,11 +408,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, NULL),
TFW_HTTP_SESS_REDIRECT_SENT);
EXPECT_EQ(tfw_http_sticky_build_redirect(mock.req, &sv, NULL),
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 @@ -432,14 +439,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, NULL),
TFW_HTTP_SESS_REDIRECT_SENT);
EXPECT_EQ(tfw_http_sticky_build_redirect(mock.req, &sv, NULL),
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.cookie.seen_set_header);
EXPECT_TRUE(mock.cookie.seen);
EXPECT_EQ(mock.http_status, 302);

err:
tfw_connection_put(c);
mock.req = NULL; /* already freed */
}
Expand Down Expand Up @@ -662,7 +674,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 @@ -671,6 +687,7 @@ TEST(http_sticky, req_no_cookie_enforce)
EXPECT_TRUE(mock.cookie.seen_set_header);
EXPECT_TRUE(mock.cookie.seen);

err:
tfw_connection_put(c);
mock.req = NULL; /* already freed */
}
Expand Down Expand Up @@ -721,7 +738,11 @@ TEST(http_sticky, req_invalid_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);

/* if cookie is invalid, then 302 response is sent to a client
* by Tempesta instead of forwarding request to backend
Expand All @@ -730,7 +751,7 @@ TEST(http_sticky, req_invalid_cookie_enforce)
EXPECT_TRUE(mock.cookie.seen_set_header);
EXPECT_TRUE(mock.cookie.seen);
EXPECT_TRUE(mock.cookie.seen_val);

err:
tfw_connection_put(c);
mock.req = NULL; /* already freed */
}
Expand All @@ -746,7 +767,11 @@ TEST(http_sticky, req_no_cookie_enforce_extented)

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 @@ -760,7 +785,7 @@ TEST(http_sticky, req_no_cookie_enforce_extented)
*/
EXPECT_TRUE(mock.loc_rmark.seen_set_header);
EXPECT_TRUE(mock.loc_rmark.seen);

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

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);

/* if cookie is invalid, then 302 response is sent to a client
* by Tempesta instead of forwarding request to backend
Expand All @@ -813,7 +842,7 @@ TEST(http_sticky, req_invalid_cookie_enforce_extended)
EXPECT_TRUE(mock.loc_rmark.seen_set_header);
EXPECT_TRUE(mock.loc_rmark.seen);
EXPECT_TRUE(mock.loc_rmark.seen_val);

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

0 comments on commit e8eb431

Please sign in to comment.