mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 12:33:02 +02:00
confd: initial audit trail support
This commit introduces a new SECURITY() log macro that logs to facility 'audit' (sometimes called 'security') with severity set to 'alert'. It is then used to instrument the single most important things to log; all changes to users and their privileges. We also add logging to sysrepo, which knows the username for changes to running-config, copies to startup-config, and RPCs. Fixes #521 Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -8,7 +8,8 @@ run name:error :1 log:console norestart if:<run/bootstrap/failure> \
|
||||
[S] /usr/libexec/confd/error --
|
||||
|
||||
service name:confd log:prio:daemon.err <run/bootstrap/success> \
|
||||
[S12345] sysrepo-plugind -f -p /run/confd.pid -n -- Configuration daemon
|
||||
[S12345] sysrepo-plugind -f -p /run/confd.pid -n -v warning \
|
||||
-- Configuration daemon
|
||||
|
||||
# Bootstrap system with startup-config
|
||||
run name:startup log:prio:user.notice norestart <pid/confd> \
|
||||
@@ -24,7 +25,7 @@ run name:error :2 log:console norestart if:<run/failure/failure> \
|
||||
[S] /usr/libexec/confd/error --
|
||||
|
||||
service name:netopeer notify:none log <pid/confd> \
|
||||
[12345] netopeer2-server -F -t 60 \
|
||||
[12345] netopeer2-server -F -t 60 -v 1 \
|
||||
-- NETCONF server
|
||||
|
||||
# Create initial /etc/resolv.conf after successful bootstrap
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
From da765b90bca45b91f72fd6525e680040eebd2d4b Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Wed, 21 Aug 2024 16:00:35 +0200
|
||||
Subject: [PATCH 7/8] Introduce new log level [SEC]
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
This adds a new log level for security and audit trail related log
|
||||
messages. E.g., nacm user applied a change, copied a ds, etc.
|
||||
|
||||
The new log level is added last to not affect the advertised command
|
||||
line log levels. A security notice has higher actual priorty than
|
||||
DBG, of course, so we remap it to WRN. The construct allows us to
|
||||
have another [label] than [WRN], which might otherwise be read as
|
||||
a bug/problem rather than just a high-priority-notification.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/log.c | 5 +++++
|
||||
src/log.h | 1 +
|
||||
src/sysrepo_types.h | 3 ++-
|
||||
tests/tcommon.c | 3 +++
|
||||
4 files changed, 11 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/log.c b/src/log.c
|
||||
index e15055ac..b89ffacf 100644
|
||||
--- a/src/log.c
|
||||
+++ b/src/log.c
|
||||
@@ -122,6 +122,11 @@ sr_log_msg(int plugin, sr_log_level_t ll, const char *msg)
|
||||
priority = LOG_INFO;
|
||||
severity = "INF";
|
||||
break;
|
||||
+ case SR_LL_SEC:
|
||||
+ priority = LOG_WARNING;
|
||||
+ severity = "SEC";
|
||||
+ ll = SR_LL_WRN; /* remap to higher level. */
|
||||
+ break;
|
||||
case SR_LL_DBG:
|
||||
priority = LOG_DEBUG;
|
||||
severity = "DBG";
|
||||
diff --git a/src/log.h b/src/log.h
|
||||
index d7e65b88..8722e51d 100644
|
||||
--- a/src/log.h
|
||||
+++ b/src/log.h
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#define SR_LOG_WRN(...) sr_log(SR_LL_WRN, __VA_ARGS__)
|
||||
#define SR_LOG_INF(...) sr_log(SR_LL_INF, __VA_ARGS__)
|
||||
+#define SR_LOG_SEC(...) sr_log(SR_LL_SEC, __VA_ARGS__)
|
||||
#define SR_LOG_DBG(...) sr_log(SR_LL_DBG, __VA_ARGS__)
|
||||
|
||||
#define SR_CHECK_MEM_GOTO(cond, err_info, go) if (cond) { SR_ERRINFO_MEM(&(err_info)); goto go; }
|
||||
diff --git a/src/sysrepo_types.h b/src/sysrepo_types.h
|
||||
index 9f820b84..9a0de2be 100644
|
||||
--- a/src/sysrepo_types.h
|
||||
+++ b/src/sysrepo_types.h
|
||||
@@ -65,7 +65,8 @@ typedef enum {
|
||||
SR_LL_ERR, /**< Print only error messages. */
|
||||
SR_LL_WRN, /**< Print error and warning messages. */
|
||||
SR_LL_INF, /**< Besides errors and warnings, print some other informational messages. */
|
||||
- SR_LL_DBG /**< Print all messages including some development debug messages. */
|
||||
+ SR_LL_DBG, /**< Print all messages including some development debug messages. */
|
||||
+ SR_LL_SEC, /**< Security, e.g., audit trail, high priority remapped to SR_LL_WRN */
|
||||
} sr_log_level_t;
|
||||
|
||||
/**
|
||||
diff --git a/tests/tcommon.c b/tests/tcommon.c
|
||||
index 0dcdd0b4..49de738d 100644
|
||||
--- a/tests/tcommon.c
|
||||
+++ b/tests/tcommon.c
|
||||
@@ -35,6 +35,9 @@ _test_log_msg(sr_log_level_t level, const char *message, const char *prefix)
|
||||
case SR_LL_WRN:
|
||||
severity = "WRN";
|
||||
break;
|
||||
+ case SR_LL_SEC:
|
||||
+ severity = "SEC";
|
||||
+ break;
|
||||
case SR_LL_INF:
|
||||
severity = "INF";
|
||||
break;
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
From 7e49f394e0afedc0259d4364f5a9a83296fe2b72 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Wed, 21 Aug 2024 16:04:43 +0200
|
||||
Subject: [PATCH 8/8] Add audit trail for high priority system changes
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Committing a change to running, copying to a datastore, or calling an
|
||||
RPC may be restricted by NACM. In these cases a system administrator
|
||||
may want to know who made a change or sent an RPC.
|
||||
|
||||
For commit, we are only interested in changes to the running ds, which
|
||||
is when the system actually activates the changes. Copying to startup
|
||||
or other datastores is handled separately.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/sysrepo.c | 12 ++++++++++++
|
||||
1 file changed, 12 insertions(+)
|
||||
|
||||
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
||||
index 86d694e5..c7b97e53 100644
|
||||
--- a/src/sysrepo.c
|
||||
+++ b/src/sysrepo.c
|
||||
@@ -3946,6 +3946,9 @@ store:
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
+ if (session && session->nacm_user && mod_info->ds == SR_DS_RUNNING)
|
||||
+ SR_LOG_SEC("user \"%s\" commiting changes to %s ...", session->nacm_user, sr_ds2str(mod_info->ds));
|
||||
+
|
||||
/* publish "done" event, all changes were applied */
|
||||
if ((err_info = sr_shmsub_change_notify_change_done(mod_info, orig_name, orig_data, timeout_ms, cb_err_info))) {
|
||||
goto cleanup;
|
||||
@@ -3957,6 +3960,9 @@ store:
|
||||
}
|
||||
|
||||
cleanup:
|
||||
+ if (session && session->nacm_user && mod_info->ds == SR_DS_RUNNING)
|
||||
+ SR_LOG_SEC("user \"%s\" committed changes to %s.", session->nacm_user, sr_ds2str(mod_info->ds));
|
||||
+
|
||||
if (change_sub_lock) {
|
||||
assert(change_sub_lock == SR_LOCK_READ);
|
||||
|
||||
@@ -4285,6 +4291,9 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_
|
||||
}
|
||||
}
|
||||
|
||||
+ if (session->nacm_user)
|
||||
+ SR_LOG_SEC("user \"%s\" copied %s to %s", session->nacm_user, sr_ds2str(src_datastore), sr_ds2str(session->ds));
|
||||
+
|
||||
cleanup:
|
||||
/* MODULES UNLOCK */
|
||||
sr_shmmod_modinfo_unlock(&mod_info);
|
||||
@@ -6552,6 +6561,9 @@ sr_rpc_send_tree(sr_session_ctx_t *session, struct lyd_node *input, uint32_t tim
|
||||
}
|
||||
}
|
||||
|
||||
+ if (session->nacm_user && path)
|
||||
+ SR_LOG_SEC("user \"%s\" called RPC %s", session->nacm_user, path);
|
||||
+
|
||||
if (LYD_CTX(input_top) != LYD_CTX(input_op)) {
|
||||
/* different contexts if these are data of an extension (schema-mount) */
|
||||
for (ext_parent = input_op; ext_parent && !(ext_parent->flags & LYD_EXT); ext_parent = lyd_parent(ext_parent)) {}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
+18
-18
@@ -607,9 +607,9 @@ static void add_group(const char *user, const char *group)
|
||||
return; /* already group member */
|
||||
|
||||
if (systemf("adduser %s %s", user, group))
|
||||
ERROR("Failed giving user %s UNIX %s permissions.", user, group);
|
||||
SECURITY("Failed giving user %s UNIX %s permissions.", user, group);
|
||||
else
|
||||
NOTE("User %s added to UNIX %s group.", user, group);
|
||||
SECURITY("User %s added to UNIX %s group.", user, group);
|
||||
}
|
||||
|
||||
static void del_group(const char *user, const char *group)
|
||||
@@ -620,9 +620,9 @@ static void del_group(const char *user, const char *group)
|
||||
return; /* not member of group */
|
||||
|
||||
if (systemf("delgroup %s %s", user, group))
|
||||
ERROR("Failed removing user %s from UNIX %s group.", user, group);
|
||||
SECURITY("Failed removing user %s from UNIX %s group.", user, group);
|
||||
else
|
||||
NOTE("User %s removed from UNIX %s group.", user, group);
|
||||
SECURITY("User %s removed from UNIX %s group.", user, group);
|
||||
}
|
||||
|
||||
/* Users with a valid shell are also allowed CLI access */
|
||||
@@ -840,15 +840,15 @@ static int sys_add_user(sr_session_ctx_t *sess, char *name)
|
||||
/* Verify IDs aren't already used, like BusyBox adduser */
|
||||
if (getpwuid(st.st_uid) || getgrgid(st.st_uid) || getgrgid(st.st_gid)) {
|
||||
/* Exists but owned by someone else. */
|
||||
ERROR("Failed mapping user %s to /home/%s, uid:gid (%d:%d) already exists.",
|
||||
SECURITY("Failed mapping user %s to /home/%s, uid:gid (%d:%d) already exists.",
|
||||
name, name, st.st_uid, st.st_gid);
|
||||
err = sys_call_adduser(sess, name, 0, 0);
|
||||
} else {
|
||||
DEBUG("Reusing uid:gid %d:%d and /home/%s for new user %s",
|
||||
st.st_uid, st.st_gid, name, name);
|
||||
SECURITY("Reusing uid:gid %d:%d and /home/%s for new user %s",
|
||||
st.st_uid, st.st_gid, name, name);
|
||||
err = sys_call_adduser(sess, name, st.st_uid, st.st_gid);
|
||||
if (err) {
|
||||
ERROR("Failed reusing uid:gid from /home/%s, retrying create user ...", name);
|
||||
SECURITY("Failed reusing uid:gid from /home/%s, retrying create user ...", name);
|
||||
err = sys_call_adduser(sess, name, 0, 0);
|
||||
} else
|
||||
reused = true;
|
||||
@@ -857,11 +857,11 @@ static int sys_add_user(sr_session_ctx_t *sess, char *name)
|
||||
err = sys_call_adduser(sess, name, 0, 0);
|
||||
|
||||
if (err) {
|
||||
ERROR("Failed creating new user \"%s\"", name);
|
||||
SECURITY("Failed creating new user \"%s\"", name);
|
||||
return SR_ERR_SYS;
|
||||
}
|
||||
|
||||
NOTE("User \"%s\" created%s.", name, reused ? ", mapped to existing home directory" : "");
|
||||
SECURITY("User \"%s\" created%s.", name, reused ? ", mapped to existing home directory" : "");
|
||||
|
||||
/*
|
||||
* OpenSSH in Infix has been set up to use /var/run/sshd/%s.keys
|
||||
@@ -1016,7 +1016,7 @@ fail:
|
||||
endspent();
|
||||
ulckpwdf();
|
||||
exit:
|
||||
ERRNO("Failed setting password for %s", user);
|
||||
SECURITY("Failed setting password for %s", user);
|
||||
|
||||
return -1;
|
||||
}
|
||||
@@ -1072,7 +1072,7 @@ static sr_error_t handle_sr_passwd_update(sr_session_ctx_t *, struct confd *conf
|
||||
assert(change->new);
|
||||
|
||||
if (change->new->type != SR_STRING_T) {
|
||||
ERROR("Internal error, expected pass to be string type.");
|
||||
SECURITY("Internal error, expected user %s password to be string type.", user);
|
||||
err = SR_ERR_INTERNAL;
|
||||
break;
|
||||
}
|
||||
@@ -1094,7 +1094,7 @@ static sr_error_t handle_sr_passwd_update(sr_session_ctx_t *, struct confd *conf
|
||||
else if (!strcmp(hash, "*"))
|
||||
NOTE("Password login disabled for user %s", user);
|
||||
else
|
||||
NOTE("Password updated for user %s", user);
|
||||
SECURITY("Password updated for user %s", user);
|
||||
break;
|
||||
case SR_OP_DELETED:
|
||||
if (set_password(user, "*", false))
|
||||
@@ -1125,10 +1125,10 @@ static sr_error_t handle_sr_shell_update(sr_session_ctx_t *sess, struct confd *c
|
||||
|
||||
shell = sys_find_usable_shell(sess, (char *)user, is_admin_user(sess, user));
|
||||
if (set_shell(user, shell)) {
|
||||
ERROR("Failed updating shell to %s for user %s", shell, user);
|
||||
SECURITY("Failed updating shell to %s for user %s", shell, user);
|
||||
err = SR_ERR_SYS;
|
||||
} else {
|
||||
DEBUG("Login shell updated for user %s", user);
|
||||
SECURITY("Login shell updated for user %s", user);
|
||||
err = SR_ERR_OK;
|
||||
}
|
||||
free(shell);
|
||||
@@ -1148,7 +1148,7 @@ static sr_error_t check_sr_user_update(sr_session_ctx_t *, struct confd *, struc
|
||||
|
||||
name = sr_xpath_key_value(val->xpath, "user", "name", &state);
|
||||
if (!is_valid_username(name)) {
|
||||
ERROR("Invalid username \"%s\"", name);
|
||||
SECURITY("Invalid username \"%s\"", name);
|
||||
return SR_ERR_VALIDATION_FAILED;
|
||||
}
|
||||
NOTE("Username \"%s\" is valid", name);
|
||||
@@ -1361,7 +1361,7 @@ static sr_error_t change_auth_done(struct confd *confd, sr_session_ctx_t *sessio
|
||||
|
||||
err = generate_auth_keys(session, XPATH_AUTH_"/user//.");
|
||||
if (err) {
|
||||
ERROR("failed saving authorized-key data.");
|
||||
SECURITY("failed saving authorized-key data.");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -1436,7 +1436,7 @@ static int change_nacm(sr_session_ctx_t *session, uint32_t sub_id, const char *m
|
||||
|
||||
shell = sys_find_usable_shell(session, (char *)user, is_admin);
|
||||
if (set_shell(user, shell))
|
||||
ERROR("Failed adjusting shell for user %s", user);
|
||||
SECURITY("Failed adjusting shell for user %s", user);
|
||||
|
||||
if (is_admin)
|
||||
add_group(user, "wheel");
|
||||
|
||||
@@ -5,12 +5,16 @@
|
||||
|
||||
#include <syslog.h>
|
||||
#include <sysrepo.h>
|
||||
#include <sysrepo.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
extern int debug;
|
||||
|
||||
/* In IETF referred to LOG_AUDIT */
|
||||
#ifndef LOG_SECURITY
|
||||
#define LOG_SECURITY (1 << 13)
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_VASPRINTF
|
||||
int vasprintf(char **strp, const char *fmt, va_list ap);
|
||||
#endif
|
||||
@@ -29,5 +33,6 @@ int asprintf(char **strp, const char *fmt, ...);
|
||||
#define EMERG(fmt, ...) syslog(LOG_EMERG, fmt, ##__VA_ARGS__)
|
||||
#define ERROR(fmt, ...) syslog(LOG_ERR, fmt, ##__VA_ARGS__)
|
||||
#define ERRNO(fmt, ...) syslog(LOG_ERR, fmt ": %s", ##__VA_ARGS__, strerror(errno))
|
||||
#define SECURITY(fmt, ...) syslog(LOG_SECURITY | LOG_NOTICE, fmt, ##__VA_ARGS__)
|
||||
|
||||
#endif /* CONFD_COMMON_H_ */
|
||||
|
||||
Reference in New Issue
Block a user