From 76edc0f29c944f3dc721fb7655242a1bf98f835d Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Mon, 13 Jan 2020 15:52:01 +0100 Subject: [PATCH] MEDIUM: proxy: Add a directive to reference an http-errors section in a proxy 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 | 69 ++++++++++++ include/proto/http_htx.h | 2 + include/types/proxy.h | 1 + src/cfgparse-listen.c | 8 +- src/http_htx.c | 227 +++++++++++++++++++++++++++++++++++++-- src/proxy.c | 1 + 6 files changed, 299 insertions(+), 9 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 1114f63e4..1d69380c8 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 + Create a new http-errors group with the name . It is an independent + section that may be referenced by one or more proxies using its name. + +errorfile + Associate a file contents to an HTTP error code + + Arguments : + 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. + + 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 errorfile 503 /etc/haproxy/errorfiles/503sorry.http +errorfiles [ ...] + Import, fully or partially, the error files defined in the http-errors + section. + May be used in sections : defaults | frontend | listen | backend + yes | yes | yes | yes + Arguments : + is the name of an existing http-errors section. + + 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 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 errorloc302 Return an HTTP redirection to a URL instead of errors generated by HAProxy diff --git a/include/proto/http_htx.h b/include/proto/http_htx.h index 3f8634010..0b25d41ff 100644 --- a/include/proto/http_htx.h +++ b/include/proto/http_htx.h @@ -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 */ diff --git a/include/types/proxy.h b/include/types/proxy.h index 232634dec..c018c26f8 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -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 */ diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index 61bf7cd82..86d1f3366 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -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(); diff --git a/src/http_htx.c b/src/http_htx.c index e848e37e8..016fd8f2b 100644 --- a/src/http_htx.c +++ b/src/http_htx.c @@ -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 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 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); diff --git a/src/proxy.c b/src/proxy.c index aed32f94b..dbce45dd4 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -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);