[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.
This commit is contained in:
Willy Tarreau 2009-10-10 17:13:00 +02:00
parent 2f6bf2b82c
commit 6162db2a81
4 changed files with 73 additions and 10 deletions

View 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:

View File

@ -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 {

View 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 */

View 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' only supports 'user', 'uid', 'group', 'gid', and 'mode'");
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', '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));