sysusers: emit audit events for user and group creation

Background: Fedora/RHEL are switching to sysusers.d metadata for creation of
users and groups for system users defined by packages
(https://fedoraproject.org/wiki/Changes/RPMSuportForSystemdSysusers).
Packages carry sysusers files. During package installation, rpm calls an
program to execute on this config. This program may either be
/usr/lib/rpm/sysusers.sh which calls useradd/groupadd, or
/usr/bin/systemd-sysusers. To match the functionality provided by
useradd/groupadd from the shadow-utils project, systemd-sysusers must emit
audit events so that it provides a drop-in replacement.

systemd-sysuers will emit audit events AUDIT_ADD_USER/AUDIT_ADD_GROUP when
adding users and groups. The operation "names" are copied from shadow-utils in
Fedora (which has a patch to change them from the upstream version), so the
format of the events that is generated on success should be identical.

The helper code is shared between sysusers and utmp-wtmp. I changed the
audit_fd variable to be unconditional. This way we can avoid ugly iffdefery
every time the variable would be used. The cost is that 4 bytes of unused
storage might be present. This is negligible, and the compiler might even be
able to optimize that away if it inlines things.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2025-01-15 10:35:28 +01:00
parent 96963e5615
commit 9c6afab6b6
4 changed files with 94 additions and 20 deletions
+56
View File
@@ -3,6 +3,7 @@
#include <getopt.h>
#include "alloc-util.h"
#include "audit-util.h"
#include "build.h"
#include "chase.h"
#include "conf-files.h"
@@ -106,6 +107,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
typedef struct Context {
int audit_fd;
OrderedHashmap *users, *groups;
OrderedHashmap *todo_uids, *todo_gids;
OrderedHashmap *members;
@@ -126,6 +129,8 @@ typedef struct Context {
static void context_done(Context *c) {
assert(c);
c->audit_fd = close_audit_fd(c->audit_fd);
ordered_hashmap_free(c->groups);
ordered_hashmap_free(c->users);
ordered_hashmap_free(c->members);
@@ -163,6 +168,48 @@ static void maybe_emit_login_defs_warning(Context *c) {
c->login_defs_need_warning = false;
}
static void log_audit_accounts(Context *c, ItemType what) {
#if HAVE_AUDIT
assert(c);
assert(IN_SET(what, ADD_USER, ADD_GROUP));
if (arg_dry_run || c->audit_fd < 0)
return;
Item *i;
int type = what == ADD_USER ? AUDIT_ADD_USER : AUDIT_ADD_GROUP;
const char *op = what == ADD_USER ? "adding-user" : "adding-group";
/* Notes:
*
* The op must not contain whitespace. The format with a dash matches what Fedora shadow-utils uses.
*
* We send id == -1, even though we know the number, in particular on success. This is because if we
* send the id, the generated audit message will not contain the name. The name seems more useful
* than the number, hence send just the name:
*
* type=ADD_USER msg=audit(01/10/2025 16:02:00.639:3854) :
* pid=3846380 uid=root auid=zbyszek ses=2 msg='op=adding-user id=unknown(952) exe=systemd-sysusers ... res=success'
* vs.
* type=ADD_USER msg=audit(01/10/2025 16:03:15.457:3908) :
* pid=3846607 uid=root auid=zbyszek ses=2 msg='op=adding-user acct=foo5 exe=systemd-sysusers ... res=success'
*/
ORDERED_HASHMAP_FOREACH(i, what == ADD_USER ? c->todo_uids : c->todo_gids)
audit_log_acct_message(
c->audit_fd,
type,
program_invocation_short_name,
op,
i->name,
/* id= */ (unsigned) -1,
/* host= */ NULL,
/* addr= */ NULL,
/* tty= */ NULL,
/* success= */ 1);
#endif
}
static int load_user_database(Context *c) {
_cleanup_fclose_ FILE *f = NULL;
const char *passwd_path;
@@ -971,6 +1018,8 @@ static int write_files(Context *c) {
group_tmp, group_path);
group_tmp = mfree(group_tmp);
}
/* OK, we have written the group entries successfully */
log_audit_accounts(c, ADD_GROUP);
if (gshadow) {
r = rename_and_apply_smack_floor_label(gshadow_tmp, gshadow_path);
if (r < 0)
@@ -988,6 +1037,8 @@ static int write_files(Context *c) {
passwd_tmp = mfree(passwd_tmp);
}
/* OK, we have written the user entries successfully */
log_audit_accounts(c, ADD_USER);
if (shadow) {
r = rename_and_apply_smack_floor_label(shadow_tmp, shadow_path);
if (r < 0)
@@ -2232,6 +2283,7 @@ static int run(int argc, char *argv[]) {
#endif
_cleanup_close_ int lock = -EBADF;
_cleanup_(context_done) Context c = {
.audit_fd = -EBADF,
.search_uid = UID_INVALID,
};
@@ -2281,6 +2333,10 @@ static int run(int argc, char *argv[]) {
assert(!arg_image);
#endif
/* Prepare to emit audit events, but only if we're operating on the host system. */
if (!arg_root)
c.audit_fd = open_audit_fd_or_warn();
/* If command line arguments are specified along with --replace, read all configuration files and
* insert the positional arguments at the specified place. Otherwise, if command line arguments are
* specified, execute just them, and finally, without --replace= or any positional arguments, just