mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 16:37:19 +09:00
sysusers: emit audit events for user and group creation (#35957)
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, so the format of the events that is generated on success should be identical. On failure, things are more complicated. We write the whole file at once, once, so we first generate "success" messages for each entry, then we try to write the files, and if things fail, we generate failure messages to all entries that we failed to write.
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#if HAVE_AUDIT
|
||||
# include <libaudit.h>
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "errno-util.h"
|
||||
#include "log.h"
|
||||
#include "pidref.h"
|
||||
|
||||
#define AUDIT_SESSION_INVALID UINT32_MAX
|
||||
@@ -17,3 +23,30 @@ bool use_audit(void);
|
||||
static inline bool audit_session_is_valid(uint32_t id) {
|
||||
return id > 0 && id != AUDIT_SESSION_INVALID;
|
||||
}
|
||||
|
||||
/* The wrappers for audit_open() and audit_close() are inline functions so that we don't get a spurious
|
||||
* linkage to libaudit in libbasic, but we also don't need to create a separate source file for two very
|
||||
* short functions. */
|
||||
|
||||
static inline int close_audit_fd(int fd) {
|
||||
#if HAVE_AUDIT
|
||||
if (fd >= 0)
|
||||
audit_close(fd);
|
||||
#else
|
||||
assert(fd < 0);
|
||||
#endif
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
static inline int open_audit_fd_or_warn(void) {
|
||||
int fd = -EBADF;
|
||||
|
||||
#if HAVE_AUDIT
|
||||
/* If the kernel lacks netlink or audit support, don't worry about it. */
|
||||
fd = audit_open();
|
||||
if (fd < 0)
|
||||
return log_full_errno(ERRNO_IS_NOT_SUPPORTED(errno) ? LOG_DEBUG : LOG_WARNING,
|
||||
errno, "Failed to connect to audit log, ignoring: %m");
|
||||
#endif
|
||||
return fd;
|
||||
}
|
||||
|
||||
@@ -5,58 +5,36 @@
|
||||
#include "audit-fd.h"
|
||||
|
||||
#if HAVE_AUDIT
|
||||
# include <stdbool.h>
|
||||
|
||||
#include <libaudit.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "capability-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "log.h"
|
||||
# include "audit-util.h"
|
||||
# include "capability-util.h"
|
||||
|
||||
static bool initialized = false;
|
||||
static int audit_fd;
|
||||
|
||||
int get_audit_fd(void) {
|
||||
static int audit_fd = -EBADF;
|
||||
#endif
|
||||
|
||||
int get_core_audit_fd(void) {
|
||||
#if HAVE_AUDIT
|
||||
if (!initialized) {
|
||||
if (have_effective_cap(CAP_AUDIT_WRITE) <= 0) {
|
||||
if (have_effective_cap(CAP_AUDIT_WRITE) <= 0)
|
||||
audit_fd = -EPERM;
|
||||
initialized = true;
|
||||
|
||||
return audit_fd;
|
||||
}
|
||||
|
||||
audit_fd = audit_open();
|
||||
|
||||
if (audit_fd < 0) {
|
||||
if (!IN_SET(errno, EAFNOSUPPORT, EPROTONOSUPPORT))
|
||||
log_error_errno(errno, "Failed to connect to audit log: %m");
|
||||
|
||||
audit_fd = errno ? -errno : -EINVAL;
|
||||
}
|
||||
else
|
||||
audit_fd = open_audit_fd_or_warn();
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
return audit_fd;
|
||||
#else
|
||||
return -EAFNOSUPPORT;
|
||||
#endif
|
||||
}
|
||||
|
||||
void close_audit_fd(void) {
|
||||
|
||||
if (initialized && audit_fd >= 0)
|
||||
safe_close(audit_fd);
|
||||
|
||||
void close_core_audit_fd(void) {
|
||||
#if HAVE_AUDIT
|
||||
close_audit_fd(audit_fd);
|
||||
initialized = true;
|
||||
audit_fd = -ECONNRESET;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int get_audit_fd(void) {
|
||||
return -EAFNOSUPPORT;
|
||||
}
|
||||
|
||||
void close_audit_fd(void) {
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int get_audit_fd(void);
|
||||
void close_audit_fd(void);
|
||||
int get_core_audit_fd(void);
|
||||
void close_core_audit_fd(void);
|
||||
|
||||
@@ -3560,7 +3560,7 @@ void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) {
|
||||
if (MANAGER_IS_RELOADING(m))
|
||||
return;
|
||||
|
||||
audit_fd = get_audit_fd();
|
||||
audit_fd = get_core_audit_fd();
|
||||
if (audit_fd < 0)
|
||||
return;
|
||||
|
||||
@@ -3575,7 +3575,7 @@ void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) {
|
||||
if (ERRNO_IS_PRIVILEGE(errno)) {
|
||||
/* We aren't allowed to send audit messages? Then let's not retry again. */
|
||||
log_debug_errno(errno, "Failed to send audit message, closing audit socket: %m");
|
||||
close_audit_fd();
|
||||
close_core_audit_fd();
|
||||
} else
|
||||
log_warning_errno(errno, "Failed to send audit message, ignoring: %m");
|
||||
}
|
||||
|
||||
@@ -98,9 +98,7 @@ _printf_(2, 3) static int log_callback(int type, const char *fmt, ...) {
|
||||
const char *fmt2;
|
||||
|
||||
#if HAVE_AUDIT
|
||||
int fd;
|
||||
|
||||
fd = get_audit_fd();
|
||||
int fd = get_core_audit_fd();
|
||||
|
||||
if (fd >= 0) {
|
||||
_cleanup_free_ char *buf = NULL;
|
||||
@@ -112,9 +110,9 @@ _printf_(2, 3) static int log_callback(int type, const char *fmt, ...) {
|
||||
|
||||
if (r >= 0) {
|
||||
if (type == SELINUX_AVC)
|
||||
audit_log_user_avc_message(get_audit_fd(), AUDIT_USER_AVC, buf, NULL, NULL, NULL, getuid());
|
||||
audit_log_user_avc_message(fd, AUDIT_USER_AVC, buf, NULL, NULL, NULL, getuid());
|
||||
else if (type == SELINUX_ERROR)
|
||||
audit_log_user_avc_message(get_audit_fd(), AUDIT_USER_SELINUX_ERR, buf, NULL, NULL, NULL, getuid());
|
||||
audit_log_user_avc_message(fd, AUDIT_USER_SELINUX_ERR, buf, NULL, NULL, NULL, getuid());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ executables += [
|
||||
'name' : 'systemd-sysusers',
|
||||
'public' : true,
|
||||
'sources' : files('sysusers.c'),
|
||||
'dependencies' : libaudit,
|
||||
},
|
||||
executable_template + {
|
||||
'name' : 'systemd-sysusers.standalone',
|
||||
@@ -20,6 +21,7 @@ executables += [
|
||||
libshared_static,
|
||||
libsystemd_static,
|
||||
],
|
||||
'dependencies' : libaudit,
|
||||
'build_by_default' : have_standalone_binaries,
|
||||
'install' : have_standalone_binaries,
|
||||
},
|
||||
|
||||
@@ -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_free_ char *passwd_path = NULL;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
@@ -952,6 +999,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)
|
||||
@@ -969,6 +1018,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)
|
||||
@@ -2215,6 +2266,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,
|
||||
};
|
||||
|
||||
@@ -2264,6 +2316,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
|
||||
|
||||
@@ -5,12 +5,9 @@
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if HAVE_AUDIT
|
||||
#include <libaudit.h>
|
||||
#endif
|
||||
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "audit-util.h"
|
||||
#include "alloc-util.h"
|
||||
#include "bus-error.h"
|
||||
#include "bus-locator.h"
|
||||
@@ -30,20 +27,14 @@
|
||||
|
||||
typedef struct Context {
|
||||
sd_bus *bus;
|
||||
#if HAVE_AUDIT
|
||||
int audit_fd;
|
||||
#endif
|
||||
} Context;
|
||||
|
||||
static void context_clear(Context *c) {
|
||||
assert(c);
|
||||
|
||||
c->bus = sd_bus_flush_close_unref(c->bus);
|
||||
#if HAVE_AUDIT
|
||||
if (c->audit_fd >= 0)
|
||||
audit_close(c->audit_fd);
|
||||
c->audit_fd = -EBADF;
|
||||
#endif
|
||||
c->audit_fd = close_audit_fd(c->audit_fd);
|
||||
}
|
||||
|
||||
static int get_startup_monotonic_time(Context *c, usec_t *ret) {
|
||||
@@ -256,22 +247,14 @@ static int run(int argc, char *argv[]) {
|
||||
};
|
||||
|
||||
_cleanup_(context_clear) Context c = {
|
||||
#if HAVE_AUDIT
|
||||
.audit_fd = -EBADF,
|
||||
#endif
|
||||
};
|
||||
|
||||
log_setup();
|
||||
|
||||
umask(0022);
|
||||
|
||||
#if HAVE_AUDIT
|
||||
/* If the kernel lacks netlink or audit support, don't worry about it. */
|
||||
c.audit_fd = audit_open();
|
||||
if (c.audit_fd < 0)
|
||||
log_full_errno(IN_SET(errno, EAFNOSUPPORT, EPROTONOSUPPORT) ? LOG_DEBUG : LOG_WARNING,
|
||||
errno, "Failed to connect to audit log, ignoring: %m");
|
||||
#endif
|
||||
c.audit_fd = open_audit_fd_or_warn();
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, &c);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user