From: Willy Tarreau Date: Sat, 10 Oct 2009 15:13:00 +0000 (+0200) Subject: [MEDIUM] add access restrictions to the stats socket X-Git-Tag: v1.4-dev4~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6162db2a81b71a5bb6217e85629a50898579922f;p=thirdparty%2Fhaproxy.git [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 92cd94293d..8386e6f295 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -476,13 +476,25 @@ pidfile starting the process. See also "daemon". stats socket [{uid | user} ] [{gid | group} ] [mode ] + [level ] + Creates a UNIX socket in stream mode at location . 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 [] Dump last known request and response errors collected by frontends and backends. If is specified, the limit the dump to errors concerning - either frontend or backend whose ID is . + either frontend or backend whose ID is . 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 [ ] Dump statistics in the CSV format. By passing , and , 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: diff --git a/include/types/global.h b/include/types/global.h index 3a8faa953d..2a7bc4609b 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 849ca55fb4..a776cb339a 100644 --- a/include/types/protocols.h +++ b/include/types/protocols.h @@ -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 */ diff --git a/src/dumpstats.c b/src/dumpstats.c index bacd9cc9fe..63696d82a1 100644 --- a/src/dumpstats.c +++ b/src/dumpstats.c @@ -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 buffer, for at most 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));