--threshold option added to work with security verb and with the --offline option so that
users can determine what qualifies as a security threat. The threshold set by the user is
compared with the overall exposure level assigned to a unit file and if the exposure is
higher than the threshold, 'security' will return a non-zero exit status. The default value
of the --threshold option is 100.
Example Run:
1. testcase.service is a unit file created for testing the --threshold option
maanya-goenka@debian:~/systemd (systemd-security)$ cat<<EOF>testcase.service
> [Service]
> ExecStart = echo hello
> EOF
For the purposes of this demo, the security table outputted below has been cut to show only the first two security settings.
maanya-goenka@debian:~/systemd (systemd-security)$ sudo build/systemd-analyze security --offline=true testcase.service
/usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's
process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'.
Support for KillMode=none is deprecated and will eventually be removed.
/usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your
unit file, and consider removing the setting altogether.
/usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating
/var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly.
NAME DESCRIPTION EXPOSURE
✗ PrivateNetwork= Service has access to the host's network 0.5
✗ User=/DynamicUser= Service runs as root user 0.4
→ Overall exposure level for testcase.service: 9.6 UNSAFE 😨
maanya-goenka@debian:~/systemd (systemd-security)$ echo $? 0
2. Next, we use the same testcase.service file but add an additional --threshold=60 parameter. We would expect 'security' to exit
with a non-zero status because the overall exposure level (= 96) is higher than the set threshold (= 60).
maanya-goenka@debian:~/systemd (systemd-security)$ sudo build/systemd-analyze security --offline=true --threshold=60 testcase.service
/usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's
process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'.
Support for KillMode=none is deprecated and will eventually be removed.
/usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your
unit file, and consider removing the setting altogether.
/usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating
/var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly.
NAME DESCRIPTION EXPOSURE
✗ PrivateNetwork= Service has access to the host's network 0.5
✗ User=/DynamicUser= Service runs as root user 0.4
→ Overall exposure level for testcase.service: 9.6 UNSAFE 😨
maanya-goenka@debian:~/systemd (systemd-security)$ echo $? 1
an error.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--threshold=<replaceable>NUMBER</replaceable></option></term>
+
+ <listitem><para>With <command>security</command>, allow the user to set a custom value
+ to compare the overall exposure level with, for the specified unit file(s). If a unit's
+ overall exposure level, is greater than that set by the user, <command>security</command>
+ will return an error. <option>--threshold=</option> can be used with <option>--offline=</option>
+ as well and its default value is 100.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--iterations=<replaceable>NUMBER</replaceable></option></term>
elif __contains_word "$verb" ${VERBS[SECURITY]}; then
if [[ $cur = -* ]]; then
- comps='--help --version --no-pager --system --user -H --host -M --machine --offline'
+ comps='--help --version --no-pager --system --user -H --host -M --machine --offline --threshold'
else
if __contains_word "--user" ${COMP_WORDS[*]}; then
mode=--user
'--image=[Add support for discrete images]:PATH' \
'--recursive-errors=[When verifying a unit, control dependency verification]:MODE' \
'--offline=[Perform a security review of the specified unit file(s)]:BOOL' \
+ '--threshold=[Set a value to compare the overall security exposure level with]: NUMBER' \
'--no-pager[Do not pipe output into a pager]' \
'--man=[Do (not) check for existence of man pages]:boolean:(1 0)' \
'--order[When generating graph for dot, show only order]' \
},
};
-static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecurityFlags flags) {
+static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecurityFlags flags, unsigned threshold) {
static const struct {
uint64_t exposure;
const char *name;
return table_log_add_error(r);
}
+ /* Return error when overall exposure level is over threshold */
+ if (exposure > threshold)
+ return -EINVAL;
+
return 0;
}
return 0;
}
-static int analyze_security_one(sd_bus *bus, const char *name, Table *overview_table, AnalyzeSecurityFlags flags) {
+static int analyze_security_one(sd_bus *bus, const char *name, Table *overview_table,
+ AnalyzeSecurityFlags flags, unsigned threshold) {
+
_cleanup_(security_info_freep) SecurityInfo *info = security_info_new();
if (!info)
return log_oom();
if (r < 0)
return r;
- r = assess(info, overview_table, flags);
+ r = assess(info, overview_table, flags, threshold);
if (r < 0)
return r;
return 0;
}
-static int offline_security_check(Unit *u) {
+static int offline_security_check(Unit *u, unsigned threshold) {
_cleanup_(table_unrefp) Table *overview_table = NULL;
AnalyzeSecurityFlags flags = 0;
_cleanup_(security_info_freep) SecurityInfo *info = NULL;
if (r < 0)
return r;
- return assess(info, overview_table, flags);
+ return assess(info, overview_table, flags, threshold);
}
-static int offline_security_checks(char **filenames, UnitFileScope scope, bool check_man, bool run_generators, const char *root) {
+static int offline_security_checks(char **filenames, UnitFileScope scope, bool check_man, bool run_generators, unsigned threshold, const char *root) {
const ManagerTestRunFlags flags =
MANAGER_TEST_RUN_MINIMAL |
MANAGER_TEST_RUN_ENV_GENERATORS |
}
for (size_t i = 0; i < count; i++) {
- k = offline_security_check(units[i]);
+ k = offline_security_check(units[i], threshold);
if (k < 0 && r == 0)
r = k;
}
return r;
}
-int analyze_security(sd_bus *bus, char **units, UnitFileScope scope, bool check_man, bool run_generators, bool offline, const char *root, AnalyzeSecurityFlags flags) {
+int analyze_security(sd_bus *bus, char **units, UnitFileScope scope, bool check_man, bool run_generators,
+ bool offline, unsigned threshold, const char *root, AnalyzeSecurityFlags flags) {
+
_cleanup_(table_unrefp) Table *overview_table = NULL;
int ret = 0, r;
assert(bus);
if (offline)
- return offline_security_checks(units, scope, check_man, run_generators, root);
+ return offline_security_checks(units, scope, check_man, run_generators, threshold, root);
if (strv_length(units) != 1) {
overview_table = table_new("unit", "exposure", "predicate", "happy");
flags |= ANALYZE_SECURITY_SHORT|ANALYZE_SECURITY_ONLY_LOADED|ANALYZE_SECURITY_ONLY_LONG_RUNNING;
STRV_FOREACH(i, list) {
- r = analyze_security_one(bus, *i, overview_table, flags);
+ r = analyze_security_one(bus, *i, overview_table, flags, threshold);
if (r < 0 && ret >= 0)
ret = r;
}
} else
name = mangled;
- r = analyze_security_one(bus, name, overview_table, flags);
+ r = analyze_security_one(bus, name, overview_table, flags, threshold);
if (r < 0 && ret >= 0)
ret = r;
}
ANALYZE_SECURITY_ONLY_LONG_RUNNING = 1 << 2,
} AnalyzeSecurityFlags;
-int analyze_security(sd_bus *bus, char **units, UnitFileScope scope, bool check_man, bool run_generators, bool offline, const char *root, AnalyzeSecurityFlags flags);
+int analyze_security(sd_bus *bus, char **units, UnitFileScope scope, bool check_man, bool run_generators,
+ bool offline, unsigned threshold, const char *root, AnalyzeSecurityFlags flags);
static char *arg_root = NULL;
static char *arg_image = NULL;
static bool arg_offline = false;
+static unsigned arg_threshold = 100;
static unsigned arg_iterations = 1;
static usec_t arg_base_time = USEC_INFINITY;
(void) pager_open(arg_pager_flags);
- return analyze_security(bus, strv_skip(argv, 1), arg_scope, arg_man, arg_generators, arg_offline, arg_root, 0);
+ return analyze_security(bus, strv_skip(argv, 1), arg_scope, arg_man, arg_generators, arg_offline, arg_threshold, arg_root, 0);
}
static int help(int argc, char *argv[], void *userdata) {
" -h --help Show this help\n"
" --recursive-errors=MODE Control which units are verified\n"
" --offline=BOOL Perform a security review on unit file(s)\n"
+ " --threshold=N Exit with a non-zero status when overall\n"
+ " exposure level is over threshold value\n"
" --version Show package version\n"
" --no-pager Do not pipe output into a pager\n"
" --system Operate on system systemd instance\n"
ARG_BASE_TIME,
ARG_RECURSIVE_ERRORS,
ARG_OFFLINE,
+ ARG_THRESHOLD,
};
static const struct option options[] = {
{ "image", required_argument, NULL, ARG_IMAGE },
{ "recursive-errors", required_argument, NULL, ARG_RECURSIVE_ERRORS },
{ "offline", required_argument, NULL, ARG_OFFLINE },
+ { "threshold", required_argument, NULL, ARG_THRESHOLD },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
{ "global", no_argument, NULL, ARG_GLOBAL },
return r;
break;
+ case ARG_THRESHOLD:
+ r = safe_atou(optarg, &arg_threshold);
+ if (r < 0 || arg_threshold > 100)
+ return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse threshold: %s", optarg);
+
+ break;
+
case ARG_ITERATIONS:
r = safe_atou(optarg, &arg_iterations);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --offline= is only supported for security right now.");
+ if (arg_threshold != 100 && !streq_ptr(argv[optind], "security"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Option --threshold= is only supported for security right now.");
+
if (arg_scope == UNIT_FILE_GLOBAL &&
!STR_IN_SET(argv[optind] ?: "time", "dot", "unit-paths", "verify"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),