1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-22 17:35:35 +03:00

journald: if available pull audit messages from the kernel into journal logs

This commit is contained in:
Lennart Poettering 2014-11-03 20:58:24 +01:00
parent 8457f8d6ac
commit 875c2e220e
8 changed files with 604 additions and 18 deletions

View File

@ -4073,6 +4073,8 @@ libsystemd_journal_core_la_SOURCES = \
src/journal/journald-wall.h \
src/journal/journald-native.c \
src/journal/journald-native.h \
src/journal/journald-audit.c \
src/journal/journald-audit.h \
src/journal/journald-rate-limit.c \
src/journal/journald-rate-limit.h \
src/journal/journal-internal.h
@ -4224,7 +4226,8 @@ bin_PROGRAMS += \
dist_systemunit_DATA += \
units/systemd-journald.socket \
units/systemd-journald-dev-log.socket
units/systemd-journald-dev-log.socket \
units/systemd-journald-audit.socket
nodist_systemunit_DATA += \
units/systemd-journald.service \
@ -4243,7 +4246,8 @@ dist_catalog_DATA = \
SOCKETS_TARGET_WANTS += \
systemd-journald.socket \
systemd-journald-dev-log.socket
systemd-journald-dev-log.socket \
systemd-journald-audit.socket
SYSINIT_TARGET_WANTS += \
systemd-journald.service \

View File

@ -0,0 +1,489 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2014 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "missing.h"
#include "journald-audit.h"
typedef struct MapField {
const char *audit_field;
const char *journal_field;
int (*map)(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov);
} MapField;
static int map_simple_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
_cleanup_free_ char *c = NULL;
size_t l = 0, allocated = 0;
const char *e;
assert(field);
assert(p);
assert(iov);
assert(n_iov);
l = strlen(field);
allocated = l + 1;
c = malloc(allocated);
if (!c)
return -ENOMEM;
memcpy(c, field, l);
for (e = *p; *e != ' ' && *e != 0; e++) {
if (!GREEDY_REALLOC(c, allocated, l+2))
return -ENOMEM;
c[l++] = *e;
}
c[l] = 0;
if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
return -ENOMEM;
(*iov)[*n_iov].iov_base = c;
(*iov)[*n_iov].iov_len = l;
(*n_iov) ++;
*p = e;
c = NULL;
return 1;
}
static int map_string_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
_cleanup_free_ char *c = NULL;
const char *s, *e;
size_t l;
assert(field);
assert(p);
assert(iov);
assert(n_iov);
/* The kernel formats string fields in one of two formats. */
if (**p == '"') {
/* Normal quoted syntax */
s = *p + 1;
e = strchr(s, '"');
if (!e)
return 0;
l = strlen(field) + (e - s);
c = malloc(l+1);
if (!c)
return -ENOMEM;
*((char*) mempcpy(stpcpy(c, field), s, e - s)) = 0;
e += 1;
} else if (unhexchar(**p) >= 0) {
/* Hexadecimal escaping */
size_t allocated = 0;
l = strlen(field);
allocated = l + 2;
c = malloc(allocated);
if (!c)
return -ENOMEM;
memcpy(c, field, l);
for (e = *p; *e != ' ' && *e != 0; e += 2) {
int a, b;
a = unhexchar(e[0]);
if (a < 0)
return 0;
b = unhexchar(e[1]);
if (b < 0)
return 0;
if (!GREEDY_REALLOC(c, allocated, l+2))
return -ENOMEM;
c[l++] = (char) ((uint8_t) a << 4 | (uint8_t) b);
}
c[l] = 0;
} else
return 0;
if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
return -ENOMEM;
(*iov)[*n_iov].iov_base = c;
(*iov)[*n_iov].iov_len = l;
(*n_iov) ++;
*p = e;
c = NULL;
return 1;
}
static int map_generic_field(const char *prefix, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
const char *e, *f;
char *c, *t;
int r;
/* Implements fallback mappings for all fields we don't know */
for (e = *p; e < *p + 16; e++) {
if (*e == 0 || *e == ' ')
return 0;
if (*e == '=')
break;
if (!((*e >= 'a' && *e <= 'z') ||
(*e >= 'A' && *e <= 'Z') ||
(*e >= '0' && *e <= '9') ||
(*e == '_')))
return 0;
}
if (e <= *p || e >= *p + 16)
return 0;
c = alloca(strlen(prefix) + (e - *p) + 2);
t = stpcpy(c, prefix);
for (f = *p; f < e; f++)
*(t++) = *f >= 'a' && *f <= 'z' ? ((*f - 'a') + 'A') : *f;
strcpy(t, "=");
e ++;
r = map_simple_field(c, &e, iov, n_iov_allocated, n_iov);
if (r < 0)
return r;
*p = e;
return r;
}
/* Kernel fields are those occuring in the audit string before
* msg='. All of these fields are trusted, hence carry the "_" prefix.
* We try to translate the fields we know into our native names. The
* other's are generically mapped to _AUDIT_FIELD_XYZ= */
static const MapField map_fields_kernel[] = {
/* First, we map certain well-known audit fields into native
* well-known fields */
{ "pid=", "_PID=", map_simple_field },
{ "ppid=", "_PPID=", map_simple_field },
{ "uid=", "_UID=", map_simple_field },
{ "euid=", "_EUID=", map_simple_field },
{ "fsuid=", "_FSUID=", map_simple_field },
{ "gid=", "_GID=", map_simple_field },
{ "egid=", "_EGID=", map_simple_field },
{ "fsgid=", "_FSGID=", map_simple_field },
{ "tty=", "_TTY=", map_simple_field },
{ "ses=", "_AUDIT_SESSION=", map_simple_field },
{ "auid=", "_AUDIT_LOGINUID=", map_simple_field },
{ "subj=", "_SELINUX_CONTEXT=", map_simple_field },
{ "comm=", "_COMM=", map_string_field },
{ "exe=", "_EXE=", map_string_field },
{ "proctitle=", "_CMDLINE=", map_string_field },
/* Some fields don't map to native well-known fields. However,
* we know that they are string fields, hence let's undo
* string field escaping for them, though we stick to the
* generic field names. */
{ "path=", "_AUDIT_FIELD_PATH=", map_string_field },
{ "dev=", "_AUDIT_FIELD_DEV=", map_string_field },
{ "name=", "_AUDIT_FIELD_NAME=", map_string_field },
{}
};
/* Userspace fields are thos occuring in the audit string after
* msg='. All of these fields are untrusted, hence carry no "_"
* prefix. We map the fields we don't know to AUDIT_FIELD_XYZ= */
static const MapField map_fields_userspace[] = {
{ "cwd=", "AUDIT_FIELD_CWD=", map_string_field },
{ "cmd=", "AUDIT_FIELD_CMD=", map_string_field },
{ "acct=", "AUDIT_FIELD_ACCT=", map_string_field },
{ "exe=", "AUDIT_FIELD_EXE=", map_string_field },
{ "comm=", "AUDIT_FIELD_COMM=", map_string_field },
{}
};
static int map_all_fields(
const char *p,
const MapField map_fields[],
const char *prefix,
bool handle_msg,
struct iovec **iov,
size_t *n_iov_allocated,
unsigned *n_iov) {
int r;
assert(p);
assert(iov);
assert(n_iov_allocated);
assert(n_iov);
for (;;) {
bool mapped = false;
const MapField *m;
const char *v;
p += strspn(p, WHITESPACE);
if (*p == 0)
return 0;
if (handle_msg) {
v = startswith(p, "msg='");
if (v) {
const char *e;
char *c;
/* Userspace message. It's enclosed in
simple quotation marks, is not
escaped, but the last field in the
line, hence let's remove the
quotation mark, and apply the
userspace mapping instead of the
kernel mapping. */
e = endswith(v, "'");
if (!e)
return 0; /* don't continue splitting up if the final quotation mark is missing */
c = strndupa(v, e - v);
return map_all_fields(c, map_fields_userspace, "AUDIT_FIELD_", false, iov, n_iov_allocated, n_iov);
}
}
/* Try to map the kernel fields to our own names */
for (m = map_fields; m->audit_field; m++) {
v = startswith(p, m->audit_field);
if (!v)
continue;
r = m->map(m->journal_field, &v, iov, n_iov_allocated, n_iov);
if (r < 0) {
log_debug("Failed to parse audit array: %s", strerror(-r));
return r;
}
if (r > 0) {
mapped = true;
p = v;
break;
}
}
if (!mapped) {
r = map_generic_field(prefix, &p, iov, n_iov_allocated, n_iov);
if (r < 0) {
log_debug("Failed to parse audit array: %s", strerror(-r));
return r;
}
if (r == 0) {
/* Couldn't process as generic field, let's just skip over it */
p += strcspn(p, WHITESPACE);
}
}
}
}
static void process_audit_string(Server *s, int type, const char *data, size_t size, const struct timeval *tv) {
_cleanup_free_ struct iovec *iov = NULL;
size_t n_iov_allocated = 0;
unsigned n_iov = 0, k;
uint64_t seconds, msec, id;
const char *p;
unsigned z;
char id_field[sizeof("_AUDIT_ID=") + DECIMAL_STR_MAX(uint64_t)],
type_field[sizeof("_AUDIT_TYPE=") + DECIMAL_STR_MAX(int)],
source_time_field[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)];
const char *m;
assert(s);
if (size <= 0)
return;
if (!data)
return;
/* Note that the input buffer is NUL terminated, but let's
* check whether there is a spurious NUL byte */
if (memchr(data, 0, size))
return;
p = startswith(data, "audit");
if (!p)
return;
if (sscanf(p, "(%" PRIi64 ".%" PRIi64 ":%" PRIi64 "): %n",
&seconds,
&msec,
&id,
&k) != 3)
return;
p += k;
n_iov_allocated = N_IOVEC_META_FIELDS + 5;
iov = new(struct iovec, n_iov_allocated);
if (!iov) {
log_oom();
return;
}
IOVEC_SET_STRING(iov[n_iov++], "_TRANSPORT=audit");
sprintf(source_time_field, "_SOURCE_REALTIME_TIMESTAMP=%" PRIu64,
(usec_t) seconds * USEC_PER_SEC + (usec_t) msec * USEC_PER_MSEC);
IOVEC_SET_STRING(iov[n_iov++], source_time_field);
sprintf(type_field, "_AUDIT_TYPE=%i", type);
IOVEC_SET_STRING(iov[n_iov++], type_field);
sprintf(id_field, "_AUDIT_ID=%" PRIu64, id);
IOVEC_SET_STRING(iov[n_iov++], id_field);
m = strappenda("MESSAGE=", data);
IOVEC_SET_STRING(iov[n_iov++], m);
z = n_iov;
map_all_fields(p, map_fields_kernel, "_AUDIT_FIELD_", true, &iov, &n_iov_allocated, &n_iov);
if (!GREEDY_REALLOC(iov, n_iov_allocated, n_iov + N_IOVEC_META_FIELDS)) {
log_oom();
goto finish;
}
server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, tv, NULL, 0, NULL, LOG_NOTICE, 0);
finish:
/* free() all entries that map_all_fields() added. All others
* are allocated on the stack or are constant. */
for (; z < n_iov; z++)
free(iov[z].iov_base);
}
void server_process_audit_message(
Server *s,
const void *buffer,
size_t buffer_size,
const struct ucred *ucred,
const struct timeval *tv,
const union sockaddr_union *sa,
socklen_t salen) {
const struct nlmsghdr *nl = buffer;
assert(s);
if (buffer_size < ALIGN(sizeof(struct nlmsghdr)))
return;
assert(buffer);
/* Filter out fake data */
if (!sa ||
salen != sizeof(struct sockaddr_nl) ||
sa->nl.nl_family != AF_NETLINK ||
sa->nl.nl_pid != 0) {
log_debug("Audit netlink message from invalid sender.");
return;
}
if (!ucred || ucred->pid != 0) {
log_debug("Audit netlink message with invalid credentials.");
return;
}
if (!NLMSG_OK(nl, buffer_size)) {
log_error("Audit netlink message truncated.");
return;
}
/* Ignore special Netlink messages */
if (IN_SET(nl->nlmsg_type, NLMSG_NOOP, NLMSG_ERROR))
return;
/* Below AUDIT_FIRST_USER_MSG theer are only control messages, let's ignore those */
if (nl->nlmsg_type < AUDIT_FIRST_USER_MSG)
return;
process_audit_string(s, nl->nlmsg_type, NLMSG_DATA(nl), nl->nlmsg_len - ALIGN(sizeof(struct nlmsghdr)), tv);
}
int server_open_audit(Server *s) {
static const int one = 1;
int r;
if (s->audit_fd < 0) {
static const union sockaddr_union sa = {
.nl.nl_family = AF_NETLINK,
.nl.nl_pid = 0,
.nl.nl_groups = AUDIT_NLGRP_READLOG,
};
s->audit_fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT);
if (s->audit_fd < 0) {
if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT)
log_debug("Audit not supported in the kernel.");
else
log_warning("Failed to create audit socket, ignoring: %m");
return 0;
}
r = bind(s->audit_fd, &sa.sa, sizeof(sa.nl));
if (r < 0) {
log_error("Failed to join audit multicast group: %m");
return -errno;
}
} else
fd_nonblock(s->audit_fd, 1);
r = setsockopt(s->audit_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
if (r < 0) {
log_error("Failed to set SO_PASSCRED on audit socket: %m");
return -errno;
}
r = setsockopt(s->audit_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
if (r < 0) {
log_error("Failed to set SO_TIMESTAMP on audit socket: %m");
return -errno;
}
r = sd_event_add_io(s->event, &s->audit_event_source, s->audit_fd, EPOLLIN, process_datagram, s);
if (r < 0) {
log_error("Failed to add audit fd to event loop: %s", strerror(-r));
return r;
}
return 0;
}

View File

@ -0,0 +1,29 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
#pragma once
/***
This file is part of systemd.
Copyright 2014 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "socket-util.h"
#include "journald-server.h"
void server_process_audit_message(Server *s, const void *buffer, size_t buffer_size, const struct ucred *ucred, const struct timeval *tv, const union sockaddr_union *sa, socklen_t salen);
int server_open_audit(Server*s);

View File

@ -50,6 +50,7 @@
#include "journald-stream.h"
#include "journald-console.h"
#include "journald-native.h"
#include "journald-audit.h"
#include "journald-server.h"
#ifdef HAVE_ACL
@ -1112,7 +1113,7 @@ int process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userda
Server *s = userdata;
assert(s);
assert(fd == s->native_fd || fd == s->syslog_fd);
assert(fd == s->native_fd || fd == s->syslog_fd || fd == s->audit_fd);
if (revents != EPOLLIN) {
log_error("Got invalid event from epoll for datagram fd: %"PRIx32, revents);
@ -1142,28 +1143,37 @@ int process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userda
CMSG_SPACE(sizeof(int)) + /* fd */
CMSG_SPACE(NAME_MAX)]; /* selinux label */
} control = {};
union sockaddr_union sa = {};
struct msghdr msghdr = {
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = &control,
.msg_controllen = sizeof(control),
.msg_name = &sa,
.msg_namelen = sizeof(sa),
};
ssize_t n;
int v;
int *fds = NULL;
unsigned n_fds = 0;
int v = 0;
size_t m;
if (ioctl(fd, SIOCINQ, &v) < 0) {
log_error("SIOCINQ failed: %m");
return -errno;
}
/* Try to get the right size, if we can. (Not all
* sockets support SIOCINQ, hence we just try, but
* don't rely on it. */
(void) ioctl(fd, SIOCINQ, &v);
if (!GREEDY_REALLOC(s->buffer, s->buffer_size, LINE_MAX + (size_t) v))
/* Fix it up, if it is too small. We use the same fixed value as auditd here. Awful!*/
m = PAGE_ALIGN(MAX3((size_t) v + 1,
(size_t) LINE_MAX,
ALIGN(sizeof(struct nlmsghdr)) + ALIGN((size_t) MAX_AUDIT_MESSAGE_LENGTH)) + 1);
if (!GREEDY_REALLOC(s->buffer, s->buffer_size, m))
return log_oom();
iovec.iov_base = s->buffer;
iovec.iov_len = s->buffer_size;
iovec.iov_len = s->buffer_size - 1; /* Leave room for trailing NUL we add later */
n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
if (n < 0) {
@ -1195,20 +1205,30 @@ int process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userda
}
}
/* And a trailing NUL, just in case */
s->buffer[n] = 0;
if (fd == s->syslog_fd) {
if (n > 0 && n_fds == 0) {
s->buffer[n] = 0;
if (n > 0 && n_fds == 0)
server_process_syslog_message(s, strstrip(s->buffer), ucred, tv, label, label_len);
} else if (n_fds > 0)
else if (n_fds > 0)
log_warning("Got file descriptors via syslog socket. Ignoring.");
} else {
} else if (fd == s->native_fd) {
if (n > 0 && n_fds == 0)
server_process_native_message(s, s->buffer, n, ucred, tv, label, label_len);
else if (n == 0 && n_fds == 1)
server_process_native_file(s, fds[0], ucred, tv, label, label_len);
else if (n_fds > 0)
log_warning("Got too many file descriptors via native socket. Ignoring.");
} else {
assert(fd == s->audit_fd);
if (n > 0 && n_fds == 0)
server_process_audit_message(s, s->buffer, n, ucred, tv, &sa, msghdr.msg_namelen);
else if (n_fds > 0)
log_warning("Got file descriptors via audit socket. Ignoring.");
}
close_many(fds, n_fds);
@ -1452,7 +1472,7 @@ int server_init(Server *s) {
assert(s);
zero(*s);
s->syslog_fd = s->native_fd = s->stdout_fd = s->dev_kmsg_fd = s->hostname_fd = -1;
s->syslog_fd = s->native_fd = s->stdout_fd = s->dev_kmsg_fd = s->audit_fd = s->hostname_fd = -1;
s->compress = true;
s->seal = true;
@ -1537,6 +1557,15 @@ int server_init(Server *s) {
s->syslog_fd = fd;
} else if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1) > 0) {
if (s->audit_fd >= 0) {
log_error("Too many audit sockets passed.");
return -EINVAL;
}
s->audit_fd = fd;
} else {
log_error("Unknown socket passed.");
return -EINVAL;
@ -1559,6 +1588,10 @@ int server_init(Server *s) {
if (r < 0)
return r;
r = server_open_audit(s);
if (r < 0)
return r;
r = server_open_kernel_seqnum(s);
if (r < 0)
return r;
@ -1632,6 +1665,7 @@ void server_done(Server *s) {
sd_event_source_unref(s->native_event_source);
sd_event_source_unref(s->stdout_event_source);
sd_event_source_unref(s->dev_kmsg_event_source);
sd_event_source_unref(s->audit_event_source);
sd_event_source_unref(s->sync_event_source);
sd_event_source_unref(s->sigusr1_event_source);
sd_event_source_unref(s->sigusr2_event_source);
@ -1644,6 +1678,7 @@ void server_done(Server *s) {
safe_close(s->native_fd);
safe_close(s->stdout_fd);
safe_close(s->dev_kmsg_fd);
safe_close(s->audit_fd);
safe_close(s->hostname_fd);
if (s->rate_limit)

View File

@ -59,6 +59,7 @@ typedef struct Server {
int native_fd;
int stdout_fd;
int dev_kmsg_fd;
int audit_fd;
int hostname_fd;
sd_event *event;
@ -67,6 +68,7 @@ typedef struct Server {
sd_event_source *native_event_source;
sd_event_source *stdout_event_source;
sd_event_source *dev_kmsg_event_source;
sd_event_source *audit_event_source;
sd_event_source *sync_event_source;
sd_event_source *sigusr1_event_source;
sd_event_source *sigusr2_event_source;

View File

@ -33,6 +33,7 @@
#include <linux/input.h>
#include <linux/if_link.h>
#include <linux/loop.h>
#include <linux/audit.h>
#ifdef HAVE_AUDIT
#include <libaudit.h>
@ -557,3 +558,11 @@ static inline int setns(int fd, int nstype) {
#ifndef LOOPBACK_IFINDEX
#define LOOPBACK_IFINDEX 1
#endif
#ifndef MAX_AUDIT_MESSAGE_LENGTH
#define MAX_AUDIT_MESSAGE_LENGTH 8970
#endif
#ifndef AUDIT_NLGRP_MAX
#define AUDIT_NLGRP_READLOG 1
#endif

View File

@ -0,0 +1,18 @@
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Journal Audit Socket
Documentation=man:systemd-journald.service(8) man:journald.conf(5)
DefaultDependencies=no
Before=sockets.target
[Socket]
Service=systemd-journald.service
ReceiveBuffer=128M
ListenNetlink=audit 1
PassCredentials=yes

View File

@ -10,17 +10,17 @@ Description=Journal Service
Documentation=man:systemd-journald.service(8) man:journald.conf(5)
DefaultDependencies=no
Requires=systemd-journald.socket
After=systemd-journald.socket systemd-journald-dev-log.socket syslog.socket
After=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket syslog.socket
Before=sysinit.target
[Service]
Sockets=systemd-journald.socket systemd-journald-dev-log.socket
Sockets=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket
ExecStart=@rootlibexecdir@/systemd-journald
Restart=always
RestartSec=0
NotifyAccess=all
StandardOutput=null
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE
WatchdogSec=1min
# Increase the default a bit in order to allow many simultaneous