[MEDIUM] add access restrictions to the stats socket
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.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 92cd942..8386e6f 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -476,13 +476,25 @@
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 @@
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 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 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:
diff --git a/include/types/global.h b/include/types/global.h
index 3a8faa9..2a7bc46 100644
--- a/include/types/global.h
+++ b/include/types/global.h
@@ -54,6 +54,11 @@
/* 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 {
diff --git a/include/types/protocols.h b/include/types/protocols.h
index 849ca55..a776cb3 100644
--- a/include/types/protocols.h
+++ b/include/types/protocols.h
@@ -101,6 +101,7 @@
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 */
diff --git a/src/dumpstats.c b/src/dumpstats.c
index bacd9cc..63696d8 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -69,6 +69,15 @@
.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 @@
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 @@
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 @@
}
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 @@
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));