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
This commit is contained in:
Christopher Faulet 2020-01-13 15:52:01 +01:00
parent 35cd81d363
commit 76edc0f29c
6 changed files with 299 additions and 9 deletions

View 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

View 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 */

View 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 */

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

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

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