From 474be415af796956a887e3fb7e93656bdacc2091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cyril=20Bont=C3=A9?= Date: Tue, 12 Oct 2010 00:14:36 +0200 Subject: [PATCH] [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) --- include/common/uri_auth.h | 7 +++++++ include/proto/dumpstats.h | 2 ++ src/cfgparse.c | 36 +++++++++++++++++++++++++++++++++++- src/dumpstats.c | 30 ++++++++++++++++++++---------- src/proto_http.c | 30 ++++++++++++++++++++++++++++-- src/uri_auth.c | 1 + 6 files changed, 93 insertions(+), 13 deletions(-) diff --git a/include/common/uri_auth.h b/include/common/uri_auth.h index bffd694d6..906cb2c94 100644 --- a/include/common/uri_auth.h +++ b/include/common/uri_auth.h @@ -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. diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h index 7038f469e..9cf5eec40 100644 --- a/include/proto/dumpstats.h +++ b/include/proto/dumpstats.h @@ -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); diff --git a/src/cfgparse.c b/src/cfgparse.c index ef2d9ca10..d1ffa371a 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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; diff --git a/src/dumpstats.c b/src/dumpstats.c index 5195d8c1e..f0d6c83f5 100644 --- a/src/dumpstats.c +++ b/src/dumpstats.c @@ -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." "\n", uri->uri_prefix); } + else if (strcmp(s->data_ctx.stats.st_code, STAT_STATUS_DENY) == 0) { + chunk_printf(&msg, + "

" + "[X] " + "Action denied." + "
\n", uri->uri_prefix); + } else { chunk_printf(&msg, "

" @@ -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, "
", @@ -1659,7 +1666,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri) (uri->flags & ST_SHLGNDS)?"":"", 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, ""); } @@ -1699,7 +1706,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri) /* name, queue */ ""); - 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, ""); } @@ -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, ""); - 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, ""); } @@ -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, - "" - "id); + if (px->cap & PR_CAP_BE && px->srv && (s->data_ctx.stats.flags & STAT_ADMIN)) { + chunk_printf(&msg, + "", + sv->id); + } + + chunk_printf(&msg, "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, ""); - 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, ""); } @@ -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, ""); - 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 : " diff --git a/src/proto_http.c b/src/proto_http.c index 3d408e13b..0cbfef266 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -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; diff --git a/src/uri_auth.c b/src/uri_auth.c index 6b2ca2aa3..fdbcef001 100644 --- a/src/uri_auth.c +++ b/src/uri_auth.c @@ -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;