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.
---
 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 bffd694..906cb2c 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 7de1c7c..4728121 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
@@ -57,6 +58,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_sock_parse_request(struct stream_interface *si, char *line);
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 211a8ff..966994d 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2415,6 +2415,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);
@@ -2577,7 +2611,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 778523d..0a4acc1 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -1351,6 +1351,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>"
@@ -1461,7 +1468,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\">",
@@ -1495,7 +1502,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, 
struct uri_auth *uri)
                                     px->id, px->id, px->id,
                                     (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>");
                        }
@@ -1533,7 +1540,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, 
struct uri_auth *uri)
                                chunk_printf(&msg,
                                     /* 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>");
                                }
@@ -1700,7 +1707,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>");
                                }
@@ -1888,10 +1895,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];
@@ -2224,7 +2234,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>");
                                }
@@ -2417,7 +2427,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 : "
diff --git a/src/proto_http.c b/src/proto_http.c
index 91d3ad1..75fc065 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -3125,14 +3125,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;
@@ -6955,6 +6979,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 6b2ca2a..fdbcef0 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;
 
-- 
1.7.1


Reply via email to