mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2024-12-27 23:22:09 +00:00
[MEDIUM] stats: add an admin level
The stats web interface must be read-only by default to prevent security holes. As it is now allowed to enable/disable servers, a new keyword "stats admin" is introduced to activate this admin level, conditioned by ACLs. (cherry picked from commit 5334bab92ca7debe36df69983c19c21b6dc63f78)
This commit is contained in:
parent
70be45dbdf
commit
474be415af
@ -43,6 +43,7 @@ struct uri_auth {
|
||||
struct stat_scope *scope; /* linked list of authorized proxies */
|
||||
struct userlist *userlist; /* private userlist to emulate legacy "stats auth user:password" */
|
||||
struct list req_acl; /* http stats ACL: allow/deny/auth */
|
||||
struct list admin_rules; /* 'stats admin' rules (chained) */
|
||||
struct uri_auth *next; /* Used at deinit() to build a list of unique elements */
|
||||
};
|
||||
|
||||
@ -61,6 +62,12 @@ struct uri_auth {
|
||||
#endif
|
||||
|
||||
|
||||
struct stats_admin_rule {
|
||||
struct list list; /* list linked to from the proxy */
|
||||
struct acl_cond *cond; /* acl condition to meet */
|
||||
};
|
||||
|
||||
|
||||
/* Various functions used to set the fields during the configuration parsing.
|
||||
* Please that all those function can initialize the root entry in order not to
|
||||
* force the user to respect a certain order in the configuration file.
|
||||
|
@ -33,6 +33,7 @@
|
||||
#define STAT_SHOW_INFO 0x00000004 /* dump the info part */
|
||||
#define STAT_HIDE_DOWN 0x00000008 /* hide 'down' servers in the stats page */
|
||||
#define STAT_NO_REFRESH 0x00000010 /* do not automatically refresh the stats page */
|
||||
#define STAT_ADMIN 0x00000020 /* indicate a stats admin level */
|
||||
#define STAT_BOUND 0x00800000 /* bound statistics to selected proxies/types/services */
|
||||
|
||||
#define STATS_TYPE_FE 0
|
||||
@ -58,6 +59,7 @@
|
||||
#define STAT_STATUS_DONE "DONE" /* the action is successful */
|
||||
#define STAT_STATUS_NONE "NONE" /* nothing happened (no action chosen or servers state didn't change) */
|
||||
#define STAT_STATUS_EXCD "EXCD" /* an error occured becayse the buffer couldn't store all data */
|
||||
#define STAT_STATUS_DENY "DENY" /* action denied */
|
||||
|
||||
|
||||
int stats_accept(struct session *s);
|
||||
|
@ -2533,6 +2533,40 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
|
||||
|
||||
if (!*args[1]) {
|
||||
goto stats_error_parsing;
|
||||
} else if (!strcmp(args[1], "admin")) {
|
||||
struct stats_admin_rule *rule;
|
||||
|
||||
if (curproxy == &defproxy) {
|
||||
Alert("parsing [%s:%d]: '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]);
|
||||
err_code |= ERR_ALERT | ERR_FATAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!stats_check_init_uri_auth(&curproxy->uri_auth)) {
|
||||
Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
|
||||
err_code |= ERR_ALERT | ERR_ABORT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (strcmp(args[2], "if") != 0 && strcmp(args[2], "unless") != 0) {
|
||||
Alert("parsing [%s:%d] : '%s %s' requires either 'if' or 'unless' followed by a condition.\n",
|
||||
file, linenum, args[0], args[1]);
|
||||
err_code |= ERR_ALERT | ERR_FATAL;
|
||||
goto out;
|
||||
}
|
||||
if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 2)) == NULL) {
|
||||
Alert("parsing [%s:%d] : error detected while parsing a '%s %s' rule.\n",
|
||||
file, linenum, args[0], args[1]);
|
||||
err_code |= ERR_ALERT | ERR_FATAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
err_code |= warnif_cond_requires_resp(cond, file, linenum);
|
||||
|
||||
rule = (struct stats_admin_rule *)calloc(1, sizeof(*rule));
|
||||
rule->cond = cond;
|
||||
LIST_INIT(&rule->list);
|
||||
LIST_ADDQ(&curproxy->uri_auth->admin_rules, &rule->list);
|
||||
} else if (!strcmp(args[1], "uri")) {
|
||||
if (*(args[2]) == 0) {
|
||||
Alert("parsing [%s:%d] : 'uri' needs an URI prefix.\n", file, linenum);
|
||||
@ -2695,7 +2729,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
|
||||
}
|
||||
} else {
|
||||
stats_error_parsing:
|
||||
Alert("parsing [%s:%d]: %s '%s', expects 'uri', 'realm', 'auth', 'scope', 'enable', 'hide-version', 'show-node', 'show-desc' or 'show-legends'.\n",
|
||||
Alert("parsing [%s:%d]: %s '%s', expects 'admin', 'uri', 'realm', 'auth', 'scope', 'enable', 'hide-version', 'show-node', 'show-desc' or 'show-legends'.\n",
|
||||
file, linenum, *args[1]?"unknown stats parameter":"missing keyword in", args[*args[1]?1:0]);
|
||||
err_code |= ERR_ALERT | ERR_FATAL;
|
||||
goto out;
|
||||
|
@ -1514,6 +1514,13 @@ int stats_dump_http(struct session *s, struct buffer *rep, struct uri_auth *uri)
|
||||
"You should retry with less servers at a time.</b>"
|
||||
"</div>\n", uri->uri_prefix);
|
||||
}
|
||||
else if (strcmp(s->data_ctx.stats.st_code, STAT_STATUS_DENY) == 0) {
|
||||
chunk_printf(&msg,
|
||||
"<p><div class=active0>"
|
||||
"<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
|
||||
"<b>Action denied.</b>"
|
||||
"</div>\n", uri->uri_prefix);
|
||||
}
|
||||
else {
|
||||
chunk_printf(&msg,
|
||||
"<p><div class=active6>"
|
||||
@ -1624,7 +1631,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
|
||||
|
||||
case DATA_ST_PX_TH:
|
||||
if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
|
||||
if (px->cap & PR_CAP_BE && px->srv) {
|
||||
if (px->cap & PR_CAP_BE && px->srv && (s->data_ctx.stats.flags & STAT_ADMIN)) {
|
||||
/* A form to enable/disable this proxy servers */
|
||||
chunk_printf(&msg,
|
||||
"<form action=\"%s\" method=\"post\">",
|
||||
@ -1659,7 +1666,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
|
||||
(uri->flags & ST_SHLGNDS)?"</u>":"",
|
||||
px->desc ? "desc" : "empty", px->desc ? px->desc : "");
|
||||
|
||||
if (px->cap & PR_CAP_BE && px->srv) {
|
||||
if (px->cap & PR_CAP_BE && px->srv && (s->data_ctx.stats.flags & STAT_ADMIN)) {
|
||||
/* Column heading for Enable or Disable server */
|
||||
chunk_printf(&msg, "<th rowspan=2 width=1></th>");
|
||||
}
|
||||
@ -1699,7 +1706,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
|
||||
/* name, queue */
|
||||
"<tr class=\"frontend\">");
|
||||
|
||||
if (px->cap & PR_CAP_BE && px->srv) {
|
||||
if (px->cap & PR_CAP_BE && px->srv && (s->data_ctx.stats.flags & STAT_ADMIN)) {
|
||||
/* Column sub-heading for Enable or Disable server */
|
||||
chunk_printf(&msg, "<td></td>");
|
||||
}
|
||||
@ -1867,7 +1874,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
|
||||
|
||||
if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
|
||||
chunk_printf(&msg, "<tr class=socket>");
|
||||
if (px->cap & PR_CAP_BE && px->srv) {
|
||||
if (px->cap & PR_CAP_BE && px->srv && (s->data_ctx.stats.flags & STAT_ADMIN)) {
|
||||
/* Column sub-heading for Enable or Disable server */
|
||||
chunk_printf(&msg, "<td></td>");
|
||||
}
|
||||
@ -2055,10 +2062,13 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
|
||||
(sv->state & SRV_BACKUP) ? "backup" : "active", sv_state);
|
||||
}
|
||||
|
||||
chunk_printf(&msg,
|
||||
"<td><input type=\"checkbox\" name=\"s\" value=\"%s\"></td>"
|
||||
"<td class=ac",
|
||||
sv->id);
|
||||
if (px->cap & PR_CAP_BE && px->srv && (s->data_ctx.stats.flags & STAT_ADMIN)) {
|
||||
chunk_printf(&msg,
|
||||
"<td><input type=\"checkbox\" name=\"s\" value=\"%s\"></td>",
|
||||
sv->id);
|
||||
}
|
||||
|
||||
chunk_printf(&msg, "<td class=ac");
|
||||
|
||||
if (uri->flags&ST_SHLGNDS) {
|
||||
char str[INET6_ADDRSTRLEN];
|
||||
@ -2391,7 +2401,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
|
||||
(!(s->data_ctx.stats.flags & STAT_BOUND) || (s->data_ctx.stats.type & (1 << STATS_TYPE_BE)))) {
|
||||
if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
|
||||
chunk_printf(&msg, "<tr class=\"backend\">");
|
||||
if (px->cap & PR_CAP_BE && px->srv) {
|
||||
if (px->cap & PR_CAP_BE && px->srv && (s->data_ctx.stats.flags & STAT_ADMIN)) {
|
||||
/* Column sub-heading for Enable or Disable server */
|
||||
chunk_printf(&msg, "<td></td>");
|
||||
}
|
||||
@ -2584,7 +2594,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
|
||||
if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
|
||||
chunk_printf(&msg, "</table>");
|
||||
|
||||
if (px->cap & PR_CAP_BE && px->srv) {
|
||||
if (px->cap & PR_CAP_BE && px->srv && (s->data_ctx.stats.flags & STAT_ADMIN)) {
|
||||
/* close the form used to enable/disable this proxy servers */
|
||||
chunk_printf(&msg,
|
||||
"Choose the action to perform on the checked servers : "
|
||||
|
@ -3152,14 +3152,38 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
|
||||
}
|
||||
|
||||
if (do_stats) {
|
||||
/* We need to provied stats for this request.
|
||||
struct stats_admin_rule *stats_admin_rule;
|
||||
|
||||
/* We need to provide stats for this request.
|
||||
* FIXME!!! that one is rather dangerous, we want to
|
||||
* make it follow standard rules (eg: clear req->analysers).
|
||||
*/
|
||||
|
||||
/* now check whether we have some admin rules for this request */
|
||||
list_for_each_entry(stats_admin_rule, &s->be->uri_auth->admin_rules, list) {
|
||||
int ret = 1;
|
||||
|
||||
if (stats_admin_rule->cond) {
|
||||
ret = acl_exec_cond(stats_admin_rule->cond, s->be, s, &s->txn, ACL_DIR_REQ);
|
||||
ret = acl_pass(ret);
|
||||
if (stats_admin_rule->cond->pol == ACL_COND_UNLESS)
|
||||
ret = !ret;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
/* no rule, or the rule matches */
|
||||
s->data_ctx.stats.flags |= STAT_ADMIN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Was the status page requested with a POST ? */
|
||||
if (txn->meth == HTTP_METH_POST) {
|
||||
http_process_req_stat_post(s, req);
|
||||
if (s->data_ctx.stats.flags & STAT_ADMIN) {
|
||||
http_process_req_stat_post(s, req);
|
||||
} else {
|
||||
s->data_ctx.stats.st_code = STAT_STATUS_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
s->logs.tv_request = now;
|
||||
@ -7124,6 +7148,8 @@ int stats_check_uri(struct session *t, struct proxy *backend)
|
||||
t->data_ctx.stats.st_code = STAT_STATUS_NONE;
|
||||
else if (memcmp(h, STAT_STATUS_EXCD, 4) == 0)
|
||||
t->data_ctx.stats.st_code = STAT_STATUS_EXCD;
|
||||
else if (memcmp(h, STAT_STATUS_DENY, 4) == 0)
|
||||
t->data_ctx.stats.st_code = STAT_STATUS_DENY;
|
||||
else
|
||||
t->data_ctx.stats.st_code = STAT_STATUS_UNKN;
|
||||
break;
|
||||
|
@ -32,6 +32,7 @@ struct uri_auth *stats_check_init_uri_auth(struct uri_auth **root)
|
||||
goto out_u;
|
||||
|
||||
LIST_INIT(&u->req_acl);
|
||||
LIST_INIT(&u->admin_rules);
|
||||
} else
|
||||
u = *root;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user