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:
Joachim Wiberg
2024-08-26 20:54:49 +02:00
parent 0ffca5aa21
commit dac2182069
5 changed files with 175 additions and 21 deletions
+3 -2
View File
@@ -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
View File
@@ -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");
+6 -1
View File
@@ -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_ */