]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] add access restrictions to the stats socket
authorWilly Tarreau <w@1wt.eu>
Sat, 10 Oct 2009 15:13:00 +0000 (17:13 +0200)
committerWilly Tarreau <w@1wt.eu>
Sat, 10 Oct 2009 15:13:00 +0000 (17:13 +0200)
The stats socket can now run at 3 different levels :
  - user
  - operator (default one)
  - admin

These levels are used to restrict access to some information
and commands. Only the admin can clear all stats. A user cannot
clear anything nor access sensible data such as sessions or
errors.

doc/configuration.txt
include/types/global.h
include/types/protocols.h
src/dumpstats.c

index 92cd94293d69406b2f77ade8ced14d1333d5e824..8386e6f295cf222ef8cae054ddbd78e992653906 100644 (file)
@@ -476,13 +476,25 @@ pidfile <pidfile>
   starting the process. See also "daemon".
 
 stats socket <path> [{uid | user} <uid>] [{gid | group} <gid>] [mode <mode>]
+  [level <level>]
+
   Creates a UNIX socket in stream mode at location <path>. Any previously
   existing socket will be backed up then replaced. Connections to this socket
-  will get a CSV-formated output of the process statistics in response to the
-  "show stat" command followed by a line feed, more general process information
-  in response to the "show info" command followed by a line feed, and a
-  complete list of all existing sessions in response to the "show sess" command
-  followed by a line feed.
+  will return various statictics outputs and even allow some commands to be
+  issued. Please consult section 9.2 "Unix Socket commands" for more details.
+
+  An optional "level" parameter can be specified to restrict the nature of
+  the commands that can be issued on the socket :
+    - "user" is the least privileged level ; only non-sensitive stats can be
+      read, and no change is allowed. It would make sense on systems where it
+      is not easy to restrict access to the socket.
+
+    - "operator" is the default level and fits most common uses. All data can
+      be read, and only non-sensible changes are permitted (eg: clear max
+      counters).
+
+    - "admin" should be used with care, as everything is permitted (eg: clear
+      all counters).
 
   On platforms which support it, it is possible to restrict access to this
   socket by specifying numerical IDs after "uid" and "gid", or valid user and
@@ -6753,7 +6765,9 @@ quit
 show errors [<iid>]
   Dump last known request and response errors collected by frontends and
   backends. If <iid> is specified, the limit the dump to errors concerning
-  either frontend or backend whose ID is <iid>.
+  either frontend or backend whose ID is <iid>. This command is restricted
+  and can only be issued on sockets configured for levels "operator" or
+  "admin".
 
   The errors which may be collected are the last request and response errors
   caused by protocol violations, often due to invalid characters in header
@@ -6806,7 +6820,9 @@ show info
 
 show sess
   Dump all known sessions. Avoid doing this on slow connections as this can
-  be huge.
+  be huge. This command is restricted and can only be issued on sockets
+  configured for levels "operator" or "admin".
+
 
 show stat [<iid> <type> <sid>]
   Dump statistics in the CSV format. By passing <id>, <type> and <sid>, it is
@@ -6846,11 +6862,14 @@ clear counters
   Clear the max values of the statistics counters in each proxy (frontend &
   backend) and in each server. The cumulated counters are not affected. This
   can be used to get clean counters after an incident, without having to
-  restart nor to clear traffic counters.
+  restart nor to clear traffic counters. This command is restricted and can
+  only be issued on sockets configured for levels "operator" or "admin".
 
 clear counters all
   Clear all statistics counters in each proxy (frontend & backend) and in each
-  server. This has the same effect as restarting.
+  server. This has the same effect as restarting. This command is restricted
+  and can only be issued on sockets configured for level "admin".
+
 
 /*
  * Local variables:
index 3a8faa953d3d5883a82c4c1ac682386b93e296c6..2a7bc4609b9d15267d4d5cb24c3656f16da56d65 100644 (file)
 /* platform-specific options */
 #define GTUNE_USE_SPLICE         (1<<5)
 
+/* Access level for a stats socket */
+#define ACCESS_LVL_NONE     0
+#define ACCESS_LVL_USER     1
+#define ACCESS_LVL_OPER     2
+#define ACCESS_LVL_ADMIN    3
 
 /* FIXME : this will have to be redefined correctly */
 struct global {
index 849ca55fb443f68a4d8f78074233b2053fca12d2..a776cb339ae57c70b429cd69a6fa26c26c7085f4 100644 (file)
@@ -101,6 +101,7 @@ struct listener {
                        uid_t uid;      /* -1 to leave unchanged */
                        gid_t gid;      /* -1 to leave unchanged */
                        mode_t mode;    /* 0 to leave unchanged */
+                       int level;      /* access level (ACCESS_LVL_*) */
                } ux;
        } perm;
        char *interface;                /* interface name or NULL */
index bacd9cc9fe52cc60b90230ffcadf2eba86c2ad8c..63696d82a18573460b25b5d44fb6b38d5c213e7e 100644 (file)
@@ -69,6 +69,15 @@ const struct chunk stats_sock_usage = {
         .len = sizeof(stats_sock_usage_msg)-1
 };
 
+const char stats_permission_denied_msg[] =
+       "Permission denied\n"
+       "";
+
+const struct chunk stats_permission_denied = {
+        .str = (char *)&stats_permission_denied_msg,
+        .len = sizeof(stats_permission_denied_msg)-1
+};
+
 /* This function parses a "stats" statement in the "global" section. It returns
  * -1 if there is any error, otherwise zero. If it returns -1, it may write an
  * error message into ther <err> buffer, for at most <errlen> bytes, trailing
@@ -129,6 +138,7 @@ static int stats_parse_global(char **args, int section_type, struct proxy *curpx
                global.stats_sock.analysers = 0;
                global.stats_sock.nice = -64;  /* we want to boost priority for local stats */
                global.stats_sock.private = global.stats_fe; /* must point to the frontend */
+               global.stats_sock.perm.ux.level = ACCESS_LVL_OPER; /* default access level */
 
                global.stats_fe->timeout.client = MS_TO_TICKS(10000); /* default timeout of 10 seconds */
                global.stats_sock.timeout = &global.stats_fe->timeout.client;
@@ -172,8 +182,21 @@ static int stats_parse_global(char **args, int section_type, struct proxy *curpx
                                global.stats_sock.perm.ux.gid = group->gr_gid;
                                cur_arg += 2;
                        }
+                       else if (!strcmp(args[cur_arg], "level")) {
+                               if (!strcmp(args[cur_arg+1], "user"))
+                                       global.stats_sock.perm.ux.level = ACCESS_LVL_USER;
+                               else if (!strcmp(args[cur_arg+1], "operator"))
+                                       global.stats_sock.perm.ux.level = ACCESS_LVL_OPER;
+                               else if (!strcmp(args[cur_arg+1], "admin"))
+                                       global.stats_sock.perm.ux.level = ACCESS_LVL_ADMIN;
+                               else {
+                                       snprintf(err, errlen, "'stats socket level' only supports 'user', 'operator', and 'admin'");
+                                       return -1;
+                               }
+                               cur_arg += 2;
+                       }
                        else {
-                               snprintf(err, errlen, "'stats socket' only supports 'user', 'uid', 'group', 'gid', and 'mode'");
+                               snprintf(err, errlen, "'stats socket' only supports 'user', 'uid', 'group', 'gid', 'level', and 'mode'");
                                return -1;
                        }
                }
@@ -289,9 +312,17 @@ int stats_sock_parse_request(struct stream_interface *si, char *line)
                }
                else if (strcmp(args[1], "sess") == 0) {
                        s->data_state = DATA_ST_INIT;
+                       if (s->listener->perm.ux.level < ACCESS_LVL_OPER) {
+                               buffer_feed(si->ib, stats_permission_denied.str, stats_permission_denied.len);
+                               return 1;
+                       }
                        si->st0 = STAT_CLI_O_SESS; // stats_dump_sess_to_buffer
                }
                else if (strcmp(args[1], "errors") == 0) {
+                       if (s->listener->perm.ux.level < ACCESS_LVL_OPER) {
+                               buffer_feed(si->ib, stats_permission_denied.str, stats_permission_denied.len);
+                               return 1;
+                       }
                        if (*args[2])
                                s->data_ctx.errors.iid  = atoi(args[2]);
                        else
@@ -314,6 +345,13 @@ int stats_sock_parse_request(struct stream_interface *si, char *line)
                        if (strcmp(args[2], "all") == 0)
                                clrall = 1;
 
+                       /* check permissions */
+                       if (s->listener->perm.ux.level < ACCESS_LVL_OPER ||
+                           (clrall && s->listener->perm.ux.level < ACCESS_LVL_ADMIN)) {
+                               buffer_feed(si->ib, stats_permission_denied.str, stats_permission_denied.len);
+                               return 1;
+                       }
+
                        for (px = proxy; px; px = px->next) {
                                if (clrall)
                                        memset(&px->counters, 0, sizeof(px->counters));