Get user and group names from files (#6472)

* Read user names from file

* Separate file modification check

* Read group names from file

* Update the documentation

* Use files only inside a containter

* Fix the volume mounting suggestions
This commit is contained in:
Vladimir Kobal 2019-07-18 20:38:12 +03:00 committed by Paul Emm. Katsoulakis
parent 6469cf9272
commit bc21588a74
3 changed files with 239 additions and 10 deletions

View File

@ -100,6 +100,8 @@ To try Netdata in a docker container, run this:
```
docker run -d --name=netdata \
-p 19999:19999 \
-v /etc/passwd:/host/etc/passwd:ro \
-v /etc/group:/host/etc/group:ro \
-v /proc:/host/proc:ro \
-v /sys:/host/sys:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \

View File

@ -499,6 +499,187 @@ static int
all_files_len = 0,
all_files_size = 0;
// ----------------------------------------------------------------------------
// read users and groups from files
struct user_or_group_id {
avl avl;
union {
uid_t uid;
gid_t gid;
} id;
char *name;
int updated;
struct user_or_group_id * next;
};
enum user_or_group_id_type {
USER_ID,
GROUP_ID
};
struct user_or_group_ids{
enum user_or_group_id_type type;
avl_tree index;
struct user_or_group_id *root;
char filename[FILENAME_MAX + 1];
};
int user_id_compare(void* a, void* b) {
if(((struct user_or_group_id *)a)->id.uid < ((struct user_or_group_id *)b)->id.uid)
return -1;
else if(((struct user_or_group_id *)a)->id.uid > ((struct user_or_group_id *)b)->id.uid)
return 1;
else
return 0;
}
struct user_or_group_ids all_user_ids = {
.type = USER_ID,
.index = {
NULL,
user_id_compare
},
.root = NULL,
.filename = "",
};
int group_id_compare(void* a, void* b) {
if(((struct user_or_group_id *)a)->id.gid < ((struct user_or_group_id *)b)->id.gid)
return -1;
else if(((struct user_or_group_id *)a)->id.gid > ((struct user_or_group_id *)b)->id.gid)
return 1;
else
return 0;
}
struct user_or_group_ids all_group_ids = {
.type = GROUP_ID,
.index = {
NULL,
group_id_compare
},
.root = NULL,
.filename = "",
};
int file_changed(const struct stat *statbuf, struct timespec *last_modification_time) {
if(likely(statbuf->st_mtim.tv_sec == last_modification_time->tv_sec &&
statbuf->st_mtim.tv_nsec == last_modification_time->tv_nsec)) return 0;
last_modification_time->tv_sec = statbuf->st_mtim.tv_sec;
last_modification_time->tv_nsec = statbuf->st_mtim.tv_nsec;
return 1;
}
int read_user_or_group_ids(struct user_or_group_ids *ids, struct timespec *last_modification_time) {
struct stat statbuf;
if(unlikely(stat(ids->filename, &statbuf)))
return 1;
else
if(likely(!file_changed(&statbuf, last_modification_time))) return 0;
procfile *ff = procfile_open(ids->filename, " :\t", PROCFILE_FLAG_DEFAULT);
if(unlikely(!ff)) return 1;
ff = procfile_readall(ff);
if(unlikely(!ff)) return 1;
size_t line, lines = procfile_lines(ff);
for(line = 0; line < lines ;line++) {
size_t words = procfile_linewords(ff, line);
if(unlikely(words < 3)) continue;
char *name = procfile_lineword(ff, line, 0);
if(unlikely(!name || !*name)) continue;
char *id_string = procfile_lineword(ff, line, 2);
if(unlikely(!id_string || !*id_string)) continue;
struct user_or_group_id *user_or_group_id = callocz(1, sizeof(struct user_or_group_id));
if(ids->type == USER_ID)
user_or_group_id->id.uid = (uid_t)str2ull(id_string);
else
user_or_group_id->id.gid = (uid_t)str2ull(id_string);
user_or_group_id->name = strdupz(name);
user_or_group_id->updated = 1;
struct user_or_group_id *existing_user_id = NULL;
if(likely(ids->root))
existing_user_id = (struct user_or_group_id *)avl_search(&ids->index, (avl *) user_or_group_id);
if(unlikely(existing_user_id)) {
freez(existing_user_id->name);
existing_user_id->name = user_or_group_id->name;
existing_user_id->updated = 1;
freez(user_or_group_id);
}
else {
if(unlikely(avl_insert(&ids->index, (avl *) user_or_group_id) != (void *) user_or_group_id)) {
error("INTERNAL ERROR: duplicate indexing of id during realloc");
};
user_or_group_id->next = ids->root;
ids->root = user_or_group_id;
}
}
procfile_close(ff);
// remove unused ids
struct user_or_group_id *user_or_group_id = ids->root, *prev_user_id = NULL;
while(user_or_group_id) {
if(unlikely(!user_or_group_id->updated)) {
if(unlikely((struct user_or_group_id *)avl_remove(&ids->index, (avl *) user_or_group_id) != user_or_group_id))
error("INTERNAL ERROR: removal of unused id from index, removed a different id");
if(prev_user_id)
prev_user_id->next = user_or_group_id->next;
else
ids->root = user_or_group_id->next;
freez(user_or_group_id->name);
freez(user_or_group_id);
if(prev_user_id)
user_or_group_id = prev_user_id->next;
else
user_or_group_id = ids->root;
}
else {
user_or_group_id->updated = 0;
prev_user_id = user_or_group_id;
user_or_group_id = user_or_group_id->next;
}
}
return 0;
}
// ----------------------------------------------------------------------------
// apps_groups.conf
// aggregate all processes in groups, to have a limited number of dimensions
@ -516,11 +697,27 @@ static struct target *get_users_target(uid_t uid) {
snprintfz(w->id, MAX_NAME, "%u", uid);
w->idhash = simple_hash(w->id);
struct passwd *pw = getpwuid(uid);
if(!pw || !pw->pw_name || !*pw->pw_name)
snprintfz(w->name, MAX_NAME, "%u", uid);
else
snprintfz(w->name, MAX_NAME, "%s", pw->pw_name);
struct user_or_group_id user_id_to_find, *user_or_group_id = NULL;
user_id_to_find.id.uid = uid;
if(*netdata_configured_host_prefix) {
static struct timespec last_passwd_modification_time;
int ret = read_user_or_group_ids(&all_user_ids, &last_passwd_modification_time);
if(likely(!ret && all_user_ids.index.root))
user_or_group_id = (struct user_or_group_id *)avl_search(&all_user_ids.index, (avl *) &user_id_to_find);
}
if(user_or_group_id && user_or_group_id->name && *user_or_group_id->name) {
snprintfz(w->name, MAX_NAME, "%s", user_or_group_id->name);
}
else {
struct passwd *pw = getpwuid(uid);
if(!pw || !pw->pw_name || !*pw->pw_name)
snprintfz(w->name, MAX_NAME, "%u", uid);
else
snprintfz(w->name, MAX_NAME, "%s", pw->pw_name);
}
netdata_fix_chart_name(w->name);
@ -548,11 +745,27 @@ struct target *get_groups_target(gid_t gid)
snprintfz(w->id, MAX_NAME, "%u", gid);
w->idhash = simple_hash(w->id);
struct group *gr = getgrgid(gid);
if(!gr || !gr->gr_name || !*gr->gr_name)
snprintfz(w->name, MAX_NAME, "%u", gid);
else
snprintfz(w->name, MAX_NAME, "%s", gr->gr_name);
struct user_or_group_id group_id_to_find, *group_id = NULL;
group_id_to_find.id.gid = gid;
if(*netdata_configured_host_prefix) {
static struct timespec last_group_modification_time;
int ret = read_user_or_group_ids(&all_group_ids, &last_group_modification_time);
if(likely(!ret && all_group_ids.index.root))
group_id = (struct user_or_group_id *)avl_search(&all_group_ids.index, (avl *) &group_id_to_find);
}
if(group_id && group_id->name && *group_id->name) {
snprintfz(w->name, MAX_NAME, "%s", group_id->name);
}
else {
struct group *gr = getgrgid(gid);
if(!gr || !gr->gr_name || !*gr->gr_name)
snprintfz(w->name, MAX_NAME, "%u", gid);
else
snprintfz(w->name, MAX_NAME, "%s", gr->gr_name);
}
netdata_fix_chart_name(w->name);
@ -3826,6 +4039,12 @@ int main(int argc, char **argv) {
info("started on pid %d", getpid());
snprintfz(all_user_ids.filename, FILENAME_MAX, "%s/etc/passwd", netdata_configured_host_prefix);
debug_log("passwd file: '%s'", all_user_ids.filename);
snprintfz(all_group_ids.filename, FILENAME_MAX, "%s/etc/group", netdata_configured_host_prefix);
debug_log("group file: '%s'", all_group_ids.filename);
#if (ALL_PIDS_ARE_READ_INSTANTLY == 0)
all_pids_sortlist = callocz(sizeof(pid_t), (size_t)pid_max);
#endif

View File

@ -24,6 +24,8 @@ This is good for an internal network or to quickly analyse a host.
```bash
docker run -d --name=netdata \
-p 19999:19999 \
-v /etc/passwd:/host/etc/passwd:ro \
-v /etc/group:/host/etc/group:ro \
-v /proc:/host/proc:ro \
-v /sys:/host/sys:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
@ -47,11 +49,15 @@ services:
security_opt:
- apparmor:unconfined
volumes:
- /etc/passwd:/host/etc/passwd:ro
- /etc/group:/host/etc/group:ro
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
```
If you don't want to use the apps.plugin functionality, you can remove the mounts of `/etc/passwd` and `/etc/group` (they are used to get proper user and group names for the monitored host) to get slightly better security.
### Docker container names resolution
If you want to have your container names resolved by netdata, you need to do two things:
@ -132,6 +138,8 @@ services:
security_opt:
- apparmor:unconfined
volumes:
- /etc/passwd:/host/etc/passwd:ro
- /etc/group:/host/etc/group:ro
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /var/run/docker.sock:/var/run/docker.sock:ro