2018-10-02 10:01:16 -04:00
|
|
|
/*
|
|
|
|
|
* HTTP actions
|
|
|
|
|
*
|
|
|
|
|
* Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
|
|
|
|
|
*
|
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
|
* as published by the Free Software Foundation; either version
|
|
|
|
|
* 2 of the License, or (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
|
2020-06-04 13:11:43 -04:00
|
|
|
#include <haproxy/acl.h>
|
2020-06-04 04:15:32 -04:00
|
|
|
#include <haproxy/action.h>
|
2020-05-27 06:58:42 -04:00
|
|
|
#include <haproxy/api.h>
|
2020-06-09 03:07:15 -04:00
|
|
|
#include <haproxy/arg.h>
|
|
|
|
|
#include <haproxy/capture-t.h>
|
2020-06-04 18:00:29 -04:00
|
|
|
#include <haproxy/cfgparse.h>
|
2020-06-02 04:22:45 -04:00
|
|
|
#include <haproxy/chunk.h>
|
2020-06-04 11:05:57 -04:00
|
|
|
#include <haproxy/global.h>
|
2020-06-02 13:11:26 -04:00
|
|
|
#include <haproxy/http.h>
|
2020-06-04 15:21:03 -04:00
|
|
|
#include <haproxy/http_ana.h>
|
2020-06-04 03:08:41 -04:00
|
|
|
#include <haproxy/http_htx.h>
|
2020-06-04 05:40:28 -04:00
|
|
|
#include <haproxy/http_rules.h>
|
2020-06-05 11:27:29 -04:00
|
|
|
#include <haproxy/log.h>
|
2020-06-04 09:06:28 -04:00
|
|
|
#include <haproxy/pattern.h>
|
2020-06-02 03:38:52 -04:00
|
|
|
#include <haproxy/pool.h>
|
2020-06-02 11:32:26 -04:00
|
|
|
#include <haproxy/regex.h>
|
2020-06-04 09:33:47 -04:00
|
|
|
#include <haproxy/sample.h>
|
2022-05-27 03:25:10 -04:00
|
|
|
#include <haproxy/sc_strm.h>
|
2022-05-27 03:47:12 -04:00
|
|
|
#include <haproxy/stconn.h>
|
2020-06-03 12:09:46 -04:00
|
|
|
#include <haproxy/tools.h>
|
2020-06-04 13:27:34 -04:00
|
|
|
#include <haproxy/uri_auth-t.h>
|
2021-04-15 15:45:57 -04:00
|
|
|
#include <haproxy/uri_normalizer.h>
|
2020-05-27 09:59:00 -04:00
|
|
|
#include <haproxy/version.h>
|
2018-10-02 10:01:16 -04:00
|
|
|
|
|
|
|
|
|
2025-04-01 16:44:54 -04:00
|
|
|
/* Release memory allocated by most of HTTP actions. Concretely, it releases
|
2020-01-14 08:47:34 -05:00
|
|
|
* <arg.http>.
|
|
|
|
|
*/
|
|
|
|
|
static void release_http_action(struct act_rule *rule)
|
|
|
|
|
{
|
2020-03-05 11:56:33 -05:00
|
|
|
istfree(&rule->arg.http.str);
|
2020-01-14 08:47:34 -05:00
|
|
|
if (rule->arg.http.re)
|
|
|
|
|
regex_free(rule->arg.http.re);
|
2024-02-23 09:57:21 -05:00
|
|
|
lf_expr_deinit(&rule->arg.http.fmt);
|
2020-01-14 08:47:34 -05:00
|
|
|
}
|
|
|
|
|
|
2025-04-01 16:44:54 -04:00
|
|
|
/* Release memory allocated by HTTP actions relying on an http reply. Concretely,
|
2020-05-13 11:56:56 -04:00
|
|
|
* it releases <.arg.http_reply>
|
|
|
|
|
*/
|
|
|
|
|
static void release_act_http_reply(struct act_rule *rule)
|
|
|
|
|
{
|
|
|
|
|
release_http_reply(rule->arg.http_reply);
|
|
|
|
|
rule->arg.http_reply = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Check function for HTTP actions relying on an http reply. The function
|
|
|
|
|
* returns 1 in success case, otherwise, it returns 0 and err is filled.
|
|
|
|
|
*/
|
|
|
|
|
static int check_act_http_reply(struct act_rule *rule, struct proxy *px, char **err)
|
|
|
|
|
{
|
|
|
|
|
struct http_reply *reply = rule->arg.http_reply;
|
|
|
|
|
|
|
|
|
|
if (!http_check_http_reply(reply, px, err)) {
|
|
|
|
|
release_act_http_reply(rule);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 10:01:16 -04:00
|
|
|
|
|
|
|
|
/* This function executes one of the set-{method,path,query,uri} actions. It
|
|
|
|
|
* builds a string in the trash from the specified format string. It finds
|
2019-12-18 09:39:56 -05:00
|
|
|
* the action to be performed in <.action>, previously filled by function
|
2020-05-05 15:53:22 -04:00
|
|
|
* parse_set_req_line(). The replacement action is executed by the function
|
2019-12-16 11:18:42 -05:00
|
|
|
* http_action_set_req_line(). On success, it returns ACT_RET_CONT. If an error
|
|
|
|
|
* occurs while soft rewrites are enabled, the action is canceled, but the rule
|
|
|
|
|
* processing continue. Otherwsize ACT_RET_ERR is returned.
|
2018-10-02 10:01:16 -04:00
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_set_req_line(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
|
|
|
|
struct buffer *replace;
|
MINOR: actions: Use ACT_RET_CONT code to ignore an error from a custom action
Some custom actions are just ignored and skipped when an error is encoutered. In
that case, we jump to the next rule. To do so, most of them use the return code
ACT_RET_ERR. Currently, for http rules and tcp content rules, it is not a
problem because this code is handled the same way than ACT_RET_CONT. But, it
means there is no way to handle the error as other actions. The custom actions
must handle the error and return ACT_RET_DONE. For instance, when http-request
rules are processed, an error when we try to replace a header value leads to a
bad request and an error 400 is returned to the client. But when we fail to
replace the URI, the error is silently ignored. This difference between the
custom actions and the others is an obstacle to write new custom actions.
So, in this first patch, ACT_RET_CONT is now returned from custom actions
instead of ACT_RET_ERR when an error is encoutered if it should be ignored. The
behavior remains the same but it is now possible to handle true errors using the
return code ACT_RET_ERR. Some actions will probably be reviewed to determine if
an error is fatal or not. Other patches will be pushed to trigger an error when
a custom action returns the ACT_RET_ERR code.
This patch is not tagged as a bug because it is just a design issue. But others
will depends on it. So be careful during backports, if so.
2019-12-13 03:01:57 -05:00
|
|
|
enum act_return ret = ACT_RET_CONT;
|
2018-10-02 10:01:16 -04:00
|
|
|
|
|
|
|
|
replace = alloc_trash_chunk();
|
|
|
|
|
if (!replace)
|
2019-12-16 11:18:42 -05:00
|
|
|
goto fail_alloc;
|
2018-10-02 10:01:16 -04:00
|
|
|
|
|
|
|
|
/* If we have to create a query string, prepare a '?'. */
|
2019-12-18 09:39:56 -05:00
|
|
|
if (rule->action == 2) // set-query
|
2018-10-02 10:01:16 -04:00
|
|
|
replace->area[replace->data++] = '?';
|
|
|
|
|
replace->data += build_logline(s, replace->area + replace->data,
|
|
|
|
|
replace->size - replace->data,
|
2019-12-17 07:46:18 -05:00
|
|
|
&rule->arg.http.fmt);
|
2018-10-02 10:01:16 -04:00
|
|
|
|
2019-12-18 09:39:56 -05:00
|
|
|
if (http_req_replace_stline(rule->action, replace->area, replace->data, px, s) == -1)
|
2019-12-16 11:18:42 -05:00
|
|
|
goto fail_rewrite;
|
2018-10-02 10:01:16 -04:00
|
|
|
|
2019-12-16 11:18:42 -05:00
|
|
|
leave:
|
2018-10-02 10:01:16 -04:00
|
|
|
free_trash_chunk(replace);
|
|
|
|
|
return ret;
|
2019-12-16 11:18:42 -05:00
|
|
|
|
|
|
|
|
fail_alloc:
|
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
|
|
|
ret = ACT_RET_ERR;
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
fail_rewrite:
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
|
2019-12-16 11:18:42 -05:00
|
|
|
if (s->flags & SF_BE_ASSIGNED)
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
|
2021-03-08 09:26:48 -05:00
|
|
|
if (sess->listener && sess->listener->counters)
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
|
2019-12-16 11:18:42 -05:00
|
|
|
if (objt_server(s->target))
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
|
2019-12-16 11:18:42 -05:00
|
|
|
|
2020-01-22 08:38:05 -05:00
|
|
|
if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
|
2019-12-16 11:18:42 -05:00
|
|
|
ret = ACT_RET_ERR;
|
2020-01-22 08:38:05 -05:00
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
|
|
|
}
|
2019-12-16 11:18:42 -05:00
|
|
|
goto leave;
|
2018-10-02 10:01:16 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* parse an http-request action among :
|
|
|
|
|
* set-method
|
|
|
|
|
* set-path
|
2020-09-02 11:17:44 -04:00
|
|
|
* set-pathq
|
2018-10-02 10:01:16 -04:00
|
|
|
* set-query
|
|
|
|
|
* set-uri
|
|
|
|
|
*
|
|
|
|
|
* All of them accept a single argument of type string representing a log-format.
|
2019-12-17 07:46:18 -05:00
|
|
|
* The resulting rule makes use of <http.fmt> to store the log-format list head,
|
2019-12-18 09:39:56 -05:00
|
|
|
* and <.action> to store the action type as an int (0=method, 1=path, 2=query,
|
|
|
|
|
* 3=uri). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
2018-10-02 10:01:16 -04:00
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_set_req_line(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
int cur_arg = *orig_arg;
|
2021-10-13 11:22:17 -04:00
|
|
|
int cap = 0;
|
2018-10-02 10:01:16 -04:00
|
|
|
|
|
|
|
|
switch (args[0][4]) {
|
|
|
|
|
case 'm' :
|
2019-12-18 09:39:56 -05:00
|
|
|
rule->action = 0; // set-method
|
2018-10-02 10:01:16 -04:00
|
|
|
break;
|
|
|
|
|
case 'p' :
|
2020-09-02 11:17:44 -04:00
|
|
|
if (args[0][8] == 'q')
|
|
|
|
|
rule->action = 4; // set-pathq
|
|
|
|
|
else
|
|
|
|
|
rule->action = 1; // set-path
|
2018-10-02 10:01:16 -04:00
|
|
|
break;
|
|
|
|
|
case 'q' :
|
2019-12-18 09:39:56 -05:00
|
|
|
rule->action = 2; // set-query
|
2018-10-02 10:01:16 -04:00
|
|
|
break;
|
|
|
|
|
case 'u' :
|
2019-12-18 09:39:56 -05:00
|
|
|
rule->action = 3; // set-uri
|
2018-10-02 10:01:16 -04:00
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
memprintf(err, "internal error: unhandled action '%s'", args[0]);
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
2019-12-17 07:46:18 -05:00
|
|
|
rule->action_ptr = http_action_set_req_line;
|
2020-01-14 08:47:34 -05:00
|
|
|
rule->release_ptr = release_http_action;
|
2024-02-23 09:57:21 -05:00
|
|
|
lf_expr_init(&rule->arg.http.fmt);
|
2018-10-02 10:01:16 -04:00
|
|
|
|
|
|
|
|
if (!*args[cur_arg] ||
|
|
|
|
|
(*args[cur_arg + 1] && strcmp(args[cur_arg + 1], "if") != 0 && strcmp(args[cur_arg + 1], "unless") != 0)) {
|
|
|
|
|
memprintf(err, "expects exactly 1 argument <format>");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
px->conf.args.ctx = ARGC_HRQ;
|
2021-10-13 11:22:17 -04:00
|
|
|
if (px->cap & PR_CAP_FE)
|
|
|
|
|
cap |= SMP_VAL_FE_HRQ_HDR;
|
|
|
|
|
if (px->cap & PR_CAP_BE)
|
|
|
|
|
cap |= SMP_VAL_BE_HRQ_HDR;
|
|
|
|
|
if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(*orig_arg)++;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-15 15:45:57 -04:00
|
|
|
/* This function executes the http-request normalize-uri action.
|
|
|
|
|
* `rule->action` is expected to be a value from `enum act_normalize_uri`.
|
|
|
|
|
*
|
|
|
|
|
* On success, it returns ACT_RET_CONT. If an error
|
|
|
|
|
* occurs while soft rewrites are enabled, the action is canceled, but the rule
|
|
|
|
|
* processing continue. Otherwsize ACT_RET_ERR is returned.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_normalize_uri(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
|
|
|
|
enum act_return ret = ACT_RET_CONT;
|
|
|
|
|
struct htx *htx = htxbuf(&s->req.buf);
|
|
|
|
|
const struct ist uri = htx_sl_req_uri(http_get_stline(htx));
|
|
|
|
|
struct buffer *replace = alloc_trash_chunk();
|
|
|
|
|
enum uri_normalizer_err err = URI_NORMALIZER_ERR_INTERNAL_ERROR;
|
|
|
|
|
|
|
|
|
|
if (!replace)
|
|
|
|
|
goto fail_alloc;
|
|
|
|
|
|
|
|
|
|
switch ((enum act_normalize_uri) rule->action) {
|
2021-04-17 05:21:10 -04:00
|
|
|
case ACT_NORMALIZE_URI_PATH_MERGE_SLASHES: {
|
2021-07-06 05:40:12 -04:00
|
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
|
|
|
const struct ist path = http_parse_path(&parser);
|
2021-04-15 15:45:58 -04:00
|
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
|
|
|
|
|
|
if (!isttest(path))
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = uri_normalizer_path_merge_slashes(iststop(path, '?'), &newpath);
|
|
|
|
|
|
2021-04-15 15:45:59 -04:00
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (!http_replace_req_path(htx, newpath, 0))
|
|
|
|
|
goto fail_rewrite;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-04-20 18:22:50 -04:00
|
|
|
case ACT_NORMALIZE_URI_PATH_STRIP_DOT: {
|
2021-07-06 05:40:12 -04:00
|
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
|
|
|
const struct ist path = http_parse_path(&parser);
|
2021-04-20 18:22:50 -04:00
|
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
|
|
|
|
|
|
if (!isttest(path))
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = uri_normalizer_path_dot(iststop(path, '?'), &newpath);
|
|
|
|
|
|
|
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (!http_replace_req_path(htx, newpath, 0))
|
|
|
|
|
goto fail_rewrite;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-04-17 05:21:10 -04:00
|
|
|
case ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT:
|
|
|
|
|
case ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT_FULL: {
|
2021-07-06 05:40:12 -04:00
|
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
|
|
|
const struct ist path = http_parse_path(&parser);
|
2021-04-15 15:45:59 -04:00
|
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
|
|
|
|
|
|
if (!isttest(path))
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2021-04-17 05:21:10 -04:00
|
|
|
err = uri_normalizer_path_dotdot(iststop(path, '?'), rule->action == ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT_FULL, &newpath);
|
2021-04-15 15:45:59 -04:00
|
|
|
|
2021-04-15 15:45:58 -04:00
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (!http_replace_req_path(htx, newpath, 0))
|
|
|
|
|
goto fail_rewrite;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-04-17 05:21:10 -04:00
|
|
|
case ACT_NORMALIZE_URI_QUERY_SORT_BY_NAME: {
|
2021-07-06 05:40:12 -04:00
|
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
|
|
|
const struct ist path = http_parse_path(&parser);
|
2021-04-15 15:46:01 -04:00
|
|
|
struct ist newquery = ist2(replace->area, replace->size);
|
|
|
|
|
|
|
|
|
|
if (!isttest(path))
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = uri_normalizer_query_sort(istfind(path, '?'), '&', &newquery);
|
|
|
|
|
|
|
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (!http_replace_req_query(htx, newquery))
|
|
|
|
|
goto fail_rewrite;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-04-17 05:21:10 -04:00
|
|
|
case ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE:
|
|
|
|
|
case ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE_STRICT: {
|
2021-07-06 05:40:12 -04:00
|
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
|
|
|
const struct ist path = http_parse_path(&parser);
|
2021-04-15 15:46:02 -04:00
|
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
|
|
|
|
|
|
if (!isttest(path))
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2021-04-17 05:21:10 -04:00
|
|
|
err = uri_normalizer_percent_upper(path, rule->action == ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE_STRICT, &newpath);
|
2021-04-15 15:46:02 -04:00
|
|
|
|
2021-04-21 15:20:36 -04:00
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (!http_replace_req_path(htx, newpath, 1))
|
|
|
|
|
goto fail_rewrite;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED:
|
|
|
|
|
case ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED_STRICT: {
|
2021-07-06 05:40:12 -04:00
|
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
|
|
|
const struct ist path = http_parse_path(&parser);
|
2021-04-21 15:20:36 -04:00
|
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
|
|
|
|
|
|
if (!isttest(path))
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = uri_normalizer_percent_decode_unreserved(path, rule->action == ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED_STRICT, &newpath);
|
|
|
|
|
|
2021-05-10 11:28:25 -04:00
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (!http_replace_req_path(htx, newpath, 1))
|
|
|
|
|
goto fail_rewrite;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ACT_NORMALIZE_URI_FRAGMENT_STRIP: {
|
2021-07-06 05:40:12 -04:00
|
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
|
|
|
const struct ist path = http_parse_path(&parser);
|
2021-05-10 11:28:25 -04:00
|
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
|
|
|
|
|
|
if (!isttest(path))
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = uri_normalizer_fragment_strip(path, &newpath);
|
|
|
|
|
|
2021-05-10 11:28:26 -04:00
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (!http_replace_req_path(htx, newpath, 1))
|
|
|
|
|
goto fail_rewrite;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ACT_NORMALIZE_URI_FRAGMENT_ENCODE: {
|
2021-07-06 05:40:12 -04:00
|
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
|
|
|
const struct ist path = http_parse_path(&parser);
|
2021-05-10 11:28:26 -04:00
|
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
|
|
|
|
|
|
if (!isttest(path))
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = uri_normalizer_fragment_encode(path, &newpath);
|
|
|
|
|
|
2021-04-15 15:46:02 -04:00
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (!http_replace_req_path(htx, newpath, 1))
|
|
|
|
|
goto fail_rewrite;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-04-15 15:45:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (err) {
|
|
|
|
|
case URI_NORMALIZER_ERR_NONE:
|
|
|
|
|
break;
|
|
|
|
|
case URI_NORMALIZER_ERR_INTERNAL_ERROR:
|
|
|
|
|
ret = ACT_RET_ERR;
|
|
|
|
|
break;
|
|
|
|
|
case URI_NORMALIZER_ERR_INVALID_INPUT:
|
|
|
|
|
ret = ACT_RET_INV;
|
|
|
|
|
break;
|
|
|
|
|
case URI_NORMALIZER_ERR_ALLOC:
|
|
|
|
|
goto fail_alloc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
free_trash_chunk(replace);
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
fail_alloc:
|
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
|
|
|
ret = ACT_RET_ERR;
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
fail_rewrite:
|
|
|
|
|
_HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
|
|
|
|
|
if (s->flags & SF_BE_ASSIGNED)
|
|
|
|
|
_HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
|
|
|
|
|
if (sess->listener && sess->listener->counters)
|
|
|
|
|
_HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
|
|
|
|
|
if (objt_server(s->target))
|
|
|
|
|
_HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
|
|
|
|
|
|
|
|
|
|
if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
|
|
|
|
|
ret = ACT_RET_ERR;
|
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
|
|
|
}
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parses the http-request normalize-uri action. It expects a single <normalizer>
|
|
|
|
|
* argument, corresponding too a value in `enum act_normalize_uri`.
|
|
|
|
|
*
|
|
|
|
|
* It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_normalize_uri(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
int cur_arg = *orig_arg;
|
|
|
|
|
|
|
|
|
|
rule->action_ptr = http_action_normalize_uri;
|
|
|
|
|
rule->release_ptr = NULL;
|
|
|
|
|
|
|
|
|
|
if (!*args[cur_arg]) {
|
|
|
|
|
memprintf(err, "missing argument <normalizer>");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-17 05:21:10 -04:00
|
|
|
if (strcmp(args[cur_arg], "path-merge-slashes") == 0) {
|
2021-04-15 15:45:58 -04:00
|
|
|
cur_arg++;
|
2021-04-15 15:45:57 -04:00
|
|
|
|
2021-04-17 05:21:10 -04:00
|
|
|
rule->action = ACT_NORMALIZE_URI_PATH_MERGE_SLASHES;
|
2021-04-15 15:45:57 -04:00
|
|
|
}
|
2021-04-20 18:22:50 -04:00
|
|
|
else if (strcmp(args[cur_arg], "path-strip-dot") == 0) {
|
|
|
|
|
cur_arg++;
|
|
|
|
|
|
|
|
|
|
rule->action = ACT_NORMALIZE_URI_PATH_STRIP_DOT;
|
|
|
|
|
}
|
2021-04-17 05:21:10 -04:00
|
|
|
else if (strcmp(args[cur_arg], "path-strip-dotdot") == 0) {
|
2021-04-15 15:45:59 -04:00
|
|
|
cur_arg++;
|
|
|
|
|
|
2021-04-15 15:46:00 -04:00
|
|
|
if (strcmp(args[cur_arg], "full") == 0) {
|
|
|
|
|
cur_arg++;
|
2021-04-17 05:21:10 -04:00
|
|
|
rule->action = ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT_FULL;
|
2021-04-15 15:46:00 -04:00
|
|
|
}
|
|
|
|
|
else if (!*args[cur_arg]) {
|
2021-04-17 05:21:10 -04:00
|
|
|
rule->action = ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT;
|
2021-04-15 15:46:00 -04:00
|
|
|
}
|
|
|
|
|
else if (strcmp(args[cur_arg], "if") != 0 && strcmp(args[cur_arg], "unless") != 0) {
|
2021-05-10 17:21:20 -04:00
|
|
|
memprintf(err, "unknown argument '%s' for 'path-strip-dotdot' normalizer", args[cur_arg]);
|
2021-04-15 15:46:00 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
2021-04-15 15:45:59 -04:00
|
|
|
}
|
2021-04-17 05:21:10 -04:00
|
|
|
else if (strcmp(args[cur_arg], "query-sort-by-name") == 0) {
|
2021-04-15 15:46:01 -04:00
|
|
|
cur_arg++;
|
|
|
|
|
|
2021-04-17 05:21:10 -04:00
|
|
|
rule->action = ACT_NORMALIZE_URI_QUERY_SORT_BY_NAME;
|
2021-04-15 15:46:01 -04:00
|
|
|
}
|
2021-04-17 05:21:10 -04:00
|
|
|
else if (strcmp(args[cur_arg], "percent-to-uppercase") == 0) {
|
2021-04-15 15:46:02 -04:00
|
|
|
cur_arg++;
|
|
|
|
|
|
|
|
|
|
if (strcmp(args[cur_arg], "strict") == 0) {
|
|
|
|
|
cur_arg++;
|
2021-04-17 05:21:10 -04:00
|
|
|
rule->action = ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE_STRICT;
|
2021-04-15 15:46:02 -04:00
|
|
|
}
|
|
|
|
|
else if (!*args[cur_arg]) {
|
2021-04-17 05:21:10 -04:00
|
|
|
rule->action = ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE;
|
2021-04-15 15:46:02 -04:00
|
|
|
}
|
|
|
|
|
else if (strcmp(args[cur_arg], "if") != 0 && strcmp(args[cur_arg], "unless") != 0) {
|
2021-05-10 17:21:20 -04:00
|
|
|
memprintf(err, "unknown argument '%s' for 'percent-to-uppercase' normalizer", args[cur_arg]);
|
2021-04-15 15:46:02 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-04-21 15:20:36 -04:00
|
|
|
else if (strcmp(args[cur_arg], "percent-decode-unreserved") == 0) {
|
|
|
|
|
cur_arg++;
|
|
|
|
|
|
|
|
|
|
if (strcmp(args[cur_arg], "strict") == 0) {
|
|
|
|
|
cur_arg++;
|
|
|
|
|
rule->action = ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED_STRICT;
|
|
|
|
|
}
|
|
|
|
|
else if (!*args[cur_arg]) {
|
|
|
|
|
rule->action = ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED;
|
|
|
|
|
}
|
|
|
|
|
else if (strcmp(args[cur_arg], "if") != 0 && strcmp(args[cur_arg], "unless") != 0) {
|
|
|
|
|
memprintf(err, "unknown argument '%s' for 'percent-decode-unreserved' normalizer", args[cur_arg]);
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-10 11:28:25 -04:00
|
|
|
else if (strcmp(args[cur_arg], "fragment-strip") == 0) {
|
|
|
|
|
cur_arg++;
|
|
|
|
|
|
|
|
|
|
rule->action = ACT_NORMALIZE_URI_FRAGMENT_STRIP;
|
|
|
|
|
}
|
2021-05-10 11:28:26 -04:00
|
|
|
else if (strcmp(args[cur_arg], "fragment-encode") == 0) {
|
|
|
|
|
cur_arg++;
|
|
|
|
|
|
|
|
|
|
rule->action = ACT_NORMALIZE_URI_FRAGMENT_ENCODE;
|
|
|
|
|
}
|
2021-04-15 15:45:57 -04:00
|
|
|
else {
|
|
|
|
|
memprintf(err, "unknown normalizer '%s'", args[cur_arg]);
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*orig_arg = cur_arg;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-12 11:44:02 -04:00
|
|
|
/* This function executes a replace-uri action. It finds its arguments in
|
2019-12-17 07:46:18 -05:00
|
|
|
* <rule>.arg.http. It builds a string in the trash from the format string
|
2019-06-12 11:44:02 -04:00
|
|
|
* previously filled by function parse_replace_uri() and will execute the regex
|
2019-12-17 07:46:18 -05:00
|
|
|
* in <http.re> to replace the URI. It uses the format string present in
|
2019-12-18 09:39:56 -05:00
|
|
|
* <http.fmt>. The component to act on (path/uri) is taken from <.action> which
|
2019-12-17 07:46:18 -05:00
|
|
|
* contains 1 for the path or 3 for the URI (values used by
|
|
|
|
|
* http_req_replace_stline()). On success, it returns ACT_RET_CONT. If an error
|
|
|
|
|
* occurs while soft rewrites are enabled, the action is canceled, but the rule
|
|
|
|
|
* processing continue. Otherwsize ACT_RET_ERR is returned.
|
2019-06-12 11:44:02 -04:00
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_replace_uri(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
MINOR: actions: Use ACT_RET_CONT code to ignore an error from a custom action
Some custom actions are just ignored and skipped when an error is encoutered. In
that case, we jump to the next rule. To do so, most of them use the return code
ACT_RET_ERR. Currently, for http rules and tcp content rules, it is not a
problem because this code is handled the same way than ACT_RET_CONT. But, it
means there is no way to handle the error as other actions. The custom actions
must handle the error and return ACT_RET_DONE. For instance, when http-request
rules are processed, an error when we try to replace a header value leads to a
bad request and an error 400 is returned to the client. But when we fail to
replace the URI, the error is silently ignored. This difference between the
custom actions and the others is an obstacle to write new custom actions.
So, in this first patch, ACT_RET_CONT is now returned from custom actions
instead of ACT_RET_ERR when an error is encoutered if it should be ignored. The
behavior remains the same but it is now possible to handle true errors using the
return code ACT_RET_ERR. Some actions will probably be reviewed to determine if
an error is fatal or not. Other patches will be pushed to trigger an error when
a custom action returns the ACT_RET_ERR code.
This patch is not tagged as a bug because it is just a design issue. But others
will depends on it. So be careful during backports, if so.
2019-12-13 03:01:57 -05:00
|
|
|
enum act_return ret = ACT_RET_CONT;
|
2019-06-12 11:44:02 -04:00
|
|
|
struct buffer *replace, *output;
|
|
|
|
|
struct ist uri;
|
|
|
|
|
int len;
|
|
|
|
|
|
|
|
|
|
replace = alloc_trash_chunk();
|
|
|
|
|
output = alloc_trash_chunk();
|
|
|
|
|
if (!replace || !output)
|
2019-12-16 11:18:42 -05:00
|
|
|
goto fail_alloc;
|
2019-07-15 10:30:24 -04:00
|
|
|
uri = htx_sl_req_uri(http_get_stline(htxbuf(&s->req.buf)));
|
2019-12-17 00:52:51 -05:00
|
|
|
|
2021-07-06 05:40:12 -04:00
|
|
|
if (rule->action == 1) { // replace-path
|
|
|
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
|
|
|
uri = iststop(http_parse_path(&parser), '?');
|
|
|
|
|
}
|
|
|
|
|
else if (rule->action == 4) { // replace-pathq
|
|
|
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
|
|
|
uri = http_parse_path(&parser);
|
|
|
|
|
}
|
2019-12-17 00:52:51 -05:00
|
|
|
|
2022-04-08 04:44:21 -04:00
|
|
|
if (!istlen(uri))
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2019-12-17 07:46:18 -05:00
|
|
|
if (!regex_exec_match2(rule->arg.http.re, uri.ptr, uri.len, MAX_MATCH, pmatch, 0))
|
2019-06-12 11:44:02 -04:00
|
|
|
goto leave;
|
|
|
|
|
|
2019-12-17 07:46:18 -05:00
|
|
|
replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
|
2019-06-12 11:44:02 -04:00
|
|
|
|
|
|
|
|
/* note: uri.ptr doesn't need to be zero-terminated because it will
|
|
|
|
|
* only be used to pick pmatch references.
|
|
|
|
|
*/
|
|
|
|
|
len = exp_replace(output->area, output->size, uri.ptr, replace->area, pmatch);
|
|
|
|
|
if (len == -1)
|
2019-12-16 11:18:42 -05:00
|
|
|
goto fail_rewrite;
|
2019-06-12 11:44:02 -04:00
|
|
|
|
2019-12-18 09:39:56 -05:00
|
|
|
if (http_req_replace_stline(rule->action, output->area, len, px, s) == -1)
|
2019-12-16 11:18:42 -05:00
|
|
|
goto fail_rewrite;
|
2019-06-12 11:44:02 -04:00
|
|
|
|
2019-12-16 11:18:42 -05:00
|
|
|
leave:
|
2019-06-12 11:44:02 -04:00
|
|
|
free_trash_chunk(output);
|
|
|
|
|
free_trash_chunk(replace);
|
|
|
|
|
return ret;
|
2019-12-16 11:18:42 -05:00
|
|
|
|
|
|
|
|
fail_alloc:
|
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
|
|
|
ret = ACT_RET_ERR;
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
fail_rewrite:
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
|
2019-12-16 11:18:42 -05:00
|
|
|
if (s->flags & SF_BE_ASSIGNED)
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
|
2021-03-08 09:26:48 -05:00
|
|
|
if (sess->listener && sess->listener->counters)
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
|
2019-12-16 11:18:42 -05:00
|
|
|
if (objt_server(s->target))
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
|
2019-12-16 11:18:42 -05:00
|
|
|
|
2020-01-22 08:38:05 -05:00
|
|
|
if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
|
2019-12-16 11:18:42 -05:00
|
|
|
ret = ACT_RET_ERR;
|
2020-01-22 08:38:05 -05:00
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
|
|
|
}
|
2019-12-16 11:18:42 -05:00
|
|
|
goto leave;
|
2019-06-12 11:44:02 -04:00
|
|
|
}
|
|
|
|
|
|
2020-09-02 11:17:44 -04:00
|
|
|
/* parse a "replace-uri", "replace-path" or "replace-pathq"
|
|
|
|
|
* http-request action.
|
2019-06-12 11:44:02 -04:00
|
|
|
* This action takes 2 arguments (a regex and a replacement format string).
|
2019-12-18 09:39:56 -05:00
|
|
|
* The resulting rule makes use of <.action> to store the action (1/3 for now),
|
2019-12-17 07:46:18 -05:00
|
|
|
* <http.re> to store the compiled regex, and <http.fmt> to store the log-format
|
2019-06-12 11:44:02 -04:00
|
|
|
* list head. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_replace_uri(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
int cur_arg = *orig_arg;
|
2021-10-13 11:22:17 -04:00
|
|
|
int cap = 0;
|
2019-06-12 11:44:02 -04:00
|
|
|
char *error = NULL;
|
|
|
|
|
|
2020-09-02 11:17:44 -04:00
|
|
|
switch (args[0][8]) {
|
|
|
|
|
case 'p':
|
|
|
|
|
if (args[0][12] == 'q')
|
|
|
|
|
rule->action = 4; // replace-pathq, same as set-pathq
|
|
|
|
|
else
|
|
|
|
|
rule->action = 1; // replace-path, same as set-path
|
|
|
|
|
break;
|
|
|
|
|
case 'u':
|
2019-12-18 09:39:56 -05:00
|
|
|
rule->action = 3; // replace-uri, same as set-uri
|
2020-09-02 11:17:44 -04:00
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
memprintf(err, "internal error: unhandled action '%s'", args[0]);
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
2019-12-17 00:52:51 -05:00
|
|
|
|
2019-06-12 11:44:02 -04:00
|
|
|
rule->action_ptr = http_action_replace_uri;
|
2020-01-14 08:47:34 -05:00
|
|
|
rule->release_ptr = release_http_action;
|
2024-02-23 09:57:21 -05:00
|
|
|
lf_expr_init(&rule->arg.http.fmt);
|
2019-06-12 11:44:02 -04:00
|
|
|
|
|
|
|
|
if (!*args[cur_arg] || !*args[cur_arg+1] ||
|
|
|
|
|
(*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) {
|
|
|
|
|
memprintf(err, "expects exactly 2 arguments <match-regex> and <replace-format>");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-17 07:46:18 -05:00
|
|
|
if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, &error))) {
|
2019-06-12 11:44:02 -04:00
|
|
|
memprintf(err, "failed to parse the regex : %s", error);
|
|
|
|
|
free(error);
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
px->conf.args.ctx = ARGC_HRQ;
|
2021-10-13 11:22:17 -04:00
|
|
|
if (px->cap & PR_CAP_FE)
|
|
|
|
|
cap |= SMP_VAL_FE_HRQ_HDR;
|
|
|
|
|
if (px->cap & PR_CAP_BE)
|
|
|
|
|
cap |= SMP_VAL_BE_HRQ_HDR;
|
|
|
|
|
if (!parse_logformat_string(args[cur_arg + 1], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
|
2020-01-14 08:50:55 -05:00
|
|
|
regex_free(rule->arg.http.re);
|
2019-06-12 11:44:02 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(*orig_arg) += 2;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 10:01:16 -04:00
|
|
|
/* This function is just a compliant action wrapper for "set-status". */
|
|
|
|
|
static enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
2019-12-17 07:46:18 -05:00
|
|
|
if (http_res_set_status(rule->arg.http.i, rule->arg.http.str, s) == -1) {
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
|
2019-12-16 11:18:42 -05:00
|
|
|
if (s->flags & SF_BE_ASSIGNED)
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
|
2021-03-08 09:26:48 -05:00
|
|
|
if (sess->listener && sess->listener->counters)
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
|
2019-12-16 11:18:42 -05:00
|
|
|
if (objt_server(s->target))
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
|
2019-12-16 11:18:42 -05:00
|
|
|
|
2020-01-22 08:38:05 -05:00
|
|
|
if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
|
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_PRXCOND;
|
2020-02-07 04:22:31 -05:00
|
|
|
return ACT_RET_ERR;
|
2020-01-22 08:38:05 -05:00
|
|
|
}
|
2019-12-16 11:18:42 -05:00
|
|
|
}
|
|
|
|
|
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* parse set-status action:
|
|
|
|
|
* This action accepts a single argument of type int representing
|
|
|
|
|
* an http status code. It returns ACT_RET_PRS_OK on success,
|
|
|
|
|
* ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
char *error;
|
|
|
|
|
|
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
|
|
|
rule->action_ptr = action_http_set_status;
|
2020-01-14 08:47:34 -05:00
|
|
|
rule->release_ptr = release_http_action;
|
2024-02-23 09:57:21 -05:00
|
|
|
lf_expr_init(&rule->arg.http.fmt);
|
2018-10-02 10:01:16 -04:00
|
|
|
|
|
|
|
|
/* Check if an argument is available */
|
|
|
|
|
if (!*args[*orig_arg]) {
|
|
|
|
|
memprintf(err, "expects 1 argument: <status>; or 3 arguments: <status> reason <fmt>");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* convert status code as integer */
|
2019-12-17 07:46:18 -05:00
|
|
|
rule->arg.http.i = strtol(args[*orig_arg], &error, 10);
|
|
|
|
|
if (*error != '\0' || rule->arg.http.i < 100 || rule->arg.http.i > 999) {
|
2018-10-02 10:01:16 -04:00
|
|
|
memprintf(err, "expects an integer status code between 100 and 999");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(*orig_arg)++;
|
|
|
|
|
|
|
|
|
|
/* set custom reason string */
|
2019-12-17 07:46:18 -05:00
|
|
|
rule->arg.http.str = ist(NULL); // If null, we use the default reason for the status code.
|
2018-10-02 10:01:16 -04:00
|
|
|
if (*args[*orig_arg] && strcmp(args[*orig_arg], "reason") == 0 &&
|
|
|
|
|
(*args[*orig_arg + 1] && strcmp(args[*orig_arg + 1], "if") != 0 && strcmp(args[*orig_arg + 1], "unless") != 0)) {
|
|
|
|
|
(*orig_arg)++;
|
2022-03-15 08:11:08 -04:00
|
|
|
rule->arg.http.str = ist(strdup(args[*orig_arg]));
|
2018-10-02 10:01:16 -04:00
|
|
|
(*orig_arg)++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This function executes the "reject" HTTP action. It clears the request and
|
|
|
|
|
* response buffer without sending any response. It can be useful as an HTTP
|
|
|
|
|
* alternative to the silent-drop action to defend against DoS attacks, and may
|
|
|
|
|
* also be used with HTTP/2 to close a connection instead of just a stream.
|
|
|
|
|
* The txn status is unchanged, indicating no response was sent. The termination
|
2020-03-06 05:18:39 -05:00
|
|
|
* flags will indicate "PR". It always returns ACT_RET_ABRT.
|
2018-10-02 10:01:16 -04:00
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_reject(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
2023-04-13 10:44:18 -04:00
|
|
|
sc_must_kill_conn(s->scf);
|
2023-04-13 09:22:29 -04:00
|
|
|
stream_abort(s);
|
2020-03-06 09:07:09 -05:00
|
|
|
s->req.analysers &= AN_REQ_FLT_END;
|
|
|
|
|
s->res.analysers &= AN_RES_FLT_END;
|
2018-10-02 10:01:16 -04:00
|
|
|
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&s->be->be_counters.denied_req);
|
|
|
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.denied_req);
|
2018-10-02 10:01:16 -04:00
|
|
|
if (sess->listener && sess->listener->counters)
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&sess->listener->counters->denied_req);
|
2018-10-02 10:01:16 -04:00
|
|
|
|
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
|
|
|
if (!(s->flags & SF_FINST_MASK))
|
|
|
|
|
s->flags |= SF_FINST_R;
|
|
|
|
|
|
2020-03-06 05:18:39 -05:00
|
|
|
return ACT_RET_ABRT;
|
2018-10-02 10:01:16 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* parse the "reject" action:
|
|
|
|
|
* This action takes no argument and returns ACT_RET_PRS_OK on success,
|
|
|
|
|
* ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_action_reject(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
|
|
|
rule->action_ptr = http_action_reject;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-10 07:59:15 -04:00
|
|
|
/* This function executes the "disable-l7-retry" HTTP action.
|
|
|
|
|
* It disables L7 retries (all retry except for a connection failure). This
|
|
|
|
|
* can be useful for example to avoid retrying on POST requests.
|
2022-03-29 09:23:40 -04:00
|
|
|
* It just removes the L7 retry flag on the HTTP transaction, and always
|
2019-05-10 07:59:15 -04:00
|
|
|
* return ACT_RET_CONT;
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_req_disable_l7_retry(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
2022-03-29 09:23:40 -04:00
|
|
|
/* In theory, the TX_L7_RETRY flags isn't set at this point, but
|
2019-05-10 07:59:15 -04:00
|
|
|
* let's be future-proof and remove it anyway.
|
|
|
|
|
*/
|
2022-03-29 09:23:40 -04:00
|
|
|
s->txn->flags &= ~TX_L7_RETRY;
|
|
|
|
|
s->txn->flags |= TX_D_L7_RETRY;
|
2019-05-10 07:59:15 -04:00
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* parse the "disable-l7-retry" action:
|
|
|
|
|
* This action takes no argument and returns ACT_RET_PRS_OK on success,
|
|
|
|
|
* ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_req_disable_l7_retry(const char **args,
|
|
|
|
|
int *orig_args, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
|
|
|
rule->action_ptr = http_req_disable_l7_retry;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 10:01:16 -04:00
|
|
|
/* This function executes the "capture" action. It executes a fetch expression,
|
|
|
|
|
* turns the result into a string and puts it in a capture slot. It always
|
|
|
|
|
* returns 1. If an error occurs the action is cancelled, but the rule
|
|
|
|
|
* processing continues.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_req_capture(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
|
|
|
|
struct sample *key;
|
|
|
|
|
struct cap_hdr *h = rule->arg.cap.hdr;
|
|
|
|
|
char **cap = s->req_cap;
|
|
|
|
|
int len;
|
|
|
|
|
|
|
|
|
|
key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR);
|
|
|
|
|
if (!key)
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
|
|
|
|
|
if (cap[h->index] == NULL)
|
|
|
|
|
cap[h->index] = pool_alloc(h->pool);
|
|
|
|
|
|
|
|
|
|
if (cap[h->index] == NULL) /* no more capture memory */
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
|
|
|
|
|
len = key->data.u.str.data;
|
|
|
|
|
if (len > h->len)
|
|
|
|
|
len = h->len;
|
|
|
|
|
|
|
|
|
|
memcpy(cap[h->index], key->data.u.str.area, len);
|
|
|
|
|
cap[h->index][len] = 0;
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This function executes the "capture" action and store the result in a
|
|
|
|
|
* capture slot if exists. It executes a fetch expression, turns the result
|
|
|
|
|
* into a string and puts it in a capture slot. It always returns 1. If an
|
|
|
|
|
* error occurs the action is cancelled, but the rule processing continues.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
|
|
|
|
struct sample *key;
|
|
|
|
|
struct cap_hdr *h;
|
|
|
|
|
char **cap = s->req_cap;
|
|
|
|
|
struct proxy *fe = strm_fe(s);
|
|
|
|
|
int len;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/* Look for the original configuration. */
|
|
|
|
|
for (h = fe->req_cap, i = fe->nb_req_cap - 1;
|
|
|
|
|
h != NULL && i != rule->arg.capid.idx ;
|
|
|
|
|
i--, h = h->next);
|
|
|
|
|
if (!h)
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
|
|
|
|
|
key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
|
|
|
|
|
if (!key)
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
|
|
|
|
|
if (cap[h->index] == NULL)
|
|
|
|
|
cap[h->index] = pool_alloc(h->pool);
|
|
|
|
|
|
|
|
|
|
if (cap[h->index] == NULL) /* no more capture memory */
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
|
|
|
|
|
len = key->data.u.str.data;
|
|
|
|
|
if (len > h->len)
|
|
|
|
|
len = h->len;
|
|
|
|
|
|
|
|
|
|
memcpy(cap[h->index], key->data.u.str.area, len);
|
|
|
|
|
cap[h->index][len] = 0;
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check an "http-request capture" action.
|
|
|
|
|
*
|
|
|
|
|
* The function returns 1 in success case, otherwise, it returns 0 and err is
|
|
|
|
|
* filled.
|
|
|
|
|
*/
|
|
|
|
|
static int check_http_req_capture(struct act_rule *rule, struct proxy *px, char **err)
|
|
|
|
|
{
|
|
|
|
|
if (rule->action_ptr != http_action_req_capture_by_id)
|
|
|
|
|
return 1;
|
|
|
|
|
|
2020-01-16 08:34:22 -05:00
|
|
|
/* capture slots can only be declared in frontends, so we can't check their
|
|
|
|
|
* existence in backends at configuration parsing step
|
|
|
|
|
*/
|
|
|
|
|
if (px->cap & PR_CAP_FE && rule->arg.capid.idx >= px->nb_req_cap) {
|
2018-10-02 10:01:16 -04:00
|
|
|
memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule",
|
|
|
|
|
rule->arg.capid.idx);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-14 08:47:34 -05:00
|
|
|
/* Release memory allocate by an http capture action */
|
|
|
|
|
static void release_http_capture(struct act_rule *rule)
|
|
|
|
|
{
|
|
|
|
|
if (rule->action_ptr == http_action_req_capture)
|
|
|
|
|
release_sample_expr(rule->arg.cap.expr);
|
|
|
|
|
else
|
|
|
|
|
release_sample_expr(rule->arg.capid.expr);
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 10:01:16 -04:00
|
|
|
/* parse an "http-request capture" action. It takes a single argument which is
|
|
|
|
|
* a sample fetch expression. It stores the expression into arg->act.p[0] and
|
|
|
|
|
* the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1].
|
|
|
|
|
* It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
struct sample_expr *expr;
|
|
|
|
|
struct cap_hdr *hdr;
|
|
|
|
|
int cur_arg;
|
|
|
|
|
int len = 0;
|
|
|
|
|
|
|
|
|
|
for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
|
|
|
|
|
if (strcmp(args[cur_arg], "if") == 0 ||
|
|
|
|
|
strcmp(args[cur_arg], "unless") == 0)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (cur_arg < *orig_arg + 3) {
|
|
|
|
|
memprintf(err, "expects <expression> [ 'len' <length> | id <idx> ]");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cur_arg = *orig_arg;
|
2020-02-14 10:50:14 -05:00
|
|
|
expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
|
2018-10-02 10:01:16 -04:00
|
|
|
if (!expr)
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
|
|
|
|
|
if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) {
|
|
|
|
|
memprintf(err,
|
|
|
|
|
"fetch method '%s' extracts information from '%s', none of which is available here",
|
|
|
|
|
args[cur_arg-1], sample_src_names(expr->fetch->use));
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!args[cur_arg] || !*args[cur_arg]) {
|
|
|
|
|
memprintf(err, "expects 'len or 'id'");
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(args[cur_arg], "len") == 0) {
|
|
|
|
|
cur_arg++;
|
|
|
|
|
|
|
|
|
|
if (!(px->cap & PR_CAP_FE)) {
|
|
|
|
|
memprintf(err, "proxy '%s' has no frontend capability", px->id);
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
px->conf.args.ctx = ARGC_CAP;
|
|
|
|
|
|
|
|
|
|
if (!args[cur_arg]) {
|
|
|
|
|
memprintf(err, "missing length value");
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
/* we copy the table name for now, it will be resolved later */
|
|
|
|
|
len = atoi(args[cur_arg]);
|
|
|
|
|
if (len <= 0) {
|
|
|
|
|
memprintf(err, "length must be > 0");
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
cur_arg++;
|
|
|
|
|
|
|
|
|
|
hdr = calloc(1, sizeof(*hdr));
|
2021-05-12 11:54:17 -04:00
|
|
|
if (!hdr) {
|
|
|
|
|
memprintf(err, "out of memory");
|
|
|
|
|
release_sample_expr(expr);
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
2018-10-02 10:01:16 -04:00
|
|
|
hdr->next = px->req_cap;
|
|
|
|
|
hdr->name = NULL; /* not a header capture */
|
|
|
|
|
hdr->namelen = 0;
|
|
|
|
|
hdr->len = len;
|
|
|
|
|
hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
|
|
|
|
|
hdr->index = px->nb_req_cap++;
|
|
|
|
|
|
|
|
|
|
px->req_cap = hdr;
|
|
|
|
|
px->to_log |= LW_REQHDR;
|
|
|
|
|
|
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
|
|
|
rule->action_ptr = http_action_req_capture;
|
2020-01-14 08:47:34 -05:00
|
|
|
rule->release_ptr = release_http_capture;
|
2018-10-02 10:01:16 -04:00
|
|
|
rule->arg.cap.expr = expr;
|
|
|
|
|
rule->arg.cap.hdr = hdr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (strcmp(args[cur_arg], "id") == 0) {
|
|
|
|
|
int id;
|
|
|
|
|
char *error;
|
|
|
|
|
|
|
|
|
|
cur_arg++;
|
|
|
|
|
|
|
|
|
|
if (!args[cur_arg]) {
|
|
|
|
|
memprintf(err, "missing id value");
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id = strtol(args[cur_arg], &error, 10);
|
|
|
|
|
if (*error != '\0') {
|
|
|
|
|
memprintf(err, "cannot parse id '%s'", args[cur_arg]);
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
cur_arg++;
|
|
|
|
|
|
|
|
|
|
px->conf.args.ctx = ARGC_CAP;
|
|
|
|
|
|
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
|
|
|
rule->action_ptr = http_action_req_capture_by_id;
|
|
|
|
|
rule->check_ptr = check_http_req_capture;
|
2020-01-14 08:47:34 -05:00
|
|
|
rule->release_ptr = release_http_capture;
|
2018-10-02 10:01:16 -04:00
|
|
|
rule->arg.capid.expr = expr;
|
|
|
|
|
rule->arg.capid.idx = id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]);
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*orig_arg = cur_arg;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This function executes the "capture" action and store the result in a
|
|
|
|
|
* capture slot if exists. It executes a fetch expression, turns the result
|
|
|
|
|
* into a string and puts it in a capture slot. It always returns 1. If an
|
|
|
|
|
* error occurs the action is cancelled, but the rule processing continues.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
|
|
|
|
struct sample *key;
|
|
|
|
|
struct cap_hdr *h;
|
|
|
|
|
char **cap = s->res_cap;
|
|
|
|
|
struct proxy *fe = strm_fe(s);
|
|
|
|
|
int len;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/* Look for the original configuration. */
|
|
|
|
|
for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
|
|
|
|
|
h != NULL && i != rule->arg.capid.idx ;
|
|
|
|
|
i--, h = h->next);
|
|
|
|
|
if (!h)
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
|
|
|
|
|
key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
|
|
|
|
|
if (!key)
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
|
|
|
|
|
if (cap[h->index] == NULL)
|
|
|
|
|
cap[h->index] = pool_alloc(h->pool);
|
|
|
|
|
|
|
|
|
|
if (cap[h->index] == NULL) /* no more capture memory */
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
|
|
|
|
|
len = key->data.u.str.data;
|
|
|
|
|
if (len > h->len)
|
|
|
|
|
len = h->len;
|
|
|
|
|
|
|
|
|
|
memcpy(cap[h->index], key->data.u.str.area, len);
|
|
|
|
|
cap[h->index][len] = 0;
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check an "http-response capture" action.
|
|
|
|
|
*
|
|
|
|
|
* The function returns 1 in success case, otherwise, it returns 0 and err is
|
|
|
|
|
* filled.
|
|
|
|
|
*/
|
|
|
|
|
static int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err)
|
|
|
|
|
{
|
|
|
|
|
if (rule->action_ptr != http_action_res_capture_by_id)
|
|
|
|
|
return 1;
|
|
|
|
|
|
2020-07-03 07:43:42 -04:00
|
|
|
/* capture slots can only be declared in frontends, so we can't check their
|
|
|
|
|
* existence in backends at configuration parsing step
|
|
|
|
|
*/
|
|
|
|
|
if (px->cap & PR_CAP_FE && rule->arg.capid.idx >= px->nb_rsp_cap) {
|
2018-10-02 10:01:16 -04:00
|
|
|
memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule",
|
|
|
|
|
rule->arg.capid.idx);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* parse an "http-response capture" action. It takes a single argument which is
|
|
|
|
|
* a sample fetch expression. It stores the expression into arg->act.p[0] and
|
2021-01-07 23:35:52 -05:00
|
|
|
* the allocated hdr_cap struct of the preallocated id into arg->act.p[1].
|
2018-10-02 10:01:16 -04:00
|
|
|
* It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
struct sample_expr *expr;
|
|
|
|
|
int cur_arg;
|
|
|
|
|
int id;
|
|
|
|
|
char *error;
|
|
|
|
|
|
|
|
|
|
for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
|
|
|
|
|
if (strcmp(args[cur_arg], "if") == 0 ||
|
|
|
|
|
strcmp(args[cur_arg], "unless") == 0)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (cur_arg < *orig_arg + 3) {
|
|
|
|
|
memprintf(err, "expects <expression> id <idx>");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cur_arg = *orig_arg;
|
2020-02-14 10:50:14 -05:00
|
|
|
expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
|
2018-10-02 10:01:16 -04:00
|
|
|
if (!expr)
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
|
|
|
|
|
if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) {
|
|
|
|
|
memprintf(err,
|
|
|
|
|
"fetch method '%s' extracts information from '%s', none of which is available here",
|
|
|
|
|
args[cur_arg-1], sample_src_names(expr->fetch->use));
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!args[cur_arg] || !*args[cur_arg]) {
|
|
|
|
|
memprintf(err, "expects 'id'");
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(args[cur_arg], "id") != 0) {
|
|
|
|
|
memprintf(err, "expects 'id', found '%s'", args[cur_arg]);
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cur_arg++;
|
|
|
|
|
|
|
|
|
|
if (!args[cur_arg]) {
|
|
|
|
|
memprintf(err, "missing id value");
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id = strtol(args[cur_arg], &error, 10);
|
|
|
|
|
if (*error != '\0') {
|
|
|
|
|
memprintf(err, "cannot parse id '%s'", args[cur_arg]);
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2018-10-02 10:01:16 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
cur_arg++;
|
|
|
|
|
|
|
|
|
|
px->conf.args.ctx = ARGC_CAP;
|
|
|
|
|
|
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
|
|
|
rule->action_ptr = http_action_res_capture_by_id;
|
|
|
|
|
rule->check_ptr = check_http_res_capture;
|
2020-01-14 08:47:34 -05:00
|
|
|
rule->release_ptr = release_http_capture;
|
2018-10-02 10:01:16 -04:00
|
|
|
rule->arg.capid.expr = expr;
|
|
|
|
|
rule->arg.capid.idx = id;
|
|
|
|
|
|
|
|
|
|
*orig_arg = cur_arg;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-12 10:40:30 -05:00
|
|
|
/* Parse a "allow" action for a request or a response rule. It takes no argument. It
|
|
|
|
|
* returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_allow(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
rule->action = ACT_ACTION_ALLOW;
|
2019-12-18 08:58:12 -05:00
|
|
|
rule->flags |= ACT_FLAG_FINAL;
|
2019-12-12 10:40:30 -05:00
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-13 15:49:03 -05:00
|
|
|
/* Parse "deny" or "tarpit" actions for a request rule or "deny" action for a
|
2020-05-13 11:56:56 -04:00
|
|
|
* response rule. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on
|
|
|
|
|
* error. It relies on http_parse_http_reply() to set
|
|
|
|
|
* <.arg.http_reply>.
|
2019-12-12 10:40:30 -05:00
|
|
|
*/
|
2020-01-13 15:49:03 -05:00
|
|
|
static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
2019-12-12 10:40:30 -05:00
|
|
|
{
|
2020-05-13 11:56:56 -04:00
|
|
|
int default_status;
|
|
|
|
|
int cur_arg, arg = 0;
|
2019-12-12 10:40:30 -05:00
|
|
|
|
|
|
|
|
cur_arg = *orig_arg;
|
2020-01-13 15:49:03 -05:00
|
|
|
if (rule->from == ACT_F_HTTP_REQ) {
|
CLEANUP: Compare the return value of `XXXcmp()` functions with zero
According to coding-style.txt it is recommended to use:
`strcmp(a, b) == 0` instead of `!strcmp(a, b)`
So let's do this.
The change was performed by running the following (very long) coccinelle patch
on src/:
@@
statement S;
expression E;
expression F;
@@
if (
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) != 0
)
(
S
|
{ ... }
)
@@
statement S;
expression E;
expression F;
@@
if (
- !
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) == 0
)
(
S
|
{ ... }
)
@@
expression E;
expression F;
expression G;
@@
(
G &&
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) != 0
)
@@
expression E;
expression F;
expression G;
@@
(
G ||
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) != 0
)
@@
expression E;
expression F;
expression G;
@@
(
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) != 0
&& G
)
@@
expression E;
expression F;
expression G;
@@
(
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) != 0
|| G
)
@@
expression E;
expression F;
expression G;
@@
(
G &&
- !
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) == 0
)
@@
expression E;
expression F;
expression G;
@@
(
G ||
- !
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) == 0
)
@@
expression E;
expression F;
expression G;
@@
(
- !
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) == 0
&& G
)
@@
expression E;
expression F;
expression G;
@@
(
- !
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) == 0
|| G
)
@@
expression E;
expression F;
expression G;
@@
(
- !
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) == 0
)
2021-01-02 16:31:53 -05:00
|
|
|
if (strcmp(args[cur_arg - 1], "tarpit") == 0) {
|
2020-01-13 15:49:03 -05:00
|
|
|
rule->action = ACT_HTTP_REQ_TARPIT;
|
2020-05-13 11:56:56 -04:00
|
|
|
default_status = 500;
|
2019-12-12 10:40:30 -05:00
|
|
|
}
|
2020-01-13 15:49:03 -05:00
|
|
|
else {
|
|
|
|
|
rule->action = ACT_ACTION_DENY;
|
2020-05-13 11:56:56 -04:00
|
|
|
default_status = 403;
|
2019-12-12 10:40:30 -05:00
|
|
|
}
|
|
|
|
|
}
|
2020-01-13 15:49:03 -05:00
|
|
|
else {
|
2020-01-14 06:00:28 -05:00
|
|
|
rule->action = ACT_ACTION_DENY;
|
2020-05-13 11:56:56 -04:00
|
|
|
default_status = 502;
|
2020-01-13 15:49:03 -05:00
|
|
|
}
|
2020-01-13 10:43:45 -05:00
|
|
|
|
2020-05-13 11:56:56 -04:00
|
|
|
/* If no args or only a deny_status specified, fallback on the legacy
|
|
|
|
|
* mode and use default error files despite the fact that
|
|
|
|
|
* default-errorfiles is not used. Otherwise, parse an http reply.
|
|
|
|
|
*/
|
2020-01-13 10:43:45 -05:00
|
|
|
|
2020-05-13 11:56:56 -04:00
|
|
|
/* Prepare parsing of log-format strings */
|
|
|
|
|
px->conf.args.ctx = ((rule->from == ACT_F_HTTP_REQ) ? ARGC_HRQ : ARGC_HRS);
|
2020-01-14 06:00:28 -05:00
|
|
|
|
2020-06-30 03:32:01 -04:00
|
|
|
if (!*(args[cur_arg]) || strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
|
2020-05-13 11:56:56 -04:00
|
|
|
rule->arg.http_reply = http_parse_http_reply((const char *[]){"default-errorfiles", ""}, &arg, px, default_status, err);
|
|
|
|
|
goto end;
|
2020-01-14 06:00:28 -05:00
|
|
|
}
|
2020-05-13 11:56:56 -04:00
|
|
|
|
|
|
|
|
if (strcmp(args[cur_arg], "deny_status") == 0) {
|
2020-06-30 03:32:01 -04:00
|
|
|
if (!*(args[cur_arg+2]) || strcmp(args[cur_arg+2], "if") == 0 || strcmp(args[cur_arg+2], "unless") == 0) {
|
2020-05-13 11:56:56 -04:00
|
|
|
rule->arg.http_reply = http_parse_http_reply((const char *[]){"status", args[cur_arg+1], "default-errorfiles", ""},
|
|
|
|
|
&arg, px, default_status, err);
|
|
|
|
|
*orig_arg += 2;
|
|
|
|
|
goto end;
|
2020-01-14 06:00:28 -05:00
|
|
|
}
|
2020-05-13 11:56:56 -04:00
|
|
|
args[cur_arg] += 5; /* skip "deny_" for the parsing */
|
2020-01-14 06:00:28 -05:00
|
|
|
}
|
|
|
|
|
|
2020-05-13 11:56:56 -04:00
|
|
|
rule->arg.http_reply = http_parse_http_reply(args, orig_arg, px, default_status, err);
|
2020-01-14 06:00:28 -05:00
|
|
|
|
2020-05-13 11:56:56 -04:00
|
|
|
end:
|
|
|
|
|
if (!rule->arg.http_reply)
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
|
|
|
|
|
rule->flags |= ACT_FLAG_FINAL;
|
|
|
|
|
rule->check_ptr = check_act_http_reply;
|
|
|
|
|
rule->release_ptr = release_act_http_reply;
|
2019-12-12 10:40:30 -05:00
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-27 09:26:43 -04:00
|
|
|
|
|
|
|
|
/* This function executes a auth action. It builds an 401/407 HTX message using
|
|
|
|
|
* the corresponding proxy's error message. On success, it returns
|
|
|
|
|
* ACT_RET_ABRT. If an error occurs ACT_RET_ERR is returned.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_auth(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
|
|
|
|
struct channel *req = &s->req;
|
|
|
|
|
struct channel *res = &s->res;
|
|
|
|
|
struct htx *htx = htx_from_buf(&res->buf);
|
|
|
|
|
struct http_reply *reply;
|
|
|
|
|
const char *auth_realm;
|
|
|
|
|
struct http_hdr_ctx ctx;
|
|
|
|
|
struct ist hdr;
|
|
|
|
|
|
|
|
|
|
/* Auth might be performed on regular http-req rules as well as on stats */
|
|
|
|
|
auth_realm = rule->arg.http.str.ptr;
|
|
|
|
|
if (!auth_realm) {
|
|
|
|
|
if (px->uri_auth && s->current_rule_list == &px->uri_auth->http_req_rules)
|
|
|
|
|
auth_realm = STATS_DEFAULT_REALM;
|
|
|
|
|
else
|
|
|
|
|
auth_realm = px->id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(s->txn->flags & TX_USE_PX_CONN)) {
|
|
|
|
|
s->txn->status = 401;
|
|
|
|
|
hdr = ist("WWW-Authenticate");
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
s->txn->status = 407;
|
|
|
|
|
hdr = ist("Proxy-Authenticate");
|
|
|
|
|
}
|
|
|
|
|
reply = http_error_message(s);
|
|
|
|
|
channel_htx_truncate(res, htx);
|
|
|
|
|
|
|
|
|
|
if (chunk_printf(&trash, "Basic realm=\"%s\"", auth_realm) == -1)
|
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
|
|
/* Write the generic 40x message */
|
|
|
|
|
if (http_reply_to_htx(s, htx, reply) == -1)
|
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
|
|
/* Remove all existing occurrences of the XXX-Authenticate header */
|
|
|
|
|
ctx.blk = NULL;
|
|
|
|
|
while (http_find_header(htx, hdr, &ctx, 1))
|
|
|
|
|
http_remove_header(htx, &ctx);
|
|
|
|
|
|
|
|
|
|
/* Now a the right XXX-Authenticate header */
|
|
|
|
|
if (!http_add_header(htx, hdr, ist2(b_orig(&trash), b_data(&trash))))
|
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
|
|
/* Finally forward the reply */
|
|
|
|
|
htx_to_buf(htx, &res->buf);
|
|
|
|
|
if (!http_forward_proxy_resp(s, 1))
|
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
|
|
/* Note: Only eval on the request */
|
2023-04-28 03:16:15 -04:00
|
|
|
s->logs.request_ts = now_ns;
|
2020-05-27 09:26:43 -04:00
|
|
|
req->analysers &= AN_REQ_FLT_END;
|
|
|
|
|
|
|
|
|
|
if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&s->sess->fe->fe_counters.intercepted_req);
|
2020-05-27 09:26:43 -04:00
|
|
|
|
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_LOCAL;
|
|
|
|
|
if (!(s->flags & SF_FINST_MASK))
|
|
|
|
|
s->flags |= SF_FINST_R;
|
|
|
|
|
|
|
|
|
|
stream_inc_http_err_ctr(s);
|
|
|
|
|
return ACT_RET_ABRT;
|
|
|
|
|
|
|
|
|
|
fail:
|
|
|
|
|
/* If an error occurred, remove the incomplete HTTP response from the
|
|
|
|
|
* buffer */
|
|
|
|
|
channel_htx_truncate(res, htx);
|
|
|
|
|
return ACT_RET_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-12 10:40:30 -05:00
|
|
|
/* Parse a "auth" action. It may take 2 optional arguments to define a "realm"
|
|
|
|
|
* parameter. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_auth(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
int cur_arg;
|
|
|
|
|
|
2020-05-27 09:26:43 -04:00
|
|
|
rule->action = ACT_CUSTOM;
|
2019-12-18 08:58:12 -05:00
|
|
|
rule->flags |= ACT_FLAG_FINAL;
|
2020-05-27 09:26:43 -04:00
|
|
|
rule->action_ptr = http_action_auth;
|
2020-01-14 08:47:34 -05:00
|
|
|
rule->release_ptr = release_http_action;
|
2024-02-23 09:57:21 -05:00
|
|
|
lf_expr_init(&rule->arg.http.fmt);
|
2019-12-12 10:40:30 -05:00
|
|
|
|
|
|
|
|
cur_arg = *orig_arg;
|
CLEANUP: Compare the return value of `XXXcmp()` functions with zero
According to coding-style.txt it is recommended to use:
`strcmp(a, b) == 0` instead of `!strcmp(a, b)`
So let's do this.
The change was performed by running the following (very long) coccinelle patch
on src/:
@@
statement S;
expression E;
expression F;
@@
if (
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) != 0
)
(
S
|
{ ... }
)
@@
statement S;
expression E;
expression F;
@@
if (
- !
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) == 0
)
(
S
|
{ ... }
)
@@
expression E;
expression F;
expression G;
@@
(
G &&
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) != 0
)
@@
expression E;
expression F;
expression G;
@@
(
G ||
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) != 0
)
@@
expression E;
expression F;
expression G;
@@
(
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) != 0
&& G
)
@@
expression E;
expression F;
expression G;
@@
(
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) != 0
|| G
)
@@
expression E;
expression F;
expression G;
@@
(
G &&
- !
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) == 0
)
@@
expression E;
expression F;
expression G;
@@
(
G ||
- !
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) == 0
)
@@
expression E;
expression F;
expression G;
@@
(
- !
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) == 0
&& G
)
@@
expression E;
expression F;
expression G;
@@
(
- !
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) == 0
|| G
)
@@
expression E;
expression F;
expression G;
@@
(
- !
(
dns_hostname_cmp
|
eb_memcmp
|
memcmp
|
strcasecmp
|
strcmp
|
strncasecmp
|
strncmp
)
- (E, F)
+ (E, F) == 0
)
2021-01-02 16:31:53 -05:00
|
|
|
if (strcmp(args[cur_arg], "realm") == 0) {
|
2019-12-12 10:40:30 -05:00
|
|
|
cur_arg++;
|
|
|
|
|
if (!*args[cur_arg]) {
|
|
|
|
|
memprintf(err, "missing realm value.\n");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
2022-03-15 08:11:08 -04:00
|
|
|
rule->arg.http.str = ist(strdup(args[cur_arg]));
|
2019-12-12 10:40:30 -05:00
|
|
|
cur_arg++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*orig_arg = cur_arg;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-17 16:30:06 -05:00
|
|
|
/* This function executes a early-hint action. It adds an HTTP Early Hint HTTP
|
|
|
|
|
* 103 response header with <.arg.http.str> name and with a value built
|
|
|
|
|
* according to <.arg.http.fmt> log line format. If it is the first early-hint
|
2020-05-05 15:53:22 -04:00
|
|
|
* rule of series, the 103 response start-line is added first. At the end, if
|
2020-01-17 16:30:06 -05:00
|
|
|
* the next rule is not an early-hint rule or if it is the last rule, the EOH
|
|
|
|
|
* block is added to terminate the response. On success, it returns
|
|
|
|
|
* ACT_RET_CONT. If an error occurs while soft rewrites are enabled, the action
|
|
|
|
|
* is canceled, but the rule processing continue. Otherwsize ACT_RET_ERR is
|
|
|
|
|
* returned.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_early_hint(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
2022-07-05 10:24:15 -04:00
|
|
|
struct act_rule *next_rule;
|
2020-01-17 16:30:06 -05:00
|
|
|
struct channel *res = &s->res;
|
|
|
|
|
struct htx *htx = htx_from_buf(&res->buf);
|
|
|
|
|
struct buffer *value = alloc_trash_chunk();
|
|
|
|
|
enum act_return ret = ACT_RET_CONT;
|
|
|
|
|
|
|
|
|
|
if (!(s->txn->req.flags & HTTP_MSGF_VER_11))
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
if (!value) {
|
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-05 10:24:15 -04:00
|
|
|
/* if there is no pending 103 response, start a new response. Otherwise,
|
|
|
|
|
* continue to add link to a previously started response
|
|
|
|
|
*/
|
|
|
|
|
if (s->txn->status != 103) {
|
2020-01-17 16:30:06 -05:00
|
|
|
struct htx_sl *sl;
|
|
|
|
|
unsigned int flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|
|
|
|
|
|
HTX_SL_F_XFER_LEN|HTX_SL_F_BODYLESS);
|
|
|
|
|
|
|
|
|
|
sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags,
|
|
|
|
|
ist("HTTP/1.1"), ist("103"), ist("Early Hints"));
|
|
|
|
|
if (!sl)
|
|
|
|
|
goto error;
|
|
|
|
|
sl->info.res.status = 103;
|
2022-07-05 10:24:15 -04:00
|
|
|
s->txn->status = 103;
|
2020-01-17 16:30:06 -05:00
|
|
|
}
|
|
|
|
|
|
2023-04-01 06:26:42 -04:00
|
|
|
/* Add the HTTP Early Hint HTTP 103 response header */
|
2020-01-17 16:30:06 -05:00
|
|
|
value->data = build_logline(s, b_tail(value), b_room(value), &rule->arg.http.fmt);
|
|
|
|
|
if (!htx_add_header(htx, rule->arg.http.str, ist2(b_head(value), b_data(value))))
|
|
|
|
|
goto error;
|
|
|
|
|
|
2022-07-05 10:24:15 -04:00
|
|
|
/* if it is the last rule or the next one is not an early-hint or an
|
|
|
|
|
* conditional early-hint, terminate the current response.
|
|
|
|
|
*/
|
|
|
|
|
next_rule = LIST_NEXT(&rule->list, typeof(rule), list);
|
|
|
|
|
if (&next_rule->list == s->current_rule_list || next_rule->action_ptr != http_action_early_hint || next_rule->cond) {
|
|
|
|
|
if (!htx_add_endof(htx, HTX_BLK_EOH))
|
2020-01-17 16:30:06 -05:00
|
|
|
goto error;
|
2020-01-28 03:28:11 -05:00
|
|
|
if (!http_forward_proxy_resp(s, 0))
|
|
|
|
|
goto error;
|
2022-07-05 10:24:15 -04:00
|
|
|
s->txn->status = 0;
|
2020-01-17 16:30:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
free_trash_chunk(value);
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
error:
|
|
|
|
|
/* If an error occurred during an Early-hint rule, remove the incomplete
|
|
|
|
|
* HTTP 103 response from the buffer */
|
|
|
|
|
channel_htx_truncate(res, htx);
|
|
|
|
|
ret = ACT_RET_ERR;
|
2022-07-05 10:24:15 -04:00
|
|
|
s->txn->status = 0;
|
2020-01-17 16:30:06 -05:00
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-17 03:33:38 -05:00
|
|
|
/* This function executes a set-header or add-header actions. It builds a string
|
|
|
|
|
* in the trash from the specified format string. It finds the action to be
|
|
|
|
|
* performed in <.action>, previously filled by function parse_set_header(). The
|
2020-05-05 15:53:22 -04:00
|
|
|
* replacement action is executed by the function http_action_set_header(). On
|
2019-12-17 03:33:38 -05:00
|
|
|
* success, it returns ACT_RET_CONT. If an error occurs while soft rewrites are
|
|
|
|
|
* enabled, the action is canceled, but the rule processing continue. Otherwsize
|
|
|
|
|
* ACT_RET_ERR is returned.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_set_header(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
2020-01-24 09:37:13 -05:00
|
|
|
struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
|
|
|
|
|
struct htx *htx = htxbuf(&msg->chn->buf);
|
2019-12-17 03:33:38 -05:00
|
|
|
enum act_return ret = ACT_RET_CONT;
|
|
|
|
|
struct buffer *replace;
|
|
|
|
|
struct http_hdr_ctx ctx;
|
|
|
|
|
struct ist n, v;
|
|
|
|
|
|
|
|
|
|
replace = alloc_trash_chunk();
|
|
|
|
|
if (!replace)
|
|
|
|
|
goto fail_alloc;
|
|
|
|
|
|
|
|
|
|
replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
|
|
|
|
|
n = rule->arg.http.str;
|
|
|
|
|
v = ist2(replace->area, replace->data);
|
|
|
|
|
|
|
|
|
|
if (rule->action == 0) { // set-header
|
|
|
|
|
/* remove all occurrences of the header */
|
|
|
|
|
ctx.blk = NULL;
|
|
|
|
|
while (http_find_header(htx, n, &ctx, 1))
|
|
|
|
|
http_remove_header(htx, &ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Now add header */
|
|
|
|
|
if (!http_add_header(htx, n, v))
|
|
|
|
|
goto fail_rewrite;
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
free_trash_chunk(replace);
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
fail_alloc:
|
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
|
|
|
ret = ACT_RET_ERR;
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
fail_rewrite:
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
|
2019-12-17 03:33:38 -05:00
|
|
|
if (s->flags & SF_BE_ASSIGNED)
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
|
2021-03-08 09:26:48 -05:00
|
|
|
if (sess->listener && sess->listener->counters)
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
|
2019-12-17 03:33:38 -05:00
|
|
|
if (objt_server(s->target))
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
|
2019-12-17 03:33:38 -05:00
|
|
|
|
2020-01-22 08:38:05 -05:00
|
|
|
if (!(msg->flags & HTTP_MSGF_SOFT_RW)) {
|
2019-12-17 03:33:38 -05:00
|
|
|
ret = ACT_RET_ERR;
|
2020-01-22 08:38:05 -05:00
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
|
|
|
}
|
2019-12-17 03:33:38 -05:00
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-12 10:40:30 -05:00
|
|
|
/* Parse a "set-header", "add-header" or "early-hint" actions. It takes an
|
|
|
|
|
* header name and a log-format string as arguments. It returns ACT_RET_PRS_OK
|
|
|
|
|
* on success, ACT_RET_PRS_ERR on error.
|
|
|
|
|
*
|
|
|
|
|
* Note: same function is used for the request and the response. However
|
|
|
|
|
* "early-hint" rules are only supported for request rules.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_set_header(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
2021-10-13 11:22:17 -04:00
|
|
|
int cap = 0, cur_arg;
|
2023-04-03 23:25:16 -04:00
|
|
|
const char *p;
|
2019-12-12 10:40:30 -05:00
|
|
|
|
2020-01-17 16:30:06 -05:00
|
|
|
if (args[*orig_arg-1][0] == 'e') {
|
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
|
|
|
rule->action_ptr = http_action_early_hint;
|
|
|
|
|
}
|
2019-12-17 03:33:38 -05:00
|
|
|
else {
|
|
|
|
|
if (args[*orig_arg-1][0] == 's')
|
|
|
|
|
rule->action = 0; // set-header
|
|
|
|
|
else
|
|
|
|
|
rule->action = 1; // add-header
|
|
|
|
|
rule->action_ptr = http_action_set_header;
|
|
|
|
|
}
|
2020-01-14 08:47:34 -05:00
|
|
|
rule->release_ptr = release_http_action;
|
2024-02-23 09:57:21 -05:00
|
|
|
lf_expr_init(&rule->arg.http.fmt);
|
2019-12-12 10:40:30 -05:00
|
|
|
|
|
|
|
|
cur_arg = *orig_arg;
|
|
|
|
|
if (!*args[cur_arg] || !*args[cur_arg+1]) {
|
|
|
|
|
memprintf(err, "expects exactly 2 arguments");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2022-03-15 08:11:08 -04:00
|
|
|
rule->arg.http.str = ist(strdup(args[cur_arg]));
|
2019-12-12 10:40:30 -05:00
|
|
|
|
|
|
|
|
if (rule->from == ACT_F_HTTP_REQ) {
|
|
|
|
|
px->conf.args.ctx = ARGC_HRQ;
|
2021-10-13 11:22:17 -04:00
|
|
|
if (px->cap & PR_CAP_FE)
|
|
|
|
|
cap |= SMP_VAL_FE_HRQ_HDR;
|
|
|
|
|
if (px->cap & PR_CAP_BE)
|
|
|
|
|
cap |= SMP_VAL_BE_HRQ_HDR;
|
2019-12-12 10:40:30 -05:00
|
|
|
}
|
|
|
|
|
else{
|
|
|
|
|
px->conf.args.ctx = ARGC_HRS;
|
2021-10-13 11:22:17 -04:00
|
|
|
if (px->cap & PR_CAP_FE)
|
|
|
|
|
cap |= SMP_VAL_FE_HRS_HDR;
|
|
|
|
|
if (px->cap & PR_CAP_BE)
|
|
|
|
|
cap |= SMP_VAL_BE_HRS_HDR;
|
2019-12-12 10:40:30 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cur_arg++;
|
2020-01-14 08:50:55 -05:00
|
|
|
if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
|
2020-03-05 11:56:33 -05:00
|
|
|
istfree(&rule->arg.http.str);
|
2019-12-12 10:40:30 -05:00
|
|
|
return ACT_RET_PRS_ERR;
|
2020-01-14 08:50:55 -05:00
|
|
|
}
|
2019-12-12 10:40:30 -05:00
|
|
|
|
2023-04-03 23:25:16 -04:00
|
|
|
/* some characters are totally forbidden in header names and
|
|
|
|
|
* may happen by accident when writing configs, causing strange
|
|
|
|
|
* failures in field. Better catch these ones early, nobody will
|
|
|
|
|
* miss them. In particular, a colon at the end (or anywhere
|
|
|
|
|
* after the first char) or a space/cr anywhere due to misplaced
|
|
|
|
|
* quotes are hard to spot.
|
|
|
|
|
*/
|
|
|
|
|
for (p = istptr(rule->arg.http.str); p < istend(rule->arg.http.str); p++) {
|
|
|
|
|
if (HTTP_IS_TOKEN(*p))
|
|
|
|
|
continue;
|
|
|
|
|
if (p == istptr(rule->arg.http.str) && *p == ':')
|
|
|
|
|
continue;
|
|
|
|
|
/* we only report this as-is but it will not cause an error */
|
|
|
|
|
memprintf(err, "header name '%s' contains forbidden character '%c'", istptr(rule->arg.http.str), *p);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-12 10:40:30 -05:00
|
|
|
*orig_arg = cur_arg + 1;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-17 03:20:34 -05:00
|
|
|
/* This function executes a replace-header or replace-value actions. It
|
|
|
|
|
* builds a string in the trash from the specified format string. It finds
|
|
|
|
|
* the action to be performed in <.action>, previously filled by function
|
2020-05-05 15:53:22 -04:00
|
|
|
* parse_replace_header(). The replacement action is executed by the function
|
2019-12-17 03:20:34 -05:00
|
|
|
* http_action_replace_header(). On success, it returns ACT_RET_CONT. If an error
|
|
|
|
|
* occurs while soft rewrites are enabled, the action is canceled, but the rule
|
|
|
|
|
* processing continue. Otherwsize ACT_RET_ERR is returned.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_replace_header(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
2020-01-24 09:37:13 -05:00
|
|
|
struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
|
|
|
|
|
struct htx *htx = htxbuf(&msg->chn->buf);
|
2019-12-17 03:20:34 -05:00
|
|
|
enum act_return ret = ACT_RET_CONT;
|
|
|
|
|
struct buffer *replace;
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
replace = alloc_trash_chunk();
|
|
|
|
|
if (!replace)
|
|
|
|
|
goto fail_alloc;
|
|
|
|
|
|
|
|
|
|
replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
|
|
|
|
|
|
|
|
|
|
r = http_replace_hdrs(s, htx, rule->arg.http.str, replace->area, rule->arg.http.re, (rule->action == 0));
|
|
|
|
|
if (r == -1)
|
|
|
|
|
goto fail_rewrite;
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
free_trash_chunk(replace);
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
fail_alloc:
|
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
|
|
|
ret = ACT_RET_ERR;
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
fail_rewrite:
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
|
2019-12-17 03:20:34 -05:00
|
|
|
if (s->flags & SF_BE_ASSIGNED)
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
|
2021-03-08 09:26:48 -05:00
|
|
|
if (sess->listener && sess->listener->counters)
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
|
2019-12-17 03:20:34 -05:00
|
|
|
if (objt_server(s->target))
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
|
2019-12-17 03:20:34 -05:00
|
|
|
|
2020-01-22 08:38:05 -05:00
|
|
|
if (!(msg->flags & HTTP_MSGF_SOFT_RW)) {
|
2019-12-17 03:20:34 -05:00
|
|
|
ret = ACT_RET_ERR;
|
2020-01-22 08:38:05 -05:00
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
|
|
|
}
|
2019-12-17 03:20:34 -05:00
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-12 10:40:30 -05:00
|
|
|
/* Parse a "replace-header" or "replace-value" actions. It takes an header name,
|
|
|
|
|
* a regex and replacement string as arguments. It returns ACT_RET_PRS_OK on
|
|
|
|
|
* success, ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_replace_header(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
2021-10-13 11:22:17 -04:00
|
|
|
int cap = 0, cur_arg;
|
2019-12-12 10:40:30 -05:00
|
|
|
|
2019-12-17 03:20:34 -05:00
|
|
|
if (args[*orig_arg-1][8] == 'h')
|
|
|
|
|
rule->action = 0; // replace-header
|
|
|
|
|
else
|
|
|
|
|
rule->action = 1; // replace-value
|
|
|
|
|
rule->action_ptr = http_action_replace_header;
|
2020-01-14 08:47:34 -05:00
|
|
|
rule->release_ptr = release_http_action;
|
2024-02-23 09:57:21 -05:00
|
|
|
lf_expr_init(&rule->arg.http.fmt);
|
2019-12-12 10:40:30 -05:00
|
|
|
|
|
|
|
|
cur_arg = *orig_arg;
|
|
|
|
|
if (!*args[cur_arg] || !*args[cur_arg+1] || !*args[cur_arg+2]) {
|
|
|
|
|
memprintf(err, "expects exactly 3 arguments");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-15 08:11:08 -04:00
|
|
|
rule->arg.http.str = ist(strdup(args[cur_arg]));
|
2019-12-12 10:40:30 -05:00
|
|
|
|
|
|
|
|
cur_arg++;
|
2020-01-14 08:50:55 -05:00
|
|
|
if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, err))) {
|
2020-03-05 11:56:33 -05:00
|
|
|
istfree(&rule->arg.http.str);
|
2019-12-12 10:40:30 -05:00
|
|
|
return ACT_RET_PRS_ERR;
|
2020-01-14 08:50:55 -05:00
|
|
|
}
|
2019-12-12 10:40:30 -05:00
|
|
|
|
|
|
|
|
if (rule->from == ACT_F_HTTP_REQ) {
|
|
|
|
|
px->conf.args.ctx = ARGC_HRQ;
|
2021-10-13 11:22:17 -04:00
|
|
|
if (px->cap & PR_CAP_FE)
|
|
|
|
|
cap |= SMP_VAL_FE_HRQ_HDR;
|
|
|
|
|
if (px->cap & PR_CAP_BE)
|
|
|
|
|
cap |= SMP_VAL_BE_HRQ_HDR;
|
2019-12-12 10:40:30 -05:00
|
|
|
}
|
|
|
|
|
else{
|
|
|
|
|
px->conf.args.ctx = ARGC_HRS;
|
2021-10-13 11:22:17 -04:00
|
|
|
if (px->cap & PR_CAP_FE)
|
|
|
|
|
cap |= SMP_VAL_FE_HRS_HDR;
|
|
|
|
|
if (px->cap & PR_CAP_BE)
|
|
|
|
|
cap |= SMP_VAL_BE_HRS_HDR;
|
2019-12-12 10:40:30 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cur_arg++;
|
2020-01-14 08:50:55 -05:00
|
|
|
if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
|
2020-03-05 11:56:33 -05:00
|
|
|
istfree(&rule->arg.http.str);
|
2020-01-14 08:50:55 -05:00
|
|
|
regex_free(rule->arg.http.re);
|
2019-12-12 10:40:30 -05:00
|
|
|
return ACT_RET_PRS_ERR;
|
2020-01-14 08:50:55 -05:00
|
|
|
}
|
2019-12-12 10:40:30 -05:00
|
|
|
|
|
|
|
|
*orig_arg = cur_arg + 1;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-20 08:58:48 -05:00
|
|
|
/* This function executes a del-header action with selected matching mode for
|
|
|
|
|
* header name. It finds the matching method to be performed in <.action>, previously
|
|
|
|
|
* filled by function parse_http_del_header(). On success, it returns ACT_RET_CONT.
|
|
|
|
|
* Otherwise ACT_RET_ERR is returned.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_del_header(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
|
|
|
|
struct http_hdr_ctx ctx;
|
|
|
|
|
struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
|
|
|
|
|
struct htx *htx = htxbuf(&msg->chn->buf);
|
|
|
|
|
enum act_return ret = ACT_RET_CONT;
|
|
|
|
|
|
|
|
|
|
/* remove all occurrences of the header */
|
|
|
|
|
ctx.blk = NULL;
|
|
|
|
|
switch (rule->action) {
|
|
|
|
|
case PAT_MATCH_STR:
|
|
|
|
|
while (http_find_header(htx, rule->arg.http.str, &ctx, 1))
|
|
|
|
|
http_remove_header(htx, &ctx);
|
|
|
|
|
break;
|
|
|
|
|
case PAT_MATCH_BEG:
|
|
|
|
|
while (http_find_pfx_header(htx, rule->arg.http.str, &ctx, 1))
|
|
|
|
|
http_remove_header(htx, &ctx);
|
|
|
|
|
break;
|
|
|
|
|
case PAT_MATCH_END:
|
|
|
|
|
while (http_find_sfx_header(htx, rule->arg.http.str, &ctx, 1))
|
|
|
|
|
http_remove_header(htx, &ctx);
|
|
|
|
|
break;
|
|
|
|
|
case PAT_MATCH_SUB:
|
|
|
|
|
while (http_find_sub_header(htx, rule->arg.http.str, &ctx, 1))
|
|
|
|
|
http_remove_header(htx, &ctx);
|
|
|
|
|
break;
|
|
|
|
|
case PAT_MATCH_REG:
|
|
|
|
|
while (http_match_header(htx, rule->arg.http.re, &ctx, 1))
|
|
|
|
|
http_remove_header(htx, &ctx);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return ACT_RET_ERR;
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parse a "del-header" action. It takes string as a required argument,
|
|
|
|
|
* optional flag (currently only -m) and optional matching method of input string
|
|
|
|
|
* with header name to be deleted. Default matching method is exact match (-m str).
|
|
|
|
|
* It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
2019-12-12 10:40:30 -05:00
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_del_header(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
int cur_arg;
|
2020-11-20 08:58:48 -05:00
|
|
|
int pat_idx;
|
2019-12-12 10:40:30 -05:00
|
|
|
|
2020-11-20 08:58:48 -05:00
|
|
|
/* set exact matching (-m str) as default */
|
|
|
|
|
rule->action = PAT_MATCH_STR;
|
|
|
|
|
rule->action_ptr = http_action_del_header;
|
2020-01-14 08:47:34 -05:00
|
|
|
rule->release_ptr = release_http_action;
|
2024-02-23 09:57:21 -05:00
|
|
|
lf_expr_init(&rule->arg.http.fmt);
|
2019-12-12 10:40:30 -05:00
|
|
|
|
|
|
|
|
cur_arg = *orig_arg;
|
|
|
|
|
if (!*args[cur_arg]) {
|
2020-11-20 08:58:48 -05:00
|
|
|
memprintf(err, "expects at least 1 argument");
|
2019-12-12 10:40:30 -05:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-15 08:11:08 -04:00
|
|
|
rule->arg.http.str = ist(strdup(args[cur_arg]));
|
2019-12-12 10:40:30 -05:00
|
|
|
px->conf.args.ctx = (rule->from == ACT_F_HTTP_REQ ? ARGC_HRQ : ARGC_HRS);
|
|
|
|
|
|
2020-11-20 08:58:48 -05:00
|
|
|
if (strcmp(args[cur_arg+1], "-m") == 0) {
|
|
|
|
|
cur_arg++;
|
|
|
|
|
if (!*args[cur_arg+1]) {
|
|
|
|
|
memprintf(err, "-m flag expects exactly 1 argument");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cur_arg++;
|
|
|
|
|
pat_idx = pat_find_match_name(args[cur_arg]);
|
|
|
|
|
switch (pat_idx) {
|
|
|
|
|
case PAT_MATCH_REG:
|
|
|
|
|
if (!(rule->arg.http.re = regex_comp(rule->arg.http.str.ptr, 1, 1, err)))
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
2022-11-14 01:32:04 -05:00
|
|
|
__fallthrough;
|
2020-11-20 08:58:48 -05:00
|
|
|
case PAT_MATCH_STR:
|
|
|
|
|
case PAT_MATCH_BEG:
|
|
|
|
|
case PAT_MATCH_END:
|
|
|
|
|
case PAT_MATCH_SUB:
|
|
|
|
|
rule->action = pat_idx;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
memprintf(err, "-m with unsupported matching method '%s'", args[cur_arg]);
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-12 10:40:30 -05:00
|
|
|
*orig_arg = cur_arg + 1;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-14 08:47:34 -05:00
|
|
|
/* Release memory allocated by an http redirect action. */
|
|
|
|
|
static void release_http_redir(struct act_rule *rule)
|
|
|
|
|
{
|
|
|
|
|
struct redirect_rule *redir;
|
|
|
|
|
|
|
|
|
|
redir = rule->arg.redir;
|
2022-04-25 04:25:15 -04:00
|
|
|
if (!redir)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-04-21 01:32:39 -04:00
|
|
|
LIST_DELETE(&redir->list);
|
2023-05-11 06:42:24 -04:00
|
|
|
http_free_redirect_rule(redir);
|
2020-01-14 08:47:34 -05:00
|
|
|
}
|
|
|
|
|
|
2019-12-12 10:40:30 -05:00
|
|
|
/* Parse a "redirect" action. It returns ACT_RET_PRS_OK on success,
|
|
|
|
|
* ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_redirect(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
struct redirect_rule *redir;
|
|
|
|
|
int dir, cur_arg;
|
|
|
|
|
|
|
|
|
|
rule->action = ACT_HTTP_REDIR;
|
2020-01-14 08:47:34 -05:00
|
|
|
rule->release_ptr = release_http_redir;
|
2019-12-12 10:40:30 -05:00
|
|
|
|
|
|
|
|
cur_arg = *orig_arg;
|
|
|
|
|
|
|
|
|
|
dir = (rule->from == ACT_F_HTTP_REQ ? 0 : 1);
|
|
|
|
|
if ((redir = http_parse_redirect_rule(px->conf.args.file, px->conf.args.line, px, &args[cur_arg], err, 1, dir)) == NULL)
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
|
2021-09-02 10:54:33 -04:00
|
|
|
if (!(redir->flags & REDIRECT_FLAG_IGNORE_EMPTY))
|
|
|
|
|
rule->flags |= ACT_FLAG_FINAL;
|
|
|
|
|
|
2019-12-12 10:40:30 -05:00
|
|
|
rule->arg.redir = redir;
|
|
|
|
|
rule->cond = redir->cond;
|
|
|
|
|
redir->cond = NULL;
|
|
|
|
|
|
|
|
|
|
/* skip all arguments */
|
|
|
|
|
while (*args[cur_arg])
|
|
|
|
|
cur_arg++;
|
|
|
|
|
|
|
|
|
|
*orig_arg = cur_arg;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-17 09:45:23 -05:00
|
|
|
/* This function executes a add-acl, del-acl, set-map or del-map actions. On
|
|
|
|
|
* success, it returns ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_set_map(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
|
|
|
|
struct pat_ref *ref;
|
|
|
|
|
struct buffer *key = NULL, *value = NULL;
|
|
|
|
|
enum act_return ret = ACT_RET_CONT;
|
|
|
|
|
|
|
|
|
|
/* collect reference */
|
|
|
|
|
ref = pat_ref_lookup(rule->arg.map.ref);
|
|
|
|
|
if (!ref)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
/* allocate key */
|
|
|
|
|
key = alloc_trash_chunk();
|
|
|
|
|
if (!key)
|
|
|
|
|
goto fail_alloc;
|
|
|
|
|
|
|
|
|
|
/* collect key */
|
|
|
|
|
key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
|
|
|
|
|
key->area[key->data] = '\0';
|
|
|
|
|
|
|
|
|
|
switch (rule->action) {
|
|
|
|
|
case 0: // add-acl
|
|
|
|
|
/* add entry only if it does not already exist */
|
2023-08-21 12:44:24 -04:00
|
|
|
HA_RWLOCK_WRLOCK(PATREF_LOCK, &ref->lock);
|
2019-12-17 09:45:23 -05:00
|
|
|
if (pat_ref_find_elt(ref, key->area) == NULL)
|
|
|
|
|
pat_ref_add(ref, key->area, NULL, NULL);
|
2023-08-21 12:44:24 -04:00
|
|
|
HA_RWLOCK_WRUNLOCK(PATREF_LOCK, &ref->lock);
|
2019-12-17 09:45:23 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 1: // set-map
|
2023-08-22 10:52:47 -04:00
|
|
|
{
|
|
|
|
|
struct pat_ref_elt *elt;
|
|
|
|
|
|
2019-12-17 09:45:23 -05:00
|
|
|
/* allocate value */
|
|
|
|
|
value = alloc_trash_chunk();
|
|
|
|
|
if (!value)
|
|
|
|
|
goto fail_alloc;
|
|
|
|
|
|
|
|
|
|
/* collect value */
|
|
|
|
|
value->data = build_logline(s, value->area, value->size, &rule->arg.map.value);
|
|
|
|
|
value->area[value->data] = '\0';
|
|
|
|
|
|
2023-08-21 12:44:24 -04:00
|
|
|
HA_RWLOCK_WRLOCK(PATREF_LOCK, &ref->lock);
|
2023-08-22 10:52:47 -04:00
|
|
|
elt = pat_ref_find_elt(ref, key->area);
|
|
|
|
|
if (elt) {
|
2019-12-17 09:45:23 -05:00
|
|
|
/* update entry if it exists */
|
2024-11-20 10:22:22 -05:00
|
|
|
pat_ref_set_elt_duplicate(ref, elt, value->area, NULL);
|
2019-12-17 09:45:23 -05:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* insert a new entry */
|
|
|
|
|
pat_ref_add(ref, key->area, value->area, NULL);
|
|
|
|
|
}
|
2023-08-21 12:44:24 -04:00
|
|
|
HA_RWLOCK_WRUNLOCK(PATREF_LOCK, &ref->lock);
|
2019-12-17 09:45:23 -05:00
|
|
|
break;
|
2023-08-22 10:52:47 -04:00
|
|
|
}
|
2019-12-17 09:45:23 -05:00
|
|
|
|
|
|
|
|
case 2: // del-acl
|
|
|
|
|
case 3: // del-map
|
|
|
|
|
/* returned code: 1=ok, 0=ko */
|
2023-08-21 12:44:24 -04:00
|
|
|
HA_RWLOCK_WRLOCK(PATREF_LOCK, &ref->lock);
|
2019-12-17 09:45:23 -05:00
|
|
|
pat_ref_delete(ref, key->area);
|
2023-08-21 12:44:24 -04:00
|
|
|
HA_RWLOCK_WRUNLOCK(PATREF_LOCK, &ref->lock);
|
2019-12-17 09:45:23 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
ret = ACT_RET_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
free_trash_chunk(key);
|
|
|
|
|
free_trash_chunk(value);
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
fail_alloc:
|
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
|
|
|
ret = ACT_RET_ERR;
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-14 08:47:34 -05:00
|
|
|
/* Release memory allocated by an http map/acl action. */
|
|
|
|
|
static void release_http_map(struct act_rule *rule)
|
|
|
|
|
{
|
|
|
|
|
free(rule->arg.map.ref);
|
2024-02-23 09:57:21 -05:00
|
|
|
lf_expr_deinit(&rule->arg.map.key);
|
2024-01-29 21:14:09 -05:00
|
|
|
if (rule->action == 1)
|
2024-02-23 09:57:21 -05:00
|
|
|
lf_expr_deinit(&rule->arg.map.value);
|
2020-01-14 08:47:34 -05:00
|
|
|
}
|
|
|
|
|
|
2019-12-12 10:40:30 -05:00
|
|
|
/* Parse a "add-acl", "del-acl", "set-map" or "del-map" actions. It takes one or
|
2019-12-17 09:45:23 -05:00
|
|
|
* two log-format string as argument depending on the action. The action is
|
|
|
|
|
* stored in <.action> as an int (0=add-acl, 1=set-map, 2=del-acl,
|
|
|
|
|
* 3=del-map). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
2019-12-12 10:40:30 -05:00
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_set_map(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
2021-10-13 11:22:17 -04:00
|
|
|
int cap = 0, cur_arg;
|
2019-12-12 10:40:30 -05:00
|
|
|
|
2019-12-17 09:45:23 -05:00
|
|
|
if (args[*orig_arg-1][0] == 'a') // add-acl
|
|
|
|
|
rule->action = 0;
|
|
|
|
|
else if (args[*orig_arg-1][0] == 's') // set-map
|
|
|
|
|
rule->action = 1;
|
|
|
|
|
else if (args[*orig_arg-1][4] == 'a') // del-acl
|
|
|
|
|
rule->action = 2;
|
|
|
|
|
else if (args[*orig_arg-1][4] == 'm') // del-map
|
|
|
|
|
rule->action = 3;
|
|
|
|
|
else {
|
|
|
|
|
memprintf(err, "internal error: unhandled action '%s'", args[0]);
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
rule->action_ptr = http_action_set_map;
|
2020-01-14 08:47:34 -05:00
|
|
|
rule->release_ptr = release_http_map;
|
2019-12-12 10:40:30 -05:00
|
|
|
|
|
|
|
|
cur_arg = *orig_arg;
|
2019-12-17 09:45:23 -05:00
|
|
|
if (rule->action == 1 && (!*args[cur_arg] || !*args[cur_arg+1])) {
|
|
|
|
|
/* 2 args for set-map */
|
2019-12-12 10:40:30 -05:00
|
|
|
memprintf(err, "expects exactly 2 arguments");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
else if (!*args[cur_arg]) {
|
2019-12-17 09:45:23 -05:00
|
|
|
/* only one arg for other actions */
|
2019-12-12 10:40:30 -05:00
|
|
|
memprintf(err, "expects exactly 1 arguments");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* '+ 8' for 'set-map(' (same for del-map)
|
|
|
|
|
* '- 9' for 'set-map(' + trailing ')' (same for del-map)
|
|
|
|
|
*/
|
|
|
|
|
rule->arg.map.ref = my_strndup(args[cur_arg-1] + 8, strlen(args[cur_arg-1]) - 9);
|
|
|
|
|
|
|
|
|
|
if (rule->from == ACT_F_HTTP_REQ) {
|
|
|
|
|
px->conf.args.ctx = ARGC_HRQ;
|
2021-10-13 11:22:17 -04:00
|
|
|
if (px->cap & PR_CAP_FE)
|
|
|
|
|
cap |= SMP_VAL_FE_HRQ_HDR;
|
|
|
|
|
if (px->cap & PR_CAP_BE)
|
|
|
|
|
cap |= SMP_VAL_BE_HRQ_HDR;
|
2019-12-12 10:40:30 -05:00
|
|
|
}
|
|
|
|
|
else{
|
|
|
|
|
px->conf.args.ctx = ARGC_HRS;
|
2021-10-13 11:22:17 -04:00
|
|
|
if (px->cap & PR_CAP_FE)
|
|
|
|
|
cap |= SMP_VAL_FE_HRS_HDR;
|
|
|
|
|
if (px->cap & PR_CAP_BE)
|
|
|
|
|
cap |= SMP_VAL_BE_HRS_HDR;
|
2019-12-12 10:40:30 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* key pattern */
|
2024-02-23 09:57:21 -05:00
|
|
|
lf_expr_init(&rule->arg.map.key);
|
2024-11-06 03:48:32 -05:00
|
|
|
if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.key, LOG_OPT_NONE, cap, err)) {
|
2020-01-14 08:50:55 -05:00
|
|
|
free(rule->arg.map.ref);
|
2019-12-12 10:40:30 -05:00
|
|
|
return ACT_RET_PRS_ERR;
|
2020-01-14 08:50:55 -05:00
|
|
|
}
|
2019-12-12 10:40:30 -05:00
|
|
|
|
2019-12-17 09:45:23 -05:00
|
|
|
if (rule->action == 1) {
|
2019-12-12 10:40:30 -05:00
|
|
|
/* value pattern for set-map only */
|
|
|
|
|
cur_arg++;
|
2024-02-23 09:57:21 -05:00
|
|
|
lf_expr_init(&rule->arg.map.value);
|
2024-11-06 03:48:32 -05:00
|
|
|
if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.value, LOG_OPT_NONE, cap, err)) {
|
2020-01-14 08:50:55 -05:00
|
|
|
free(rule->arg.map.ref);
|
2019-12-12 10:40:30 -05:00
|
|
|
return ACT_RET_PRS_ERR;
|
2020-01-14 08:50:55 -05:00
|
|
|
}
|
2019-12-12 10:40:30 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*orig_arg = cur_arg + 1;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-18 03:20:16 -05:00
|
|
|
/* This function executes a track-sc* actions. On success, it returns
|
|
|
|
|
* ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_track_sc(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
|
|
|
|
struct stktable *t;
|
|
|
|
|
struct stksess *ts;
|
|
|
|
|
struct stktable_key *key;
|
MINOR: stick-tables/counters: add http_fail_cnt and http_fail_rate data types
Historically we've been counting lots of client-triggered events in stick
tables to help detect misbehaving ones, but we've been missing the same on
the server side, and there's been repeated requests for being able to count
the server errors per URL in order to precisely monitor the quality of
service or even to avoid routing requests to certain dead services, which
is also called "circuit breaking" nowadays.
This commit introduces http_fail_cnt and http_fail_rate, which work like
http_err_cnt and http_err_rate in that they respectively count events and
their frequency, but they only consider server-side issues such as network
errors, unparsable and truncated responses, and 5xx status codes other
than 501 and 505 (since these ones are usually triggered by the client).
Note that retryable errors are purposely not accounted for, so that only
what the client really sees is considered.
With this it becomes very simple to put some protective measures in place
to perform a redirect or return an excuse page when the error rate goes
beyond a certain threshold for a given URL, and give more chances to the
server to recover from this condition. Typically it could look like this
to bypass a URL causing more than 10 requests per second:
stick-table type string len 80 size 4k expire 1m store http_fail_rate(1m)
http-request track-sc0 base # track host+path, ignore query string
http-request return status 503 content-type text/html \
lf-file excuse.html if { sc0_http_fail_rate gt 10 }
A more advanced mechanism using gpt0 could even implement high/low rates
to disable/enable the service.
Reg-test converteers_ref_cnt_never_dec.vtc was updated to test it.
2021-02-10 06:07:15 -05:00
|
|
|
void *ptr1, *ptr2, *ptr3, *ptr4, *ptr5, *ptr6;
|
2019-12-18 03:20:16 -05:00
|
|
|
int opt;
|
|
|
|
|
|
MINOR: stick-tables/counters: add http_fail_cnt and http_fail_rate data types
Historically we've been counting lots of client-triggered events in stick
tables to help detect misbehaving ones, but we've been missing the same on
the server side, and there's been repeated requests for being able to count
the server errors per URL in order to precisely monitor the quality of
service or even to avoid routing requests to certain dead services, which
is also called "circuit breaking" nowadays.
This commit introduces http_fail_cnt and http_fail_rate, which work like
http_err_cnt and http_err_rate in that they respectively count events and
their frequency, but they only consider server-side issues such as network
errors, unparsable and truncated responses, and 5xx status codes other
than 501 and 505 (since these ones are usually triggered by the client).
Note that retryable errors are purposely not accounted for, so that only
what the client really sees is considered.
With this it becomes very simple to put some protective measures in place
to perform a redirect or return an excuse page when the error rate goes
beyond a certain threshold for a given URL, and give more chances to the
server to recover from this condition. Typically it could look like this
to bypass a URL causing more than 10 requests per second:
stick-table type string len 80 size 4k expire 1m store http_fail_rate(1m)
http-request track-sc0 base # track host+path, ignore query string
http-request return status 503 content-type text/html \
lf-file excuse.html if { sc0_http_fail_rate gt 10 }
A more advanced mechanism using gpt0 could even implement high/low rates
to disable/enable the service.
Reg-test converteers_ref_cnt_never_dec.vtc was updated to test it.
2021-02-10 06:07:15 -05:00
|
|
|
ptr1 = ptr2 = ptr3 = ptr4 = ptr5 = ptr6 = NULL;
|
2019-12-18 03:20:16 -05:00
|
|
|
opt = ((rule->from == ACT_F_HTTP_REQ) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES) | SMP_OPT_FINAL;
|
|
|
|
|
|
|
|
|
|
t = rule->arg.trk_ctr.table.t;
|
2021-03-10 10:58:03 -05:00
|
|
|
|
|
|
|
|
if (stkctr_entry(&s->stkctr[rule->action]))
|
|
|
|
|
goto end;
|
|
|
|
|
|
2019-12-18 03:20:16 -05:00
|
|
|
key = stktable_fetch_key(t, s->be, sess, s, opt, rule->arg.trk_ctr.expr, NULL);
|
|
|
|
|
|
|
|
|
|
if (!key)
|
|
|
|
|
goto end;
|
|
|
|
|
ts = stktable_get_entry(t, key);
|
|
|
|
|
if (!ts)
|
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
|
|
stream_track_stkctr(&s->stkctr[rule->action], t, ts);
|
|
|
|
|
|
|
|
|
|
/* let's count a new HTTP request as it's the first time we do it */
|
|
|
|
|
ptr1 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
|
|
|
|
|
ptr2 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE);
|
|
|
|
|
|
|
|
|
|
/* When the client triggers a 4xx from the server, it's most often due
|
|
|
|
|
* to a missing object or permission. These events should be tracked
|
|
|
|
|
* because if they happen often, it may indicate a brute force or a
|
|
|
|
|
* vulnerability scan. Normally this is done when receiving the response
|
|
|
|
|
* but here we're tracking after this ought to have been done so we have
|
|
|
|
|
* to do it on purpose.
|
|
|
|
|
*/
|
2024-01-10 12:44:30 -05:00
|
|
|
if (rule->from == ACT_F_HTTP_RES &&
|
|
|
|
|
http_status_matches(http_err_status_codes, s->txn->status)) {
|
2019-12-18 03:20:16 -05:00
|
|
|
ptr3 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_CNT);
|
|
|
|
|
ptr4 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_RATE);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-10 12:44:30 -05:00
|
|
|
if (rule->from == ACT_F_HTTP_RES &&
|
|
|
|
|
http_status_matches(http_fail_status_codes, s->txn->status)) {
|
MINOR: stick-tables/counters: add http_fail_cnt and http_fail_rate data types
Historically we've been counting lots of client-triggered events in stick
tables to help detect misbehaving ones, but we've been missing the same on
the server side, and there's been repeated requests for being able to count
the server errors per URL in order to precisely monitor the quality of
service or even to avoid routing requests to certain dead services, which
is also called "circuit breaking" nowadays.
This commit introduces http_fail_cnt and http_fail_rate, which work like
http_err_cnt and http_err_rate in that they respectively count events and
their frequency, but they only consider server-side issues such as network
errors, unparsable and truncated responses, and 5xx status codes other
than 501 and 505 (since these ones are usually triggered by the client).
Note that retryable errors are purposely not accounted for, so that only
what the client really sees is considered.
With this it becomes very simple to put some protective measures in place
to perform a redirect or return an excuse page when the error rate goes
beyond a certain threshold for a given URL, and give more chances to the
server to recover from this condition. Typically it could look like this
to bypass a URL causing more than 10 requests per second:
stick-table type string len 80 size 4k expire 1m store http_fail_rate(1m)
http-request track-sc0 base # track host+path, ignore query string
http-request return status 503 content-type text/html \
lf-file excuse.html if { sc0_http_fail_rate gt 10 }
A more advanced mechanism using gpt0 could even implement high/low rates
to disable/enable the service.
Reg-test converteers_ref_cnt_never_dec.vtc was updated to test it.
2021-02-10 06:07:15 -05:00
|
|
|
ptr5 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_FAIL_CNT);
|
|
|
|
|
ptr6 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_FAIL_RATE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ptr1 || ptr2 || ptr3 || ptr4 || ptr5 || ptr6) {
|
2019-12-18 03:20:16 -05:00
|
|
|
HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
|
|
|
|
|
|
|
|
|
|
if (ptr1)
|
2021-06-30 11:18:28 -04:00
|
|
|
stktable_data_cast(ptr1, std_t_uint)++;
|
2019-12-18 03:20:16 -05:00
|
|
|
if (ptr2)
|
2021-06-30 11:18:28 -04:00
|
|
|
update_freq_ctr_period(&stktable_data_cast(ptr2, std_t_frqp),
|
2019-12-18 03:20:16 -05:00
|
|
|
t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
|
|
|
|
|
if (ptr3)
|
2021-06-30 11:18:28 -04:00
|
|
|
stktable_data_cast(ptr3, std_t_uint)++;
|
2019-12-18 03:20:16 -05:00
|
|
|
if (ptr4)
|
2021-06-30 11:18:28 -04:00
|
|
|
update_freq_ctr_period(&stktable_data_cast(ptr4, std_t_frqp),
|
2019-12-18 03:20:16 -05:00
|
|
|
t->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u, 1);
|
MINOR: stick-tables/counters: add http_fail_cnt and http_fail_rate data types
Historically we've been counting lots of client-triggered events in stick
tables to help detect misbehaving ones, but we've been missing the same on
the server side, and there's been repeated requests for being able to count
the server errors per URL in order to precisely monitor the quality of
service or even to avoid routing requests to certain dead services, which
is also called "circuit breaking" nowadays.
This commit introduces http_fail_cnt and http_fail_rate, which work like
http_err_cnt and http_err_rate in that they respectively count events and
their frequency, but they only consider server-side issues such as network
errors, unparsable and truncated responses, and 5xx status codes other
than 501 and 505 (since these ones are usually triggered by the client).
Note that retryable errors are purposely not accounted for, so that only
what the client really sees is considered.
With this it becomes very simple to put some protective measures in place
to perform a redirect or return an excuse page when the error rate goes
beyond a certain threshold for a given URL, and give more chances to the
server to recover from this condition. Typically it could look like this
to bypass a URL causing more than 10 requests per second:
stick-table type string len 80 size 4k expire 1m store http_fail_rate(1m)
http-request track-sc0 base # track host+path, ignore query string
http-request return status 503 content-type text/html \
lf-file excuse.html if { sc0_http_fail_rate gt 10 }
A more advanced mechanism using gpt0 could even implement high/low rates
to disable/enable the service.
Reg-test converteers_ref_cnt_never_dec.vtc was updated to test it.
2021-02-10 06:07:15 -05:00
|
|
|
if (ptr5)
|
2021-06-30 11:18:28 -04:00
|
|
|
stktable_data_cast(ptr5, std_t_uint)++;
|
MINOR: stick-tables/counters: add http_fail_cnt and http_fail_rate data types
Historically we've been counting lots of client-triggered events in stick
tables to help detect misbehaving ones, but we've been missing the same on
the server side, and there's been repeated requests for being able to count
the server errors per URL in order to precisely monitor the quality of
service or even to avoid routing requests to certain dead services, which
is also called "circuit breaking" nowadays.
This commit introduces http_fail_cnt and http_fail_rate, which work like
http_err_cnt and http_err_rate in that they respectively count events and
their frequency, but they only consider server-side issues such as network
errors, unparsable and truncated responses, and 5xx status codes other
than 501 and 505 (since these ones are usually triggered by the client).
Note that retryable errors are purposely not accounted for, so that only
what the client really sees is considered.
With this it becomes very simple to put some protective measures in place
to perform a redirect or return an excuse page when the error rate goes
beyond a certain threshold for a given URL, and give more chances to the
server to recover from this condition. Typically it could look like this
to bypass a URL causing more than 10 requests per second:
stick-table type string len 80 size 4k expire 1m store http_fail_rate(1m)
http-request track-sc0 base # track host+path, ignore query string
http-request return status 503 content-type text/html \
lf-file excuse.html if { sc0_http_fail_rate gt 10 }
A more advanced mechanism using gpt0 could even implement high/low rates
to disable/enable the service.
Reg-test converteers_ref_cnt_never_dec.vtc was updated to test it.
2021-02-10 06:07:15 -05:00
|
|
|
if (ptr6)
|
2021-06-30 11:18:28 -04:00
|
|
|
update_freq_ctr_period(&stktable_data_cast(ptr6, std_t_frqp),
|
MINOR: stick-tables/counters: add http_fail_cnt and http_fail_rate data types
Historically we've been counting lots of client-triggered events in stick
tables to help detect misbehaving ones, but we've been missing the same on
the server side, and there's been repeated requests for being able to count
the server errors per URL in order to precisely monitor the quality of
service or even to avoid routing requests to certain dead services, which
is also called "circuit breaking" nowadays.
This commit introduces http_fail_cnt and http_fail_rate, which work like
http_err_cnt and http_err_rate in that they respectively count events and
their frequency, but they only consider server-side issues such as network
errors, unparsable and truncated responses, and 5xx status codes other
than 501 and 505 (since these ones are usually triggered by the client).
Note that retryable errors are purposely not accounted for, so that only
what the client really sees is considered.
With this it becomes very simple to put some protective measures in place
to perform a redirect or return an excuse page when the error rate goes
beyond a certain threshold for a given URL, and give more chances to the
server to recover from this condition. Typically it could look like this
to bypass a URL causing more than 10 requests per second:
stick-table type string len 80 size 4k expire 1m store http_fail_rate(1m)
http-request track-sc0 base # track host+path, ignore query string
http-request return status 503 content-type text/html \
lf-file excuse.html if { sc0_http_fail_rate gt 10 }
A more advanced mechanism using gpt0 could even implement high/low rates
to disable/enable the service.
Reg-test converteers_ref_cnt_never_dec.vtc was updated to test it.
2021-02-10 06:07:15 -05:00
|
|
|
t->data_arg[STKTABLE_DT_HTTP_FAIL_RATE].u, 1);
|
2019-12-18 03:20:16 -05:00
|
|
|
|
|
|
|
|
HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
|
|
|
|
|
|
|
|
|
|
/* If data was modified, we need to touch to re-schedule sync */
|
|
|
|
|
stktable_touch_local(t, ts, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_CONTENT);
|
|
|
|
|
if (sess->fe != s->be)
|
|
|
|
|
stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_BACKEND);
|
|
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
}
|
2019-12-12 10:40:30 -05:00
|
|
|
|
2020-01-14 08:47:34 -05:00
|
|
|
static void release_http_track_sc(struct act_rule *rule)
|
|
|
|
|
{
|
|
|
|
|
release_sample_expr(rule->arg.trk_ctr.expr);
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-12 10:40:30 -05:00
|
|
|
/* Parse a "track-sc*" actions. It returns ACT_RET_PRS_OK on success,
|
|
|
|
|
* ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_track_sc(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
struct sample_expr *expr;
|
|
|
|
|
unsigned int where;
|
|
|
|
|
unsigned int tsc_num;
|
|
|
|
|
const char *tsc_num_str;
|
|
|
|
|
int cur_arg;
|
|
|
|
|
|
|
|
|
|
tsc_num_str = &args[*orig_arg-1][8];
|
|
|
|
|
if (cfg_parse_track_sc_num(&tsc_num, tsc_num_str, tsc_num_str + strlen(tsc_num_str), err) == -1)
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
|
|
|
|
|
cur_arg = *orig_arg;
|
|
|
|
|
expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line,
|
2020-02-14 10:50:14 -05:00
|
|
|
err, &px->conf.args, NULL);
|
2019-12-12 10:40:30 -05:00
|
|
|
if (!expr)
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
|
|
|
|
|
where = 0;
|
|
|
|
|
if (px->cap & PR_CAP_FE)
|
|
|
|
|
where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_FE_HRS_HDR);
|
|
|
|
|
if (px->cap & PR_CAP_BE)
|
|
|
|
|
where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_BE_HRQ_HDR : SMP_VAL_BE_HRS_HDR);
|
|
|
|
|
|
|
|
|
|
if (!(expr->fetch->val & where)) {
|
|
|
|
|
memprintf(err, "fetch method '%s' extracts information from '%s', none of which is available here",
|
|
|
|
|
args[cur_arg-1], sample_src_names(expr->fetch->use));
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2019-12-12 10:40:30 -05:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(args[cur_arg], "table") == 0) {
|
|
|
|
|
cur_arg++;
|
|
|
|
|
if (!*args[cur_arg]) {
|
|
|
|
|
memprintf(err, "missing table name");
|
2020-01-14 08:50:55 -05:00
|
|
|
release_sample_expr(expr);
|
2019-12-12 10:40:30 -05:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* we copy the table name for now, it will be resolved later */
|
|
|
|
|
rule->arg.trk_ctr.table.n = strdup(args[cur_arg]);
|
|
|
|
|
cur_arg++;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-18 03:20:16 -05:00
|
|
|
rule->action = tsc_num;
|
2019-12-12 10:40:30 -05:00
|
|
|
rule->arg.trk_ctr.expr = expr;
|
2019-12-18 03:20:16 -05:00
|
|
|
rule->action_ptr = http_action_track_sc;
|
2020-01-14 08:47:34 -05:00
|
|
|
rule->release_ptr = release_http_track_sc;
|
2019-12-12 10:40:30 -05:00
|
|
|
rule->check_ptr = check_trk_action;
|
|
|
|
|
|
|
|
|
|
*orig_arg = cur_arg;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-10 07:43:54 -05:00
|
|
|
static enum act_return action_timeout_set_stream_timeout(struct act_rule *rule,
|
|
|
|
|
struct proxy *px,
|
|
|
|
|
struct session *sess,
|
|
|
|
|
struct stream *s,
|
|
|
|
|
int flags)
|
|
|
|
|
{
|
|
|
|
|
struct sample *key;
|
|
|
|
|
|
|
|
|
|
if (rule->arg.timeout.expr) {
|
|
|
|
|
key = sample_fetch_as_type(px, sess, s, SMP_OPT_FINAL, rule->arg.timeout.expr, SMP_T_SINT);
|
|
|
|
|
if (!key)
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
|
|
|
|
|
stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(key->data.u.sint));
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(rule->arg.timeout.value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parse a "set-timeout" action. Returns ACT_RET_PRS_ERR if parsing error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_set_timeout(const char **args,
|
|
|
|
|
int *orig_arg,
|
|
|
|
|
struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
int cur_arg;
|
|
|
|
|
|
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
|
|
|
rule->action_ptr = action_timeout_set_stream_timeout;
|
|
|
|
|
rule->release_ptr = release_timeout_action;
|
|
|
|
|
|
|
|
|
|
cur_arg = *orig_arg;
|
|
|
|
|
if (!*args[cur_arg] || !*args[cur_arg + 1]) {
|
|
|
|
|
memprintf(err, "expects exactly 2 arguments");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-27 10:43:07 -04:00
|
|
|
if (cfg_parse_rule_set_timeout(args, cur_arg, rule, px, err) == -1) {
|
2020-12-10 07:43:54 -05:00
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*orig_arg = cur_arg + 2;
|
|
|
|
|
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-20 04:07:22 -05:00
|
|
|
/* This function executes a strict-mode actions. On success, it always returns
|
|
|
|
|
* ACT_RET_CONT
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_strict_mode(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
|
|
|
|
struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
|
|
|
|
|
|
|
|
|
|
if (rule->action == 0) // strict-mode on
|
|
|
|
|
msg->flags &= ~HTTP_MSGF_SOFT_RW;
|
|
|
|
|
else // strict-mode off
|
|
|
|
|
msg->flags |= HTTP_MSGF_SOFT_RW;
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parse a "strict-mode" action. It returns ACT_RET_PRS_OK on success,
|
|
|
|
|
* ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_strict_mode(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
int cur_arg;
|
|
|
|
|
|
|
|
|
|
cur_arg = *orig_arg;
|
|
|
|
|
if (!*args[cur_arg]) {
|
|
|
|
|
memprintf(err, "expects exactly 1 arguments");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcasecmp(args[cur_arg], "on") == 0)
|
|
|
|
|
rule->action = 0; // strict-mode on
|
|
|
|
|
else if (strcasecmp(args[cur_arg], "off") == 0)
|
|
|
|
|
rule->action = 1; // strict-mode off
|
|
|
|
|
else {
|
|
|
|
|
memprintf(err, "Unexpected value '%s'. Only 'on' and 'off' are supported", args[cur_arg]);
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
rule->action_ptr = http_action_strict_mode;
|
|
|
|
|
|
|
|
|
|
*orig_arg = cur_arg + 1;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
MEDIUM: http-rules: Add the return action to HTTP rules
Thanks to this new action, it is now possible to return any responses from
HAProxy, with any status code, based on an errorfile, a file or a string. Unlike
the other internal messages generated by HAProxy, these ones are not interpreted
as errors. And it is not necessary to use a file containing a full HTTP
response, although it is still possible. In addition, using a log-format string
or a log-format file, it is possible to have responses with a dynamic
content. This action can be used on the request path or the response path. The
only constraint is to have a responses smaller than a buffer. And to avoid any
warning the buffer space reserved to the headers rewritting should also be free.
When a response is returned with a file or a string as payload, it only contains
the content-length header and the content-type header, if applicable. Here are
examples:
http-request return content-type image/x-icon file /var/www/favicon.ico \
if { path /favicon.ico }
http-request return status 403 content-type text/plain \
lf-string "Access denied. IP %[src] is blacklisted." \
if { src -f /etc/haproxy/blacklist.lst }
2020-01-24 11:44:23 -05:00
|
|
|
/* This function executes a return action. It builds an HTX message from an
|
|
|
|
|
* errorfile, an raw file or a log-format string, depending on <.action>
|
|
|
|
|
* value. On success, it returns ACT_RET_ABRT. If an error occurs ACT_RET_ERR is
|
|
|
|
|
* returned.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_return(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
|
|
|
|
struct channel *req = &s->req;
|
|
|
|
|
|
2021-02-19 05:41:01 -05:00
|
|
|
s->txn->status = rule->arg.http_reply->status;
|
2023-11-28 02:38:45 -05:00
|
|
|
|
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
|
|
|
s->flags |= SF_ERR_LOCAL;
|
|
|
|
|
if (!(s->flags & SF_FINST_MASK))
|
|
|
|
|
s->flags |= ((rule->from == ACT_F_HTTP_REQ) ? SF_FINST_R : SF_FINST_H);
|
|
|
|
|
|
2020-05-13 10:38:37 -04:00
|
|
|
if (http_reply_message(s, rule->arg.http_reply) == -1)
|
|
|
|
|
return ACT_RET_ERR;
|
MEDIUM: http-rules: Add the return action to HTTP rules
Thanks to this new action, it is now possible to return any responses from
HAProxy, with any status code, based on an errorfile, a file or a string. Unlike
the other internal messages generated by HAProxy, these ones are not interpreted
as errors. And it is not necessary to use a file containing a full HTTP
response, although it is still possible. In addition, using a log-format string
or a log-format file, it is possible to have responses with a dynamic
content. This action can be used on the request path or the response path. The
only constraint is to have a responses smaller than a buffer. And to avoid any
warning the buffer space reserved to the headers rewritting should also be free.
When a response is returned with a file or a string as payload, it only contains
the content-length header and the content-type header, if applicable. Here are
examples:
http-request return content-type image/x-icon file /var/www/favicon.ico \
if { path /favicon.ico }
http-request return status 403 content-type text/plain \
lf-string "Access denied. IP %[src] is blacklisted." \
if { src -f /etc/haproxy/blacklist.lst }
2020-01-24 11:44:23 -05:00
|
|
|
|
|
|
|
|
if (rule->from == ACT_F_HTTP_REQ) {
|
|
|
|
|
/* let's log the request time */
|
2023-04-28 03:16:15 -04:00
|
|
|
s->logs.request_ts = now_ns;
|
MEDIUM: http-rules: Add the return action to HTTP rules
Thanks to this new action, it is now possible to return any responses from
HAProxy, with any status code, based on an errorfile, a file or a string. Unlike
the other internal messages generated by HAProxy, these ones are not interpreted
as errors. And it is not necessary to use a file containing a full HTTP
response, although it is still possible. In addition, using a log-format string
or a log-format file, it is possible to have responses with a dynamic
content. This action can be used on the request path or the response path. The
only constraint is to have a responses smaller than a buffer. And to avoid any
warning the buffer space reserved to the headers rewritting should also be free.
When a response is returned with a file or a string as payload, it only contains
the content-length header and the content-type header, if applicable. Here are
examples:
http-request return content-type image/x-icon file /var/www/favicon.ico \
if { path /favicon.ico }
http-request return status 403 content-type text/plain \
lf-string "Access denied. IP %[src] is blacklisted." \
if { src -f /etc/haproxy/blacklist.lst }
2020-01-24 11:44:23 -05:00
|
|
|
req->analysers &= AN_REQ_FLT_END;
|
|
|
|
|
|
|
|
|
|
if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */
|
2021-04-06 07:53:36 -04:00
|
|
|
_HA_ATOMIC_INC(&s->sess->fe->fe_counters.intercepted_req);
|
MEDIUM: http-rules: Add the return action to HTTP rules
Thanks to this new action, it is now possible to return any responses from
HAProxy, with any status code, based on an errorfile, a file or a string. Unlike
the other internal messages generated by HAProxy, these ones are not interpreted
as errors. And it is not necessary to use a file containing a full HTTP
response, although it is still possible. In addition, using a log-format string
or a log-format file, it is possible to have responses with a dynamic
content. This action can be used on the request path or the response path. The
only constraint is to have a responses smaller than a buffer. And to avoid any
warning the buffer space reserved to the headers rewritting should also be free.
When a response is returned with a file or a string as payload, it only contains
the content-length header and the content-type header, if applicable. Here are
examples:
http-request return content-type image/x-icon file /var/www/favicon.ico \
if { path /favicon.ico }
http-request return status 403 content-type text/plain \
lf-string "Access denied. IP %[src] is blacklisted." \
if { src -f /etc/haproxy/blacklist.lst }
2020-01-24 11:44:23 -05:00
|
|
|
}
|
|
|
|
|
|
2020-05-13 10:38:37 -04:00
|
|
|
return ACT_RET_ABRT;
|
MEDIUM: http-rules: Add the return action to HTTP rules
Thanks to this new action, it is now possible to return any responses from
HAProxy, with any status code, based on an errorfile, a file or a string. Unlike
the other internal messages generated by HAProxy, these ones are not interpreted
as errors. And it is not necessary to use a file containing a full HTTP
response, although it is still possible. In addition, using a log-format string
or a log-format file, it is possible to have responses with a dynamic
content. This action can be used on the request path or the response path. The
only constraint is to have a responses smaller than a buffer. And to avoid any
warning the buffer space reserved to the headers rewritting should also be free.
When a response is returned with a file or a string as payload, it only contains
the content-length header and the content-type header, if applicable. Here are
examples:
http-request return content-type image/x-icon file /var/www/favicon.ico \
if { path /favicon.ico }
http-request return status 403 content-type text/plain \
lf-string "Access denied. IP %[src] is blacklisted." \
if { src -f /etc/haproxy/blacklist.lst }
2020-01-24 11:44:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parse a "return" action. It returns ACT_RET_PRS_OK on success,
|
2020-05-13 08:36:55 -04:00
|
|
|
* ACT_RET_PRS_ERR on error. It relies on http_parse_http_reply() to set
|
|
|
|
|
* <.arg.http_reply>.
|
MEDIUM: http-rules: Add the return action to HTTP rules
Thanks to this new action, it is now possible to return any responses from
HAProxy, with any status code, based on an errorfile, a file or a string. Unlike
the other internal messages generated by HAProxy, these ones are not interpreted
as errors. And it is not necessary to use a file containing a full HTTP
response, although it is still possible. In addition, using a log-format string
or a log-format file, it is possible to have responses with a dynamic
content. This action can be used on the request path or the response path. The
only constraint is to have a responses smaller than a buffer. And to avoid any
warning the buffer space reserved to the headers rewritting should also be free.
When a response is returned with a file or a string as payload, it only contains
the content-length header and the content-type header, if applicable. Here are
examples:
http-request return content-type image/x-icon file /var/www/favicon.ico \
if { path /favicon.ico }
http-request return status 403 content-type text/plain \
lf-string "Access denied. IP %[src] is blacklisted." \
if { src -f /etc/haproxy/blacklist.lst }
2020-01-24 11:44:23 -05:00
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_return(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
2020-05-13 08:36:55 -04:00
|
|
|
/* Prepare parsing of log-format strings */
|
|
|
|
|
px->conf.args.ctx = ((rule->from == ACT_F_HTTP_REQ) ? ARGC_HRQ : ARGC_HRS);
|
|
|
|
|
rule->arg.http_reply = http_parse_http_reply(args, orig_arg, px, 200, err);
|
|
|
|
|
if (!rule->arg.http_reply)
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
MEDIUM: http-rules: Add the return action to HTTP rules
Thanks to this new action, it is now possible to return any responses from
HAProxy, with any status code, based on an errorfile, a file or a string. Unlike
the other internal messages generated by HAProxy, these ones are not interpreted
as errors. And it is not necessary to use a file containing a full HTTP
response, although it is still possible. In addition, using a log-format string
or a log-format file, it is possible to have responses with a dynamic
content. This action can be used on the request path or the response path. The
only constraint is to have a responses smaller than a buffer. And to avoid any
warning the buffer space reserved to the headers rewritting should also be free.
When a response is returned with a file or a string as payload, it only contains
the content-length header and the content-type header, if applicable. Here are
examples:
http-request return content-type image/x-icon file /var/www/favicon.ico \
if { path /favicon.ico }
http-request return status 403 content-type text/plain \
lf-string "Access denied. IP %[src] is blacklisted." \
if { src -f /etc/haproxy/blacklist.lst }
2020-01-24 11:44:23 -05:00
|
|
|
|
2020-05-13 02:50:07 -04:00
|
|
|
rule->flags |= ACT_FLAG_FINAL;
|
2020-05-12 12:33:37 -04:00
|
|
|
rule->action = ACT_CUSTOM;
|
2020-05-13 11:56:56 -04:00
|
|
|
rule->check_ptr = check_act_http_reply;
|
MEDIUM: http-rules: Add the return action to HTTP rules
Thanks to this new action, it is now possible to return any responses from
HAProxy, with any status code, based on an errorfile, a file or a string. Unlike
the other internal messages generated by HAProxy, these ones are not interpreted
as errors. And it is not necessary to use a file containing a full HTTP
response, although it is still possible. In addition, using a log-format string
or a log-format file, it is possible to have responses with a dynamic
content. This action can be used on the request path or the response path. The
only constraint is to have a responses smaller than a buffer. And to avoid any
warning the buffer space reserved to the headers rewritting should also be free.
When a response is returned with a file or a string as payload, it only contains
the content-length header and the content-type header, if applicable. Here are
examples:
http-request return content-type image/x-icon file /var/www/favicon.ico \
if { path /favicon.ico }
http-request return status 403 content-type text/plain \
lf-string "Access denied. IP %[src] is blacklisted." \
if { src -f /etc/haproxy/blacklist.lst }
2020-01-24 11:44:23 -05:00
|
|
|
rule->action_ptr = http_action_return;
|
2020-05-13 11:56:56 -04:00
|
|
|
rule->release_ptr = release_act_http_reply;
|
MEDIUM: http-rules: Add the return action to HTTP rules
Thanks to this new action, it is now possible to return any responses from
HAProxy, with any status code, based on an errorfile, a file or a string. Unlike
the other internal messages generated by HAProxy, these ones are not interpreted
as errors. And it is not necessary to use a file containing a full HTTP
response, although it is still possible. In addition, using a log-format string
or a log-format file, it is possible to have responses with a dynamic
content. This action can be used on the request path or the response path. The
only constraint is to have a responses smaller than a buffer. And to avoid any
warning the buffer space reserved to the headers rewritting should also be free.
When a response is returned with a file or a string as payload, it only contains
the content-length header and the content-type header, if applicable. Here are
examples:
http-request return content-type image/x-icon file /var/www/favicon.ico \
if { path /favicon.ico }
http-request return status 403 content-type text/plain \
lf-string "Access denied. IP %[src] is blacklisted." \
if { src -f /etc/haproxy/blacklist.lst }
2020-01-24 11:44:23 -05:00
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-29 04:46:38 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/* This function executes a wait-for-body action. It waits for the message
|
|
|
|
|
* payload for a max configured time (.arg.p[0]) and eventually for only first
|
|
|
|
|
* <arg.p[1]> bytes (0 means no limit). It relies on http_wait_for_msg_body()
|
|
|
|
|
* function. it returns ACT_RET_CONT when conditions are met to stop to wait.
|
|
|
|
|
* Otherwise ACT_RET_YIELD is returned to wait for more data. ACT_RET_INV is
|
|
|
|
|
* returned if a parsing error is raised by lower level and ACT_RET_ERR if an
|
2021-04-24 04:25:42 -04:00
|
|
|
* internal error occurred. Finally ACT_RET_ABRT is returned when a timeout
|
|
|
|
|
* occurred.
|
2021-03-29 04:46:38 -04:00
|
|
|
*/
|
|
|
|
|
static enum act_return http_action_wait_for_body(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
|
|
|
|
struct channel *chn = ((rule->from == ACT_F_HTTP_REQ) ? &s->req : &s->res);
|
|
|
|
|
unsigned int time = (uintptr_t)rule->arg.act.p[0];
|
|
|
|
|
unsigned int bytes = (uintptr_t)rule->arg.act.p[1];
|
|
|
|
|
|
|
|
|
|
switch (http_wait_for_msg_body(s, chn, time, bytes)) {
|
|
|
|
|
case HTTP_RULE_RES_CONT:
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
case HTTP_RULE_RES_YIELD:
|
|
|
|
|
return ACT_RET_YIELD;
|
|
|
|
|
case HTTP_RULE_RES_BADREQ:
|
|
|
|
|
return ACT_RET_INV;
|
|
|
|
|
case HTTP_RULE_RES_ERROR:
|
|
|
|
|
return ACT_RET_ERR;
|
|
|
|
|
case HTTP_RULE_RES_ABRT:
|
|
|
|
|
return ACT_RET_ABRT;
|
|
|
|
|
default:
|
|
|
|
|
return ACT_RET_ERR;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parse a "wait-for-body" action. It returns ACT_RET_PRS_OK on success,
|
|
|
|
|
* ACT_RET_PRS_ERR on error.
|
|
|
|
|
*/
|
|
|
|
|
static enum act_parse_ret parse_http_wait_for_body(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
int cur_arg;
|
|
|
|
|
unsigned int time, bytes;
|
|
|
|
|
const char *res;
|
|
|
|
|
|
|
|
|
|
cur_arg = *orig_arg;
|
|
|
|
|
if (!*args[cur_arg]) {
|
|
|
|
|
memprintf(err, "expects time <time> [ at-least <bytes> ]");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
time = UINT_MAX; /* To be sure it is set */
|
|
|
|
|
bytes = 0; /* Default value, wait all the body */
|
|
|
|
|
while (*(args[cur_arg])) {
|
|
|
|
|
if (strcmp(args[cur_arg], "time") == 0) {
|
|
|
|
|
if (!*args[cur_arg + 1]) {
|
|
|
|
|
memprintf(err, "missing argument for '%s'", args[cur_arg]);
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
res = parse_time_err(args[cur_arg+1], &time, TIME_UNIT_MS);
|
|
|
|
|
if (res == PARSE_TIME_OVER) {
|
|
|
|
|
memprintf(err, "time overflow (maximum value is 2147483647 ms or ~24.8 days)");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
if (res == PARSE_TIME_UNDER) {
|
|
|
|
|
memprintf(err, "time underflow (minimum non-null value is 1 ms)");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
if (res) {
|
|
|
|
|
memprintf(err, "unexpected character '%c'", *res);
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
cur_arg++;
|
|
|
|
|
}
|
|
|
|
|
else if (strcmp(args[cur_arg], "at-least") == 0) {
|
|
|
|
|
if (!*args[cur_arg + 1]) {
|
|
|
|
|
memprintf(err, "missing argument for '%s'", args[cur_arg]);
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
res = parse_size_err(args[cur_arg+1], &bytes);
|
|
|
|
|
if (res) {
|
|
|
|
|
memprintf(err, "unexpected character '%c'", *res);
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
cur_arg++;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
break;
|
|
|
|
|
cur_arg++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (time == UINT_MAX) {
|
|
|
|
|
memprintf(err, "expects time <time> [ at-least <bytes> ]");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rule->arg.act.p[0] = (void *)(uintptr_t)time;
|
|
|
|
|
rule->arg.act.p[1] = (void *)(uintptr_t)bytes;
|
|
|
|
|
|
|
|
|
|
*orig_arg = cur_arg;
|
|
|
|
|
|
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
|
|
|
rule->action_ptr = http_action_wait_for_body;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
MINOR: action: add do-log action
Thanks to the two previous commits, we can now expose the do-log action
on all available action contexts, including the new quic-init context.
Each context is responsible for exposing the do-log action by registering
the relevant log steps, saving the idendifier, and then store it in the
rule's context so that do_log_action() automatically uses it to produce
the log during runtime.
To use the feature, it is simply needed to use "do-log" (without argument)
on an action directive, example:
tcp-request connection do-log
As mentioned before, each context where the action is exposed has its own
log step identifier. Currently known identifiers are:
quic-initial: quic-init
tcp-request connection: tcp-req-conn
tcp-request session: tcp-req-sess
tcp-request content: tcp-req-cont
tcp-response content: tcp-res-cont
http-request: http-req
http-response: http-res
http-after-response: http-after-res
Thus, these "additional" logging steps can be used as-is under log-profile
section (after "on" keyword). However, although the parser will accept
them, it makes no sense to use them with the "log-steps" proxy keyword,
since the only path for these origins to trigger a log generation is
through the explicit use of "do-log" action.
This need was described in GH #401, it should help to conditionally
trigger logs using ACL at specific key points.. and may either be used
alone or combined with "log-steps" to add additional log "trackers" during
transaction handling.
Documentation was updated and some examples were added.
2024-09-20 03:34:13 -04:00
|
|
|
static enum log_orig_id do_log_http_req;
|
|
|
|
|
static enum log_orig_id do_log_http_res;
|
|
|
|
|
static enum log_orig_id do_log_http_after_res;
|
|
|
|
|
|
|
|
|
|
static void init_do_log(void)
|
|
|
|
|
{
|
|
|
|
|
do_log_http_req = log_orig_register("http-req");
|
|
|
|
|
BUG_ON(do_log_http_req == LOG_ORIG_UNSPEC);
|
|
|
|
|
do_log_http_res = log_orig_register("http-res");
|
|
|
|
|
BUG_ON(do_log_http_res == LOG_ORIG_UNSPEC);
|
|
|
|
|
do_log_http_after_res = log_orig_register("http-after-res");
|
|
|
|
|
BUG_ON(do_log_http_after_res == LOG_ORIG_UNSPEC);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
INITCALL0(STG_PREPARE, init_do_log);
|
|
|
|
|
|
|
|
|
|
static enum act_parse_ret parse_http_req_do_log(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
return do_log_parse_act(do_log_http_req, args, orig_arg, px, rule, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum act_parse_ret parse_http_res_do_log(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
return do_log_parse_act(do_log_http_res, args, orig_arg, px, rule, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum act_parse_ret parse_http_after_res_do_log(const char **args, int *orig_arg, struct proxy *px,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
return do_log_parse_act(do_log_http_after_res, args, orig_arg, px, rule, err);
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 10:01:16 -04:00
|
|
|
/************************************************************************/
|
|
|
|
|
/* All supported http-request action keywords must be declared here. */
|
|
|
|
|
/************************************************************************/
|
|
|
|
|
|
|
|
|
|
static struct action_kw_list http_req_actions = {
|
|
|
|
|
.kw = {
|
2021-05-06 09:33:09 -04:00
|
|
|
{ "add-acl", parse_http_set_map, KWF_MATCH_PREFIX },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "add-header", parse_http_set_header, 0 },
|
|
|
|
|
{ "allow", parse_http_allow, 0 },
|
|
|
|
|
{ "auth", parse_http_auth, 0 },
|
|
|
|
|
{ "capture", parse_http_req_capture, 0 },
|
2021-05-06 09:33:09 -04:00
|
|
|
{ "del-acl", parse_http_set_map, KWF_MATCH_PREFIX },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "del-header", parse_http_del_header, 0 },
|
2021-05-06 09:33:09 -04:00
|
|
|
{ "del-map", parse_http_set_map, KWF_MATCH_PREFIX },
|
2020-01-13 15:49:03 -05:00
|
|
|
{ "deny", parse_http_deny, 0 },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "disable-l7-retry", parse_http_req_disable_l7_retry, 0 },
|
MINOR: action: add do-log action
Thanks to the two previous commits, we can now expose the do-log action
on all available action contexts, including the new quic-init context.
Each context is responsible for exposing the do-log action by registering
the relevant log steps, saving the idendifier, and then store it in the
rule's context so that do_log_action() automatically uses it to produce
the log during runtime.
To use the feature, it is simply needed to use "do-log" (without argument)
on an action directive, example:
tcp-request connection do-log
As mentioned before, each context where the action is exposed has its own
log step identifier. Currently known identifiers are:
quic-initial: quic-init
tcp-request connection: tcp-req-conn
tcp-request session: tcp-req-sess
tcp-request content: tcp-req-cont
tcp-response content: tcp-res-cont
http-request: http-req
http-response: http-res
http-after-response: http-after-res
Thus, these "additional" logging steps can be used as-is under log-profile
section (after "on" keyword). However, although the parser will accept
them, it makes no sense to use them with the "log-steps" proxy keyword,
since the only path for these origins to trigger a log generation is
through the explicit use of "do-log" action.
This need was described in GH #401, it should help to conditionally
trigger logs using ACL at specific key points.. and may either be used
alone or combined with "log-steps" to add additional log "trackers" during
transaction handling.
Documentation was updated and some examples were added.
2024-09-20 03:34:13 -04:00
|
|
|
{ "do-log", parse_http_req_do_log, 0 },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "early-hint", parse_http_set_header, 0 },
|
2021-05-06 09:50:12 -04:00
|
|
|
{ "normalize-uri", parse_http_normalize_uri, KWF_EXPERIMENTAL },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "redirect", parse_http_redirect, 0 },
|
|
|
|
|
{ "reject", parse_http_action_reject, 0 },
|
|
|
|
|
{ "replace-header", parse_http_replace_header, 0 },
|
|
|
|
|
{ "replace-path", parse_replace_uri, 0 },
|
2020-09-02 11:17:44 -04:00
|
|
|
{ "replace-pathq", parse_replace_uri, 0 },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "replace-uri", parse_replace_uri, 0 },
|
|
|
|
|
{ "replace-value", parse_http_replace_header, 0 },
|
MEDIUM: http-rules: Add the return action to HTTP rules
Thanks to this new action, it is now possible to return any responses from
HAProxy, with any status code, based on an errorfile, a file or a string. Unlike
the other internal messages generated by HAProxy, these ones are not interpreted
as errors. And it is not necessary to use a file containing a full HTTP
response, although it is still possible. In addition, using a log-format string
or a log-format file, it is possible to have responses with a dynamic
content. This action can be used on the request path or the response path. The
only constraint is to have a responses smaller than a buffer. And to avoid any
warning the buffer space reserved to the headers rewritting should also be free.
When a response is returned with a file or a string as payload, it only contains
the content-length header and the content-type header, if applicable. Here are
examples:
http-request return content-type image/x-icon file /var/www/favicon.ico \
if { path /favicon.ico }
http-request return status 403 content-type text/plain \
lf-string "Access denied. IP %[src] is blacklisted." \
if { src -f /etc/haproxy/blacklist.lst }
2020-01-24 11:44:23 -05:00
|
|
|
{ "return", parse_http_return, 0 },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "set-header", parse_http_set_header, 0 },
|
2021-05-06 09:33:09 -04:00
|
|
|
{ "set-map", parse_http_set_map, KWF_MATCH_PREFIX },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "set-method", parse_set_req_line, 0 },
|
|
|
|
|
{ "set-path", parse_set_req_line, 0 },
|
2020-09-02 11:17:44 -04:00
|
|
|
{ "set-pathq", parse_set_req_line, 0 },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "set-query", parse_set_req_line, 0 },
|
|
|
|
|
{ "set-uri", parse_set_req_line, 0 },
|
2019-12-20 04:07:22 -05:00
|
|
|
{ "strict-mode", parse_http_strict_mode, 0 },
|
2020-01-13 15:49:03 -05:00
|
|
|
{ "tarpit", parse_http_deny, 0 },
|
2021-05-06 09:33:09 -04:00
|
|
|
{ "track-sc", parse_http_track_sc, KWF_MATCH_PREFIX },
|
2020-12-10 07:43:54 -05:00
|
|
|
{ "set-timeout", parse_http_set_timeout, 0 },
|
2021-03-29 04:46:38 -04:00
|
|
|
{ "wait-for-body", parse_http_wait_for_body, 0 },
|
2018-10-02 10:01:16 -04:00
|
|
|
{ NULL, NULL }
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2018-11-25 13:14:37 -05:00
|
|
|
INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
|
|
|
|
|
|
2018-10-02 10:01:16 -04:00
|
|
|
static struct action_kw_list http_res_actions = {
|
|
|
|
|
.kw = {
|
2021-05-06 09:33:09 -04:00
|
|
|
{ "add-acl", parse_http_set_map, KWF_MATCH_PREFIX },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "add-header", parse_http_set_header, 0 },
|
|
|
|
|
{ "allow", parse_http_allow, 0 },
|
|
|
|
|
{ "capture", parse_http_res_capture, 0 },
|
2021-05-06 09:33:09 -04:00
|
|
|
{ "del-acl", parse_http_set_map, KWF_MATCH_PREFIX },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "del-header", parse_http_del_header, 0 },
|
2021-05-06 09:33:09 -04:00
|
|
|
{ "del-map", parse_http_set_map, KWF_MATCH_PREFIX },
|
2020-01-13 15:49:03 -05:00
|
|
|
{ "deny", parse_http_deny, 0 },
|
MINOR: action: add do-log action
Thanks to the two previous commits, we can now expose the do-log action
on all available action contexts, including the new quic-init context.
Each context is responsible for exposing the do-log action by registering
the relevant log steps, saving the idendifier, and then store it in the
rule's context so that do_log_action() automatically uses it to produce
the log during runtime.
To use the feature, it is simply needed to use "do-log" (without argument)
on an action directive, example:
tcp-request connection do-log
As mentioned before, each context where the action is exposed has its own
log step identifier. Currently known identifiers are:
quic-initial: quic-init
tcp-request connection: tcp-req-conn
tcp-request session: tcp-req-sess
tcp-request content: tcp-req-cont
tcp-response content: tcp-res-cont
http-request: http-req
http-response: http-res
http-after-response: http-after-res
Thus, these "additional" logging steps can be used as-is under log-profile
section (after "on" keyword). However, although the parser will accept
them, it makes no sense to use them with the "log-steps" proxy keyword,
since the only path for these origins to trigger a log generation is
through the explicit use of "do-log" action.
This need was described in GH #401, it should help to conditionally
trigger logs using ACL at specific key points.. and may either be used
alone or combined with "log-steps" to add additional log "trackers" during
transaction handling.
Documentation was updated and some examples were added.
2024-09-20 03:34:13 -04:00
|
|
|
{ "do-log", parse_http_res_do_log, 0 },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "redirect", parse_http_redirect, 0 },
|
|
|
|
|
{ "replace-header", parse_http_replace_header, 0 },
|
|
|
|
|
{ "replace-value", parse_http_replace_header, 0 },
|
MEDIUM: http-rules: Add the return action to HTTP rules
Thanks to this new action, it is now possible to return any responses from
HAProxy, with any status code, based on an errorfile, a file or a string. Unlike
the other internal messages generated by HAProxy, these ones are not interpreted
as errors. And it is not necessary to use a file containing a full HTTP
response, although it is still possible. In addition, using a log-format string
or a log-format file, it is possible to have responses with a dynamic
content. This action can be used on the request path or the response path. The
only constraint is to have a responses smaller than a buffer. And to avoid any
warning the buffer space reserved to the headers rewritting should also be free.
When a response is returned with a file or a string as payload, it only contains
the content-length header and the content-type header, if applicable. Here are
examples:
http-request return content-type image/x-icon file /var/www/favicon.ico \
if { path /favicon.ico }
http-request return status 403 content-type text/plain \
lf-string "Access denied. IP %[src] is blacklisted." \
if { src -f /etc/haproxy/blacklist.lst }
2020-01-24 11:44:23 -05:00
|
|
|
{ "return", parse_http_return, 0 },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "set-header", parse_http_set_header, 0 },
|
2021-05-06 09:33:09 -04:00
|
|
|
{ "set-map", parse_http_set_map, KWF_MATCH_PREFIX },
|
2019-12-12 10:40:30 -05:00
|
|
|
{ "set-status", parse_http_set_status, 0 },
|
2019-12-20 04:07:22 -05:00
|
|
|
{ "strict-mode", parse_http_strict_mode, 0 },
|
2021-05-06 09:33:09 -04:00
|
|
|
{ "track-sc", parse_http_track_sc, KWF_MATCH_PREFIX },
|
2023-10-16 10:09:13 -04:00
|
|
|
{ "set-timeout", parse_http_set_timeout, 0 },
|
2021-03-29 04:46:38 -04:00
|
|
|
{ "wait-for-body", parse_http_wait_for_body, 0 },
|
2018-10-02 10:01:16 -04:00
|
|
|
{ NULL, NULL }
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2018-11-25 13:14:37 -05:00
|
|
|
INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
|
2018-10-02 10:01:16 -04:00
|
|
|
|
2020-01-22 03:26:35 -05:00
|
|
|
static struct action_kw_list http_after_res_actions = {
|
|
|
|
|
.kw = {
|
|
|
|
|
{ "add-header", parse_http_set_header, 0 },
|
|
|
|
|
{ "allow", parse_http_allow, 0 },
|
2021-12-06 02:43:22 -05:00
|
|
|
{ "capture", parse_http_res_capture, 0 },
|
2023-01-05 05:17:38 -05:00
|
|
|
{ "del-acl", parse_http_set_map, KWF_MATCH_PREFIX },
|
2020-01-22 03:26:35 -05:00
|
|
|
{ "del-header", parse_http_del_header, 0 },
|
2023-01-05 05:17:38 -05:00
|
|
|
{ "del-map", parse_http_set_map, KWF_MATCH_PREFIX },
|
MINOR: action: add do-log action
Thanks to the two previous commits, we can now expose the do-log action
on all available action contexts, including the new quic-init context.
Each context is responsible for exposing the do-log action by registering
the relevant log steps, saving the idendifier, and then store it in the
rule's context so that do_log_action() automatically uses it to produce
the log during runtime.
To use the feature, it is simply needed to use "do-log" (without argument)
on an action directive, example:
tcp-request connection do-log
As mentioned before, each context where the action is exposed has its own
log step identifier. Currently known identifiers are:
quic-initial: quic-init
tcp-request connection: tcp-req-conn
tcp-request session: tcp-req-sess
tcp-request content: tcp-req-cont
tcp-response content: tcp-res-cont
http-request: http-req
http-response: http-res
http-after-response: http-after-res
Thus, these "additional" logging steps can be used as-is under log-profile
section (after "on" keyword). However, although the parser will accept
them, it makes no sense to use them with the "log-steps" proxy keyword,
since the only path for these origins to trigger a log generation is
through the explicit use of "do-log" action.
This need was described in GH #401, it should help to conditionally
trigger logs using ACL at specific key points.. and may either be used
alone or combined with "log-steps" to add additional log "trackers" during
transaction handling.
Documentation was updated and some examples were added.
2024-09-20 03:34:13 -04:00
|
|
|
{ "do-log", parse_http_after_res_do_log, 0 },
|
2020-01-22 03:26:35 -05:00
|
|
|
{ "replace-header", parse_http_replace_header, 0 },
|
|
|
|
|
{ "replace-value", parse_http_replace_header, 0 },
|
|
|
|
|
{ "set-header", parse_http_set_header, 0 },
|
2023-01-05 05:17:38 -05:00
|
|
|
{ "set-map", parse_http_set_map, KWF_MATCH_PREFIX },
|
2020-01-22 03:26:35 -05:00
|
|
|
{ "set-status", parse_http_set_status, 0 },
|
|
|
|
|
{ "strict-mode", parse_http_strict_mode, 0 },
|
|
|
|
|
{ NULL, NULL }
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
INITCALL1(STG_REGISTER, http_after_res_keywords_register, &http_after_res_actions);
|
|
|
|
|
|
2018-10-02 10:01:16 -04:00
|
|
|
/*
|
|
|
|
|
* Local variables:
|
|
|
|
|
* c-indent-level: 8
|
|
|
|
|
* c-basic-offset: 8
|
|
|
|
|
* End:
|
|
|
|
|
*/
|