]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: http-rules: Support an optional error message in http deny rules
authorChristopher Faulet <cfaulet@haproxy.com>
Tue, 14 Jan 2020 11:00:28 +0000 (12:00 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 20 Jan 2020 14:18:46 +0000 (15:18 +0100)
It is now possible to set the error message to use when a deny rule is
executed. It may be a specific error file, adding "errorfile <file>" :

  http-request deny deny_status 400 errorfile /etc/haproxy/errorfiles/400badreq.http

It may also be an error file from an http-errors section, adding "errorfiles
<name>" :

  http-request deny errorfiles my-errors  # use 403 error from "my-errors" section

When defined, this error message is set in the HTTP transaction. The tarpit rule
is also concerned by this change.

doc/configuration.txt
include/proto/http_htx.h
include/types/action.h
src/http_act.c
src/http_ana.c

index d12a3ae1c0274bffe00195dba81bd026136f9e88..93b9c65434897dd926a0987cd44ba3039b0231b0 100644 (file)
@@ -4412,12 +4412,17 @@ http-request del-map(<file-name>) <key fmt> [ { if | unless } <condition> ]
   It takes one argument: "file name" It is the equivalent of the "del map"
   command from the stats socket, but can be triggered by an HTTP request.
 
-http-request deny [deny_status <status>] [ { if | unless } <condition> ]
+http-request deny [deny_status <status>] [ { errorfile | errorfiles } <err> ]
+                           [ { if | unless } <condition> ]
 
   This stops the evaluation of the rules and immediately rejects the request
   and emits an HTTP 403 error, or optionally the status code specified as an
   argument to "deny_status". The list of permitted status codes is limited to
-  those that can be overridden by the "errorfile" directive.
+  those that can be overridden by the "errorfile" directive. A specific error
+  message may be specified. It may be an error file, using the "errorfile"
+  keyword followed by the file containing the full HTTP response. It may also
+  be an error from an http-errors section, using the "errorfiles" keyword
+  followed by the section name.
   No further "http-request" rules are evaluated.
 
 http-request disable-l7-retry [ { if | unless } <condition> ]
@@ -4912,7 +4917,8 @@ http-request strict-mode { on | off }
   the frontend, the default mode is restored when HAProxy starts the backend
   rules evaluation.
 
-http-request tarpit [deny_status <status>] [ { if | unless } <condition> ]
+http-request tarpit [deny_status <status>] [ { errorfile | errorfiles } <err> ]
+                           [ { if | unless } <condition> ]
 
   This stops the evaluation of the rules and immediately blocks the request
   without responding for a delay specified by "timeout tarpit" or
@@ -4925,7 +4931,11 @@ http-request tarpit [deny_status <status>] [ { if | unless } <condition> ]
   efficient against very dumb robots, and will significantly reduce the load
   on firewalls compared to a "deny" rule. But when facing "correctly"
   developed robots, it can make things worse by forcing haproxy and the front
-  firewall to support insane number of concurrent connections.
+  firewall to support insane number of concurrent connections. A specific error
+  message may be specified. It may be an error file, using the "errorfile"
+  keyword followed by the file containing the full HTTP response. It may also
+  be an error from an http-errors section, using the "errorfiles" keyword
+  followed by the section name.
   See also the "silent-drop" action.
 
 http-request track-sc0 <key> [table <table>] [ { if | unless } <condition> ]
@@ -5107,12 +5117,17 @@ http-response del-map(<file-name>) <key fmt> [ { if | unless } <condition> ]
   It takes one argument: "file name" It is the equivalent of the "del map"
   command from the stats socket, but can be triggered by an HTTP response.
 
-http-response deny [deny_status <status>] [ { if | unless } <condition> ]
+http-response deny [deny_status <status>] [ { errorfile | errorfiles } <err> ]
+                           [ { if | unless } <condition> ]
 
   This stops the evaluation of the rules and immediately rejects the response
   and emits an HTTP 502 error, or optionally the status code specified as an
   argument to "deny_status". The list of permitted status codes is limited to
-  those that can be overridden by the "errorfile" directive.
+  those that can be overridden by the "errorfile" directive. A specific error
+  message may be specified. It may be an error file, using the "errorfile"
+  keyword followed by the file containing the full HTTP response. It may also
+  be an error from an http-errors section, using the "errorfiles" keyword
+  followed by the section name.
   No further "http-response" rules are evaluated.
 
 http-response redirect <rule> [ { if | unless } <condition> ]
index 0b25d41ff14381b348189094ed5d754e3cd93ee2..4b980e2cf5b60d36928a7a28d39e94256e578b8f 100644 (file)
@@ -29,6 +29,7 @@
 #include <types/http_htx.h>
 
 extern struct buffer http_err_chunks[HTTP_ERR_SIZE];
+extern struct list http_errors_list;
 
 struct htx_sl *http_get_stline(struct htx *htx);
 int http_find_header(const struct htx *htx, const struct ist name, struct http_hdr_ctx *ctx, int full);
index 63dcfcf94dfb13a030c83fae614382ca066aaed3..6ec3f018d0a206883598f94b938067a8fbe9d7a7 100644 (file)
@@ -123,6 +123,10 @@ struct act_rule {
                        struct list fmt;       /* log-format compatible expression */
                        struct my_regex *re;   /* used by replace-header/value/uri/path */
                } http;                        /* args used by some HTTP rules */
+               struct {
+                       int status;            /* status code */
+                       struct buffer *errmsg; /* HTTP error message, may be NULL */
+               } http_deny;                   /* args used by HTTP deny rules */
                struct redirect_rule *redir;   /* redirect rule or "http-request redirect" */
                struct {
                        char *ref;             /* MAP or ACL file name to update */
index 218b1c4917016ccaeb363d1903c1fb6be93b0d73..774df140adb73b078103d17d2d09b37fd83700ac 100644 (file)
@@ -785,52 +785,118 @@ static enum act_parse_ret parse_http_allow(const char **args, int *orig_arg, str
        return ACT_RET_PRS_OK;
 }
 
+/* Check an "http-request deny" action when an http-errors section is referenced.
+ *
+ * The function returns 1 in success case, otherwise, it returns 0 and err is
+ * filled.
+ */
+static int check_http_deny_action(struct act_rule *rule, struct proxy *px, char **err)
+{
+       struct http_errors *http_errs;
+       int status = (intptr_t)(rule->arg.act.p[0]);
+       int ret = 1;
+
+       list_for_each_entry(http_errs, &http_errors_list, list) {
+               if (strcmp(http_errs->id, (char *)rule->arg.act.p[1]) == 0) {
+                       free(rule->arg.act.p[1]);
+                       rule->arg.http_deny.status = status;
+                       rule->arg.http_deny.errmsg = http_errs->errmsg[http_get_status_idx(status)];
+                       if (!rule->arg.http_deny.errmsg)
+                               ha_warning("Proxy '%s': status '%d' referenced by http deny rule "
+                                          "not declared in http-errors section '%s'.\n",
+                                          px->id, status, http_errs->id);
+                       break;
+               }
+       }
+
+       if (&http_errs->list == &http_errors_list) {
+               memprintf(err, "unknown http-errors section '%s' referenced by http deny rule",
+                         (char *)rule->arg.act.p[1]);
+               free(rule->arg.act.p[1]);
+               ret = 0;
+       }
+
+       return ret;
+}
+
 /* Parse "deny" or "tarpit" actions for a request rule or "deny" action for a
- * response rule. It may take 2 optional arguments to define the status code. It
- * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
+ * response rule. It may take optional arguments to define the status code, the
+ * error file or the http-errors section to use. It returns ACT_RET_PRS_OK on
+ * success, ACT_RET_PRS_ERR on error.
  */
 static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
                                          struct act_rule *rule, char **err)
 {
-       int code, hc, cur_arg;
+       int default_status, status, hc, cur_arg;
+
 
        cur_arg = *orig_arg;
        if (rule->from == ACT_F_HTTP_REQ) {
                if (!strcmp(args[cur_arg-1], "tarpit")) {
                        rule->action = ACT_HTTP_REQ_TARPIT;
-                       rule->arg.http.i = HTTP_ERR_500;
+                       default_status = status = 500;
                }
                else {
                        rule->action = ACT_ACTION_DENY;
-                       rule->arg.http.i = HTTP_ERR_403;
+                       default_status = status = 403;
                }
        }
        else {
-               rule->action = ACT_ACTION_DENY;;
-               rule->arg.http.i = HTTP_ERR_502;
+               rule->action = ACT_ACTION_DENY;
+               default_status = status = 502;
        }
        rule->flags |= ACT_FLAG_FINAL;
 
        if (strcmp(args[cur_arg], "deny_status") == 0) {
                cur_arg++;
                if (!*args[cur_arg]) {
-                       memprintf(err, "missing status code.\n");
+                       memprintf(err, "'%s' expects <status_code> as argument", args[cur_arg-1]);
                        return ACT_RET_PRS_ERR;
                }
 
-               code = atol(args[cur_arg]);
+               status = atol(args[cur_arg]);
                cur_arg++;
                for (hc = 0; hc < HTTP_ERR_SIZE; hc++) {
-                       if (http_err_codes[hc] == code) {
-                               rule->arg.http.i = hc;
+                       if (http_err_codes[hc] == status)
                                break;
-                       }
                }
-               if (hc >= HTTP_ERR_SIZE)
-                       memprintf(err, "status code %d not handled, using default code %d",
-                                 code, http_err_codes[rule->arg.http.i]);
+               if (hc >= HTTP_ERR_SIZE) {
+                       memprintf(err, "status code '%d' not handled, using default code '%d'",
+                                 status, default_status);
+                       status = default_status;
+                       hc = http_get_status_idx(status);
+               }
        }
 
+       if (strcmp(args[cur_arg], "errorfile") == 0) {
+               cur_arg++;
+               if (!*args[cur_arg]) {
+                       memprintf(err, "'%s' expects <file> as argument", args[cur_arg-1]);
+                       return ACT_RET_PRS_ERR;
+               }
+
+               rule->arg.http_deny.errmsg = http_load_errorfile(args[cur_arg], err);
+               if (!rule->arg.http_deny.errmsg)
+                       return ACT_RET_PRS_ERR;
+               cur_arg++;
+       }
+       else if (strcmp(args[cur_arg], "errorfiles") == 0) {
+               cur_arg++;
+               if (!*args[cur_arg]) {
+                       memprintf(err, "'%s' expects <http_errors_name> as argument", args[cur_arg-1]);
+                       return ACT_RET_PRS_ERR;
+               }
+               /* Must be resolved during the config validity check */
+               rule->arg.act.p[0] = (void *)((intptr_t)status);
+               rule->arg.act.p[1] = strdup(args[cur_arg]);
+               rule->check_ptr = check_http_deny_action;
+               cur_arg++;
+               goto out;
+       }
+
+       rule->arg.http_deny.status = status;
+
+  out:
        *orig_arg = cur_arg;
        return ACT_RET_PRS_OK;
 }
index 10ee24111565ba86472cb0a4f415445aa1746d7e..82a9e14a26ad93a6b935b7179e361ea7d3ca94f2 100644 (file)
@@ -2896,13 +2896,17 @@ static enum rule_result http_req_get_intercept_rule(struct proxy *px, struct lis
 
                        case ACT_ACTION_DENY:
                                txn->flags |= TX_CLDENY;
-                               txn->status = http_err_codes[rule->arg.http.i];
+                               txn->status = rule->arg.http_deny.status;
+                               if (rule->arg.http_deny.errmsg)
+                                       txn->errmsg = rule->arg.http_deny.errmsg;
                                rule_ret = HTTP_RULE_RES_DENY;
                                goto end;
 
                        case ACT_HTTP_REQ_TARPIT:
                                txn->flags |= TX_CLTARPIT;
-                               txn->status = http_err_codes[rule->arg.http.i];
+                               txn->status = rule->arg.http_deny.status;
+                               if (rule->arg.http_deny.errmsg)
+                                       txn->errmsg = rule->arg.http_deny.errmsg;
                                rule_ret = HTTP_RULE_RES_DENY;
                                goto end;
 
@@ -3073,7 +3077,9 @@ resume_execution:
 
                        case ACT_ACTION_DENY:
                                txn->flags |= TX_CLDENY;
-                               txn->status = http_err_codes[rule->arg.http.i];
+                               txn->status = rule->arg.http_deny.status;
+                               if (rule->arg.http_deny.errmsg)
+                                       txn->errmsg = rule->arg.http_deny.errmsg;
                                rule_ret = HTTP_RULE_RES_DENY;
                                goto end;