]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
analyze: add --profile switch to security verb 21541/head
authorLuca Boccassi <luca.boccassi@microsoft.com>
Fri, 26 Nov 2021 15:46:40 +0000 (15:46 +0000)
committerLuca Boccassi <luca.boccassi@microsoft.com>
Fri, 26 Nov 2021 18:17:26 +0000 (18:17 +0000)
Allows to pass a portable profile when doing offline analysis of
units. Especially useful for analyzing portable images, since a
lot of the security-relevant settings in those cases come from
the profiles, but they are not shipped in the portable images.

man/systemd-analyze.xml
shell-completion/bash/systemd-analyze
shell-completion/zsh/_systemd-analyze
src/analyze/analyze-security.c
src/analyze/analyze-security.h
src/analyze/analyze.c
test/units/testsuite-65.sh

index c0b6b477d6a9af504e36cc4dbdb704dc39635cdd..6482fcfe485dc651a06323e571d231f015ef789d 100644 (file)
@@ -818,6 +818,15 @@ $ systemd-analyze verify /tmp/source:alias.service
         an error.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--profile=<replaceable>PATH</replaceable></option></term>
+
+        <listitem><para>With <command>security</command> <option>--offline=</option>, takes into
+        consideration the specified portable profile when assessing the unit(s) settings.
+        The profile can be passed by name, in which case the well-known system locations will
+        be searched, or it can be the full path to a specific drop-in file.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--threshold=<replaceable>NUMBER</replaceable></option></term>
 
index 350dcabb9486e971e2b8c3037190dfebde122da6..ddee57b0e71db3bb4d8440674e45d77363d43889 100644 (file)
@@ -145,7 +145,7 @@ _systemd_analyze() {
 
     elif __contains_word "$verb" ${VERBS[SECURITY]}; then
         if [[ $cur = -* ]]; then
-            comps='--help --version --no-pager --system --user -H --host -M --machine --offline --threshold --security-policy --json=off --json=pretty --json=short --root --image'
+            comps='--help --version --no-pager --system --user -H --host -M --machine --offline --threshold --security-policy --json=off --json=pretty --json=short --root --image --profile=default --profile=nonetwork --profile=strict --profile=trusted'
         elif ! __contains_word "--offline" ${COMP_WORDS[*]}; then
             if __contains_word "--user" ${COMP_WORDS[*]}; then
                 mode=--user
index 468fd0e5829326d37889e3566ba606979fed44e3..639964f064583c9ba896cb113cfd24798180199c 100644 (file)
@@ -87,6 +87,7 @@ _arguments \
     '--threshold=[Set a value to compare the overall security exposure level with]: NUMBER' \
     '--security-policy=[Allow user to use customized requirements to compare unit file(s) against]: PATH' \
     '--json=[Generate a JSON output of the security analysis table]:MODE:(pretty short off)' \
+    '--profile=[Include the specified profile in the security review of the unit(s)]: PATH' \
     '--no-pager[Do not pipe output into a pager]' \
     '--man=[Do (not) check for existence of man pages]:BOOL:(yes no)' \
     '--generators=[Do (not) run unit generators]:BOOL:(yes no)' \
index d3e011e903f5c40716e54512509d51680eeb392c..d52518677cff8c6d59d869bae8f58c28fa2860e2 100644 (file)
@@ -9,6 +9,7 @@
 #include "bus-map-properties.h"
 #include "bus-unit-util.h"
 #include "bus-util.h"
+#include "copy.h"
 #include "env-util.h"
 #include "format-table.h"
 #include "in-addr-prefix-util.h"
@@ -17,6 +18,7 @@
 #include "manager.h"
 #include "missing_capability.h"
 #include "missing_sched.h"
+#include "mkdir.h"
 #include "nulstr-util.h"
 #include "parse-util.h"
 #include "path-util.h"
@@ -2646,6 +2648,7 @@ static int offline_security_checks(char **filenames,
                                    bool run_generators,
                                    unsigned threshold,
                                    const char *root,
+                                   const char *profile,
                                    PagerFlags pager_flags,
                                    JsonFormatFlags json_format_flags) {
 
@@ -2682,6 +2685,13 @@ static int offline_security_checks(char **filenames,
         if (r < 0)
                 return r;
 
+        if (profile) {
+                /* Ensure the temporary directory is in the search path, so that we can add drop-ins. */
+                r = strv_extend(&m->lookup_paths.search_path, m->lookup_paths.temporary_dir);
+                if (r < 0)
+                        return log_oom();
+        }
+
         log_debug("Loading remaining units from the command line...");
 
         STRV_FOREACH(filename, filenames) {
@@ -2697,6 +2707,33 @@ static int offline_security_checks(char **filenames,
                         continue;
                 }
 
+                /* When a portable image is analyzed, the profile is what provides a good chunk of
+                 * the security-related settings, but they are obviously not shipped with the image.
+                 * This allows to take them in consideration. */
+                if (profile) {
+                        _cleanup_free_ char *unit_name = NULL, *dropin = NULL, *profile_path = NULL;
+
+                        r = path_extract_filename(prepared, &unit_name);
+                        if (r < 0)
+                                return log_oom();
+
+                        dropin = strjoin(m->lookup_paths.temporary_dir, "/", unit_name, ".d/profile.conf");
+                        if (!dropin)
+                                return log_oom();
+                        (void) mkdir_parents(dropin, 0755);
+
+                        if (!is_path(profile)) {
+                                r = find_portable_profile(profile, unit_name, &profile_path);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to find portable profile %s: %m", profile);
+                                profile = profile_path;
+                        }
+
+                        r = copy_file(profile, dropin, 0, 0644, 0, 0, 0);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to copy: %m");
+                }
+
                 k = manager_load_startable_unit_or_warn(m, NULL, prepared, &units[count]);
                 if (k < 0) {
                         if (r == 0)
@@ -2725,6 +2762,7 @@ int analyze_security(sd_bus *bus,
                      bool offline,
                      unsigned threshold,
                      const char *root,
+                     const char *profile,
                      JsonFormatFlags json_format_flags,
                      PagerFlags pager_flags,
                      AnalyzeSecurityFlags flags) {
@@ -2735,7 +2773,7 @@ int analyze_security(sd_bus *bus,
         assert(bus);
 
         if (offline)
-                return offline_security_checks(units, policy, scope, check_man, run_generators, threshold, root, pager_flags, json_format_flags);
+                return offline_security_checks(units, policy, scope, check_man, run_generators, threshold, root, profile, pager_flags, json_format_flags);
 
         if (strv_length(units) != 1) {
                 overview_table = table_new("unit", "exposure", "predicate", "happy");
index 492881c3853ae9355491cfa389af852d991cc566..07483248eebb704ec5db6c4f5b635741b265e4f0 100644 (file)
@@ -24,6 +24,7 @@ int analyze_security(sd_bus *bus,
                      bool offline,
                      unsigned threshold,
                      const char *root,
+                     const char *profile,
                      JsonFormatFlags json_format_flags,
                      PagerFlags pager_flags,
                      AnalyzeSecurityFlags flags);
index 740faa507bbd6c96bb3c71708e411ab1416967fd..a641be4179b1441f825da6e74a8785403ecb1bef 100644 (file)
@@ -105,6 +105,7 @@ static usec_t arg_base_time = USEC_INFINITY;
 static char *arg_unit = NULL;
 static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
 static bool arg_quiet = false;
+static char *arg_profile = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
@@ -112,6 +113,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_security_policy, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_unit, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_profile, freep);
 
 typedef struct BootTimes {
         usec_t firmware_time;
@@ -2423,6 +2425,7 @@ static int do_security(int argc, char *argv[], void *userdata) {
                                 arg_offline,
                                 arg_threshold,
                                 arg_root,
+                                arg_profile,
                                 arg_json_format_flags,
                                 arg_pager_flags,
                                 /*flags=*/ 0);
@@ -2497,6 +2500,8 @@ static int help(int argc, char *argv[], void *userdata) {
                "     --iterations=N          Show the specified number of iterations\n"
                "     --base-time=TIMESTAMP   Calculate calendar times relative to\n"
                "                             specified time\n"
+               "     --profile=name|PATH     Include the specified profile in the\n"
+               "                             security review of the unit(s)\n"
                "  -h --help                  Show this help\n"
                "     --version               Show package version\n"
                "  -q --quiet                 Do not emit hints\n"
@@ -2536,6 +2541,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_THRESHOLD,
                 ARG_SECURITY_POLICY,
                 ARG_JSON,
+                ARG_PROFILE,
         };
 
         static const struct option options[] = {
@@ -2565,6 +2571,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "base-time",        required_argument, NULL, ARG_BASE_TIME        },
                 { "unit",             required_argument, NULL, 'U'                  },
                 { "json",             required_argument, NULL, ARG_JSON             },
+                { "profile",          required_argument, NULL, ARG_PROFILE          },
                 {}
         };
 
@@ -2713,6 +2720,24 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case ARG_PROFILE:
+                        if (isempty(optarg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Profile file name is empty");
+
+                        if (is_path(optarg)) {
+                                r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_profile);
+                                if (r < 0)
+                                        return r;
+                                if (!endswith(arg_profile, ".conf"))
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Profile file name must end with .conf: %s", arg_profile);
+                        } else {
+                                r = free_and_strdup(&arg_profile, optarg);
+                                if (r < 0)
+                                        return log_oom();
+                        }
+
+                        break;
+
                 case 'U': {
                         _cleanup_free_ char *mangled = NULL;
 
index f98a933cb14b0ec2f1fb03911cd90cec4b0e983c..245f74c5d93fe043b16e9697f56361c33642d39c 100755 (executable)
@@ -573,7 +573,20 @@ systemd-analyze security --threshold=90 --offline=true \
                            --security-policy=/tmp/testfile.json \
                            --root=/tmp/img/ testfile.service
 
+# The strict profile adds a lot of sanboxing options
+systemd-analyze security --threshold=20 --offline=true \
+                           --security-policy=/tmp/testfile.json \
+                           --profile=strict \
+                           --root=/tmp/img/ testfile.service
+
 set +e
+# The trusted profile doesn't add any sanboxing options
+systemd-analyze security --threshold=20 --offline=true \
+                           --security-policy=/tmp/testfile.json \
+                           --profile=/usr/lib/systemd/portable/profile/trusted/service.conf \
+                           --root=/tmp/img/ testfile.service \
+    && { echo 'unexpected success'; exit 1; }
+
 systemd-analyze security --threshold=50 --offline=true \
                            --security-policy=/tmp/testfile.json \
                            --root=/tmp/img/ testfile.service \