]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: proxy: Add a directive to reference an http-errors section in a proxy
authorChristopher Faulet <cfaulet@haproxy.com>
Mon, 13 Jan 2020 14:52:01 +0000 (15:52 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 20 Jan 2020 14:18:46 +0000 (15:18 +0100)
It is now possible to import in a proxy, fully or partially, error files
declared in an http-errors section. It may be done using the "errorfiles"
directive, followed by a name and optionally a list of status code. If there is
no status code specified, all error files of the http-errors section are
imported. Otherwise, only error files associated to the listed status code are
imported. For instance :

  http-errors my-errors
      errorfile 400 ...
      errorfile 403 ...
      errorfile 404 ...

  frontend frt
      errorfiles my-errors 403 404  # ==> error 400 not imported

doc/configuration.txt
include/proto/http_htx.h
include/types/proxy.h
src/cfgparse-listen.c
src/http_htx.c
src/proxy.c

index 1114f63e4090dafd3c890863fd6564ca78602837..1d69380c8a7ed09170f3d9829bff61d27822d945 100644 (file)
@@ -52,6 +52,7 @@ Summary
 3.5.      Peers
 3.6.      Mailers
 3.7.      Programs
+3.8.      HTTP-errors
 
 4.    Proxies
 4.1.      Proxy keywords matrix
@@ -2378,6 +2379,45 @@ no option start-on-reload
   program section.
 
 
+3.8. HTTP-errors
+----------------
+
+It is possible to globally declare several groups of HTTP errors, to be
+imported afterwards in any proxy section. Same group may be referenced at
+several places and can be fully or partially imported.
+
+http-errors <name>
+  Create a new http-errors group with the name <name>. It is an independent
+  section that may be referenced by one or more proxies using its name.
+
+errorfile <code> <file>
+  Associate a file contents to an HTTP error code
+
+  Arguments :
+    <code>    is the HTTP status code. Currently, HAProxy is capable of
+              generating codes 200, 400, 403, 404, 405, 408, 410, 425, 429,
+              500, 502, 503, and 504.
+
+    <file>    designates a file containing the full HTTP response. It is
+              recommended to follow the common practice of appending ".http" to
+              the filename so that people do not confuse the response with HTML
+              error pages, and to use absolute paths, since files are read
+              before any chroot is performed.
+
+  Please referrers to "errorfile" keyword in section 4 for details.
+
+  Example:
+    http-errors website-1
+        errorfile 400 /etc/haproxy/errorfiles/site1/400.http
+        errorfile 404 /etc/haproxy/errorfiles/site1/404.http
+        errorfile 408 /dev/null  # work around Chrome pre-connect bug
+
+    http-errors website-2
+        errorfile 400 /etc/haproxy/errorfiles/site2/400.http
+        errorfile 404 /etc/haproxy/errorfiles/site2/404.http
+        errorfile 408 /dev/null  # work around Chrome pre-connect bug
+
+
 4. Proxies
 ----------
 
@@ -2489,6 +2529,7 @@ email-alert myhostname                    X          X         X         X
 email-alert to                            X          X         X         X
 enabled                                   X          X         X         X
 errorfile                                 X          X         X         X
+errorfiles                                X          X         X         X
 errorloc                                  X          X         X         X
 errorloc302                               X          X         X         X
 -- keyword -------------------------- defaults - frontend - listen -- backend -
@@ -3651,6 +3692,34 @@ errorfile <code> <file>
         errorfile 503 /etc/haproxy/errorfiles/503sorry.http
 
 
+errorfiles <name> [<code> ...]
+  Import, fully or partially, the error files defined in the <name> http-errors
+  section.
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    yes   |   yes  |   yes
+  Arguments :
+    <name>  is the name of an existing http-errors section.
+
+    <code>  is a HTTP status code. Several status code may be listed.
+            Currently, HAProxy is capable of generating codes 200, 400, 403,
+            404, 405, 408, 410, 425, 429, 500, 502, 503, and 504.
+
+  Errors defined in the http-errors section with the name <name> are imported
+  in the current proxy. If no status code is specified, all error files of the
+  http-errors section are imported. Otherwise, only error files associated to
+  the listed status code are imported. Those error files override the already
+  defined custom errors for the proxy. And they may be overridden by following
+  ones. Fonctionnly, it is exactly the same than declaring all error files by
+  hand using "errorfile" directives.
+
+  See also : "errorfile", "errorloc", "errorloc302" , "errorloc303" and section
+             3.8 about http-errors.
+
+  Example :
+        errorfiles generic
+       errorfiles site-1 403 404
+
+
 errorloc <code> <url>
 errorloc302 <code> <url>
   Return an HTTP redirection to a URL instead of errors generated by HAProxy
index 3f8634010ba5f55be4f639a34fef7357d90db9fa..0b25d41ff14381b348189094ed5d754e3cd93ee2 100644 (file)
@@ -53,5 +53,7 @@ struct buffer *http_load_errorfile(const char *file, char **errmsg);
 struct buffer *http_load_errormsg(const char *key, const struct ist msg, char **errmsg);
 struct buffer *http_parse_errorfile(int status, const char *file, char **errmsg);
 struct buffer *http_parse_errorloc(int errloc, int status, const char *url, char **errmsg);
+int proxy_dup_default_conf_errors(struct proxy *curpx, struct proxy *defpx, char **errmsg);
+void proxy_release_conf_errors(struct proxy *px);
 
 #endif /* _PROTO_HTTP_HTX_H */
index 232634decc3022a0887c4ee62fc5de7a9f21f373..c018c26f8649108b1254605219affc9cb261ecf9 100644 (file)
@@ -449,6 +449,7 @@ struct proxy {
                struct eb_root used_server_name; /* list of server names in use */
                struct list bind;               /* list of bind settings */
                struct list listeners;          /* list of listeners belonging to this frontend */
+               struct list errors;             /* list of all custom error files */
                struct arg_list args;           /* sample arg list that need to be resolved */
                struct ebpt_node by_name;       /* proxies are stored sorted by name here */
                char *logformat_string;         /* log format string */
index 61bf7cd829ec69ff9dcc62c300550a0d80b4f881..86d1f3366f0ad5b7d4d05f2cffabba6a90aa785a 100644 (file)
@@ -262,7 +262,11 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                }
 
                /* initialize error relocations */
-               memcpy(&curproxy->errmsg, &defproxy.errmsg, sizeof(defproxy.errmsg));
+               if (!proxy_dup_default_conf_errors(curproxy, &defproxy, &errmsg)) {
+                       ha_alert("parsing [%s:%d] : proxy '%s' : %s\n", file, linenum, curproxy->id, errmsg);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
 
                if (curproxy->cap & PR_CAP_FE) {
                        curproxy->maxconn = defproxy.maxconn;
@@ -502,7 +506,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                        free(defproxy.conf.logformat_sd_string);
                free(defproxy.conf.lfsd_file);
 
-               memset(&defproxy.errmsg, 0, sizeof(defproxy.errmsg));
+               proxy_release_conf_errors(&defproxy);
 
                /* we cannot free uri_auth because it might already be used */
                init_default_instance();
index e848e37e84d15c4260cb20702e47063fda57ad00..016fd8f2bc9543eaed7bbe154afed910d3eedc5d 100644 (file)
@@ -32,6 +32,27 @@ struct buffer http_err_chunks[HTTP_ERR_SIZE];
 struct eb_root http_error_messages = EB_ROOT;
 struct list http_errors_list = LIST_HEAD_INIT(http_errors_list);
 
+/* The declaration of an errorfiles/errorfile directives. Used during config
+ * parsing only. */
+struct conf_errors {
+       char type;                                  /* directive type (0: errorfiles, 1: errorfile) */
+       union {
+               struct {
+                       int status;                 /* the status code associated to this error */
+                       struct buffer *msg;         /* the HTX error message */
+               } errorfile;                        /* describe an "errorfile" directive */
+               struct {
+                       char *name;                 /* the http-errors section name */
+                       char status[HTTP_ERR_SIZE]; /* list of status to import (0: ignore, 1: implicit import, 2: explicit import) */
+               } errorfiles;                       /* describe an "errorfiles" directive */
+       } info;
+
+       char *file;                                 /* file where the directive appears */
+       int line;                                   /* line where the directive appears */
+
+       struct list list;                           /* next conf_errors */
+};
+
 static int http_update_authority(struct htx *htx, struct htx_sl *sl, const struct ist host);
 static int http_update_host(struct htx *htx, struct htx_sl *sl, const struct ist uri);
 
@@ -1064,8 +1085,10 @@ static int proxy_parse_errorloc(char **args, int section, struct proxy *curpx,
                                  struct proxy *defpx, const char *file, int line,
                                  char **errmsg)
 {
+       struct conf_errors *conf_err;
        struct buffer *msg;
-       int errloc, status, rc, ret = 0;
+       int errloc, status;
+       int ret = 0;
 
        if (warnifnotcap(curpx, PR_CAP_FE | PR_CAP_BE, file, line, args[0], NULL)) {
                ret = 1;
@@ -1087,21 +1110,33 @@ static int proxy_parse_errorloc(char **args, int section, struct proxy *curpx,
                goto out;
        }
 
-       rc = http_get_status_idx(status);
-       curpx->errmsg[rc] = msg;
+       conf_err = calloc(1, sizeof(*conf_err));
+       if (!conf_err) {
+               memprintf(errmsg, "%s : out of memory.", args[0]);
+               ret = -1;
+               goto out;
+       }
+       conf_err->type = 1;
+       conf_err->info.errorfile.status = status;
+       conf_err->info.errorfile.msg = msg;
+       conf_err->file = strdup(file);
+       conf_err->line = line;
+       LIST_ADDQ(&curpx->conf.errors, &conf_err->list);
 
   out:
        return ret;
-}
 
+}
 
 /* Parses the "errorfile" proxy keyword */
 static int proxy_parse_errorfile(char **args, int section, struct proxy *curpx,
                                 struct proxy *defpx, const char *file, int line,
                                 char **errmsg)
 {
+       struct conf_errors *conf_err;
        struct buffer *msg;
-       int status, rc, ret = 0;
+       int status;
+       int ret = 0;
 
        if (warnifnotcap(curpx, PR_CAP_FE | PR_CAP_BE, file, line, args[0], NULL)) {
                ret = 1;
@@ -1122,14 +1157,190 @@ static int proxy_parse_errorfile(char **args, int section, struct proxy *curpx,
                goto out;
        }
 
-       rc = http_get_status_idx(status);
-       curpx->errmsg[rc] = msg;
+       conf_err = calloc(1, sizeof(*conf_err));
+       if (!conf_err) {
+               memprintf(errmsg, "%s : out of memory.", args[0]);
+               ret = -1;
+               goto out;
+       }
+       conf_err->type = 1;
+       conf_err->info.errorfile.status = status;
+       conf_err->info.errorfile.msg = msg;
+       conf_err->file = strdup(file);
+       conf_err->line = line;
+       LIST_ADDQ(&curpx->conf.errors, &conf_err->list);
 
   out:
        return ret;
 
 }
 
+/* Parses the "errorfiles" proxy keyword */
+static int proxy_parse_errorfiles(char **args, int section, struct proxy *curpx,
+                                 struct proxy *defpx, const char *file, int line,
+                                 char **err)
+{
+       struct conf_errors *conf_err = NULL;
+       char *name = NULL;
+       int rc, ret = 0;
+
+       if (warnifnotcap(curpx, PR_CAP_FE | PR_CAP_BE, file, line, args[0], NULL)) {
+               ret = 1;
+               goto out;
+       }
+
+       if (!*(args[1])) {
+               memprintf(err, "%s : expects <name> as argument.", args[0]);
+               ret = -1;
+               goto out;
+       }
+
+       name = strdup(args[1]);
+       conf_err = calloc(1, sizeof(*conf_err));
+       if (!name || !conf_err) {
+               memprintf(err, "%s : out of memory.", args[0]);
+               ret = -1;
+               goto error;
+       }
+       conf_err->type = 0;
+
+       conf_err->info.errorfiles.name = name;
+       if (!*(args[2])) {
+               for (rc = 0; rc < HTTP_ERR_SIZE; rc++)
+                       conf_err->info.errorfiles.status[rc] = 1;
+       }
+       else {
+               int cur_arg, status;
+               for (cur_arg = 2; *(args[cur_arg]); cur_arg++) {
+                       status = atol(args[cur_arg]);
+
+                       for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
+                               if (http_err_codes[rc] == status) {
+                                       conf_err->info.errorfiles.status[rc] = 2;
+                                       break;
+                               }
+                       }
+                       if (rc >= HTTP_ERR_SIZE) {
+                               memprintf(err, "%s : status code '%d' not handled.", args[0], status);
+                               ret = -1;
+                               goto out;
+                       }
+               }
+       }
+       conf_err->file = strdup(file);
+       conf_err->line = line;
+       LIST_ADDQ(&curpx->conf.errors, &conf_err->list);
+  out:
+       return ret;
+
+  error:
+       free(name);
+       free(conf_err);
+       goto out;
+}
+
+/* Check "errorfiles" proxy keyword */
+static int proxy_check_errors(struct proxy *px)
+{
+       struct conf_errors *conf_err, *conf_err_back;
+       struct http_errors *http_errs;
+       int rc, err = 0;
+
+       list_for_each_entry_safe(conf_err, conf_err_back, &px->conf.errors, list) {
+               if (conf_err->type == 1) {
+                       /* errorfile */
+                       rc = http_get_status_idx(conf_err->info.errorfile.status);
+                       px->errmsg[rc] = conf_err->info.errorfile.msg;
+               }
+               else {
+                       /* errorfiles */
+                       list_for_each_entry(http_errs, &http_errors_list, list) {
+                               if (strcmp(http_errs->id, conf_err->info.errorfiles.name) == 0)
+                                       break;
+                       }
+
+                       /* unknown http-errors section */
+                       if (&http_errs->list == &http_errors_list) {
+                               ha_alert("config : proxy '%s': unknown http-errors section '%s' (at %s:%d).\n",
+                                        px->id, conf_err->info.errorfiles.name, conf_err->file, conf_err->line);
+                               err |= ERR_ALERT | ERR_FATAL;
+                               free(conf_err->info.errorfiles.name);
+                               goto next;
+                       }
+
+                       free(conf_err->info.errorfiles.name);
+                       for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
+                               if (conf_err->info.errorfiles.status[rc] > 0) {
+                                       if (http_errs->errmsg[rc])
+                                               px->errmsg[rc] = http_errs->errmsg[rc];
+                                       else if (conf_err->info.errorfiles.status[rc] == 2)
+                                               ha_warning("config: proxy '%s' : status '%d' not declared in"
+                                                          " http-errors section '%s' (at %s:%d).\n",
+                                                          px->id, http_err_codes[rc], http_errs->id,
+                                                          conf_err->file, conf_err->line);
+                               }
+                       }
+               }
+         next:
+               LIST_DEL(&conf_err->list);
+               free(conf_err->file);
+               free(conf_err);
+       }
+
+  out:
+       return err;
+}
+
+int proxy_dup_default_conf_errors(struct proxy *curpx, struct proxy *defpx, char **errmsg)
+{
+       struct conf_errors *conf_err, *new_conf_err = NULL;
+       int ret = 0;
+
+       list_for_each_entry(conf_err, &defpx->conf.errors, list) {
+               new_conf_err = calloc(1, sizeof(*new_conf_err));
+               if (!new_conf_err) {
+                       memprintf(errmsg, "unable to duplicate default errors (out of memory).");
+                       goto out;
+               }
+               new_conf_err->type = conf_err->type;
+               if (conf_err->type == 1) {
+                       new_conf_err->info.errorfile.status = conf_err->info.errorfile.status;
+                       new_conf_err->info.errorfile.msg    = conf_err->info.errorfile.msg;
+               }
+               else {
+                       new_conf_err->info.errorfiles.name = strdup(conf_err->info.errorfiles.name);
+                       if (!new_conf_err->info.errorfiles.name) {
+                               memprintf(errmsg, "unable to duplicate default errors (out of memory).");
+                               goto out;
+                       }
+                       memcpy(&new_conf_err->info.errorfiles.status, &conf_err->info.errorfiles.status,
+                              sizeof(conf_err->info.errorfiles.status));
+               }
+               new_conf_err->file = strdup(conf_err->file);
+               new_conf_err->line = conf_err->line;
+               LIST_ADDQ(&curpx->conf.errors, &new_conf_err->list);
+               new_conf_err = NULL;
+       }
+       ret = 1;
+
+  out:
+       free(new_conf_err);
+       return ret;
+}
+
+void proxy_release_conf_errors(struct proxy *px)
+{
+       struct conf_errors *conf_err, *conf_err_back;
+
+       list_for_each_entry_safe(conf_err, conf_err_back, &px->conf.errors, list) {
+               if (conf_err->type == 0)
+                       free(conf_err->info.errorfiles.name);
+               LIST_DEL(&conf_err->list);
+               free(conf_err->file);
+               free(conf_err);
+       }
+}
+
 /*
  * Parse an <http-errors> section.
  * Returns the error code, 0 if OK, or any combination of :
@@ -1218,10 +1429,12 @@ static struct cfg_kw_list cfg_kws = {ILH, {
         { CFG_LISTEN, "errorloc302",  proxy_parse_errorloc },
         { CFG_LISTEN, "errorloc303",  proxy_parse_errorloc },
         { CFG_LISTEN, "errorfile",    proxy_parse_errorfile },
+        { CFG_LISTEN, "errorfiles",   proxy_parse_errorfiles },
         { 0, NULL, NULL },
 }};
 
 INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
+REGISTER_POST_PROXY_CHECK(proxy_check_errors);
 
 REGISTER_CONFIG_SECTION("http-errors", cfg_parse_http_errors, NULL);
 
index aed32f94b4c380eea6ec5c95cb08c1dd47d7e280..dbce45dd4313c4db2c9ab6d1eb4afb82c7636403 100644 (file)
@@ -873,6 +873,7 @@ void init_new_proxy(struct proxy *p)
        LIST_INIT(&p->format_unique_id);
        LIST_INIT(&p->conf.bind);
        LIST_INIT(&p->conf.listeners);
+       LIST_INIT(&p->conf.errors);
        LIST_INIT(&p->conf.args.list);
        LIST_INIT(&p->tcpcheck_rules);
        LIST_INIT(&p->filter_configs);