]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/analyze/analyze-security.c
strv: make iterator in STRV_FOREACH() declaread in the loop
[thirdparty/systemd.git] / src / analyze / analyze-security.c
index 24500e3a5b9fd1af8ecf64216e33300db8c1f189..458b681143a12a7b3b81de499f8fd9adfedb8f6e 100644 (file)
@@ -3,20 +3,25 @@
 #include <sys/utsname.h>
 
 #include "af-list.h"
+#include "analyze.h"
 #include "analyze-security.h"
 #include "analyze-verify.h"
 #include "bus-error.h"
 #include "bus-map-properties.h"
 #include "bus-unit-util.h"
 #include "bus-util.h"
+#include "copy.h"
 #include "env-util.h"
+#include "fd-util.h"
+#include "fileio.h"
 #include "format-table.h"
-#include "in-addr-util.h"
+#include "in-addr-prefix-util.h"
 #include "locale-util.h"
 #include "macro.h"
 #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"
@@ -107,6 +112,7 @@ typedef struct SecurityInfo {
 
 struct security_assessor {
         const char *id;
+        const char *json_field;
         const char *description_good;
         const char *description_bad;
         const char *description_na;
@@ -754,6 +760,7 @@ static int assess_ambient_capabilities(
 static const struct security_assessor security_assessor_table[] = {
         {
                 .id = "User=/DynamicUser=",
+                .json_field = "UserOrDynamicUser",
                 .description_bad = "Service runs as root user",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#User=",
                 .weight = 2000,
@@ -762,6 +769,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "SupplementaryGroups=",
+                .json_field = "SupplementaryGroups",
                 .description_good = "Service has no supplementary groups",
                 .description_bad = "Service runs with supplementary groups",
                 .description_na = "Service runs as root, option does not matter",
@@ -772,6 +780,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "PrivateDevices=",
+                .json_field = "PrivateDevices",
                 .description_good = "Service has no access to hardware devices",
                 .description_bad = "Service potentially has access to hardware devices",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateDevices=",
@@ -782,6 +791,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "PrivateMounts=",
+                .json_field = "PrivateMounts",
                 .description_good = "Service cannot install system mounts",
                 .description_bad = "Service may install system mounts",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateMounts=",
@@ -792,6 +802,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "PrivateNetwork=",
+                .json_field = "PrivateNetwork",
                 .description_good = "Service has no access to the host's network",
                 .description_bad = "Service has access to the host's network",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateNetwork=",
@@ -802,6 +813,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "PrivateTmp=",
+                .json_field = "PrivateTmp",
                 .description_good = "Service has no access to other software's temporary files",
                 .description_bad = "Service has access to other software's temporary files",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateTmp=",
@@ -813,6 +825,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "PrivateUsers=",
+                .json_field = "PrivateUsers",
                 .description_good = "Service does not have access to other users",
                 .description_bad = "Service has access to other users",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateUsers=",
@@ -823,6 +836,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "ProtectControlGroups=",
+                .json_field = "ProtectControlGroups",
                 .description_good = "Service cannot modify the control group file system",
                 .description_bad = "Service may modify the control group file system",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectControlGroups=",
@@ -833,6 +847,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "ProtectKernelModules=",
+                .json_field = "ProtectKernelModules",
                 .description_good = "Service cannot load or read kernel modules",
                 .description_bad = "Service may load or read kernel modules",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectKernelModules=",
@@ -843,6 +858,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "ProtectKernelTunables=",
+                .json_field = "ProtectKernelTunables",
                 .description_good = "Service cannot alter kernel tunables (/proc/sys, …)",
                 .description_bad = "Service may alter kernel tunables",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectKernelTunables=",
@@ -853,6 +869,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "ProtectKernelLogs=",
+                .json_field = "ProtectKernelLogs",
                 .description_good = "Service cannot read from or write to the kernel log ring buffer",
                 .description_bad = "Service may read from or write to the kernel log ring buffer",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectKernelLogs=",
@@ -863,6 +880,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "ProtectClock=",
+                .json_field = "ProtectClock",
                 .description_good = "Service cannot write to the hardware clock or system clock",
                 .description_bad = "Service may write to the hardware clock or system clock",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectClock=",
@@ -873,6 +891,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "ProtectHome=",
+                .json_field = "ProtectHome",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectHome=",
                 .weight = 1000,
                 .range = 10,
@@ -881,6 +900,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "ProtectHostname=",
+                .json_field = "ProtectHostname",
                 .description_good = "Service cannot change system host/domainname",
                 .description_bad = "Service may change system host/domainname",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectHostname=",
@@ -891,6 +911,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "ProtectSystem=",
+                .json_field = "ProtectSystem",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectSystem=",
                 .weight = 1000,
                 .range = 10,
@@ -899,6 +920,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "RootDirectory=/RootImage=",
+                .json_field = "RootDirectoryOrRootImage",
                 .description_good = "Service has its own root directory/image",
                 .description_bad = "Service runs within the host's root directory",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RootDirectory=",
@@ -909,6 +931,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "LockPersonality=",
+                .json_field = "LockPersonality",
                 .description_good = "Service cannot change ABI personality",
                 .description_bad = "Service may change ABI personality",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#LockPersonality=",
@@ -919,6 +942,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "MemoryDenyWriteExecute=",
+                .json_field = "MemoryDenyWriteExecute",
                 .description_good = "Service cannot create writable executable memory mappings",
                 .description_bad = "Service may create writable executable memory mappings",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#MemoryDenyWriteExecute=",
@@ -929,6 +953,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "NoNewPrivileges=",
+                .json_field = "NoNewPrivileges",
                 .description_good = "Service processes cannot acquire new privileges",
                 .description_bad = "Service processes may acquire new privileges",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#NoNewPrivileges=",
@@ -939,6 +964,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_SYS_ADMIN",
+                .json_field = "CapabilityBoundingSet_CAP_SYS_ADMIN",
                 .description_good = "Service has no administrator privileges",
                 .description_bad = "Service has administrator privileges",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -949,6 +975,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_SET(UID|GID|PCAP)",
+                .json_field = "CapabilityBoundingSet_CAP_SET_UID_GID_PCAP",
                 .description_good = "Service cannot change UID/GID identities/capabilities",
                 .description_bad = "Service may change UID/GID identities/capabilities",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -961,6 +988,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_SYS_PTRACE",
+                .json_field = "CapabilityBoundingSet_CAP_SYS_PTRACE",
                 .description_good = "Service has no ptrace() debugging abilities",
                 .description_bad = "Service has ptrace() debugging abilities",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -971,6 +999,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_SYS_TIME",
+                .json_field = "CapabilityBoundingSet_CAP_SYS_TIME",
                 .description_good = "Service processes cannot change the system clock",
                 .description_bad = "Service processes may change the system clock",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -981,6 +1010,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_NET_ADMIN",
+                .json_field = "CapabilityBoundingSet_CAP_NET_ADMIN",
                 .description_good = "Service has no network configuration privileges",
                 .description_bad = "Service has network configuration privileges",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -991,6 +1021,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_SYS_RAWIO",
+                .json_field = "CapabilityBoundingSet_CAP_SYS_RAWIO",
                 .description_good = "Service has no raw I/O access",
                 .description_bad = "Service has raw I/O access",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1001,6 +1032,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_SYS_MODULE",
+                .json_field = "CapabilityBoundingSet_CAP_SYS_MODULE",
                 .description_good = "Service cannot load kernel modules",
                 .description_bad = "Service may load kernel modules",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1011,6 +1043,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_AUDIT_*",
+                .json_field = "CapabilityBoundingSet_CAP_AUDIT",
                 .description_good = "Service has no audit subsystem access",
                 .description_bad = "Service has audit subsystem access",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1023,6 +1056,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_SYSLOG",
+                .json_field = "CapabilityBoundingSet_CAP_SYSLOG",
                 .description_good = "Service has no access to kernel logging",
                 .description_bad = "Service has access to kernel logging",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1033,6 +1067,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_SYS_(NICE|RESOURCE)",
+                .json_field = "CapabilityBoundingSet_CAP_SYS_NICE_RESOURCE",
                 .description_good = "Service has no privileges to change resource use parameters",
                 .description_bad = "Service has privileges to change resource use parameters",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1044,6 +1079,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_MKNOD",
+                .json_field = "CapabilityBoundingSet_CAP_MKNOD",
                 .description_good = "Service cannot create device nodes",
                 .description_bad = "Service may create device nodes",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1054,6 +1090,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_(CHOWN|FSETID|SETFCAP)",
+                .json_field = "CapabilityBoundingSet_CAP_CHOWN_FSETID_SETFCAP",
                 .description_good = "Service cannot change file ownership/access mode/capabilities",
                 .description_bad = "Service may change file ownership/access mode/capabilities unrestricted",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1066,6 +1103,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_(DAC_*|FOWNER|IPC_OWNER)",
+                .json_field = "CapabilityBoundingSet_CAP_DAC_FOWNER_IPC_OWNER",
                 .description_good = "Service cannot override UNIX file/IPC permission checks",
                 .description_bad = "Service may override UNIX file/IPC permission checks",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1079,6 +1117,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_KILL",
+                .json_field = "CapabilityBoundingSet_CAP_KILL",
                 .description_good = "Service cannot send UNIX signals to arbitrary processes",
                 .description_bad = "Service may send UNIX signals to arbitrary processes",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1089,6 +1128,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_NET_(BIND_SERVICE|BROADCAST|RAW)",
+                .json_field = "CapabilityBoundingSet_CAP_NET_BIND_SERVICE_BROADCAST_RAW)",
                 .description_good = "Service has no elevated networking privileges",
                 .description_bad = "Service has elevated networking privileges",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1101,6 +1141,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_SYS_BOOT",
+                .json_field = "CapabilityBoundingSet_CAP_SYS_BOOT",
                 .description_good = "Service cannot issue reboot()",
                 .description_bad = "Service may issue reboot()",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1111,6 +1152,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_MAC_*",
+                .json_field = "CapabilityBoundingSet_CAP_MAC",
                 .description_good = "Service cannot adjust SMACK MAC",
                 .description_bad = "Service may adjust SMACK MAC",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1122,6 +1164,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_LINUX_IMMUTABLE",
+                .json_field = "CapabilityBoundingSet_CAP_LINUX_IMMUTABLE",
                 .description_good = "Service cannot mark files immutable",
                 .description_bad = "Service may mark files immutable",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1132,6 +1175,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_IPC_LOCK",
+                .json_field = "CapabilityBoundingSet_CAP_IPC_LOCK",
                 .description_good = "Service cannot lock memory into RAM",
                 .description_bad = "Service may lock memory into RAM",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1142,6 +1186,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_SYS_CHROOT",
+                .json_field = "CapabilityBoundingSet_CAP_SYS_CHROOT",
                 .description_good = "Service cannot issue chroot()",
                 .description_bad = "Service may issue chroot()",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1152,6 +1197,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_BLOCK_SUSPEND",
+                .json_field = "CapabilityBoundingSet_CAP_BLOCK_SUSPEND",
                 .description_good = "Service cannot establish wake locks",
                 .description_bad = "Service may establish wake locks",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1162,6 +1208,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_WAKE_ALARM",
+                .json_field = "CapabilityBoundingSet_CAP_WAKE_ALARM",
                 .description_good = "Service cannot program timers that wake up the system",
                 .description_bad = "Service may program timers that wake up the system",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1172,6 +1219,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_LEASE",
+                .json_field = "CapabilityBoundingSet_CAP_LEASE",
                 .description_good = "Service cannot create file leases",
                 .description_bad = "Service may create file leases",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1182,6 +1230,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG",
+                .json_field = "CapabilityBoundingSet_CAP_SYS_TTY_CONFIG",
                 .description_good = "Service cannot issue vhangup()",
                 .description_bad = "Service may issue vhangup()",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1192,6 +1241,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "CapabilityBoundingSet=~CAP_SYS_PACCT",
+                .json_field = "CapabilityBoundingSet_CAP_SYS_PACCT",
                 .description_good = "Service cannot use acct()",
                 .description_bad = "Service may use acct()",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
@@ -1202,6 +1252,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "UMask=",
+                .json_field = "UMask",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#UMask=",
                 .weight = 100,
                 .range = 10,
@@ -1209,6 +1260,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "KeyringMode=",
+                .json_field = "KeyringMode",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#KeyringMode=",
                 .description_good = "Service doesn't share key material with other services",
                 .description_bad = "Service shares key material with other service",
@@ -1218,6 +1270,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "ProtectProc=",
+                .json_field = "ProtectProc",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectProc=",
                 .description_good = "Service has restricted access to process tree (/proc hidepid=)",
                 .description_bad = "Service has full access to process tree (/proc hidepid=)",
@@ -1227,6 +1280,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "ProcSubset=",
+                .json_field = "ProcSubset",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProcSubset=",
                 .description_good = "Service has no access to non-process /proc files (/proc subset=)",
                 .description_bad = "Service has full access to non-process /proc files (/proc subset=)",
@@ -1236,6 +1290,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "NotifyAccess=",
+                .json_field = "NotifyAccess",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#NotifyAccess=",
                 .description_good = "Service child processes cannot alter service state",
                 .description_bad = "Service child processes may alter service state",
@@ -1245,6 +1300,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "RemoveIPC=",
+                .json_field = "RemoveIPC",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RemoveIPC=",
                 .description_good = "Service user cannot leave SysV IPC objects around",
                 .description_bad = "Service user may leave SysV IPC objects around",
@@ -1256,6 +1312,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "Delegate=",
+                .json_field = "Delegate",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Delegate=",
                 .description_good = "Service does not maintain its own delegated control group subtree",
                 .description_bad = "Service maintains its own delegated control group subtree",
@@ -1267,6 +1324,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "RestrictRealtime=",
+                .json_field = "RestrictRealtime",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictRealtime=",
                 .description_good = "Service realtime scheduling access is restricted",
                 .description_bad = "Service may acquire realtime scheduling",
@@ -1277,6 +1335,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "RestrictSUIDSGID=",
+                .json_field = "RestrictSUIDSGID",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictSUIDSGID=",
                 .description_good = "SUID/SGID file creation by service is restricted",
                 .description_bad = "Service may create SUID/SGID files",
@@ -1286,7 +1345,8 @@ static const struct security_assessor security_assessor_table[] = {
                 .offset = offsetof(SecurityInfo, restrict_suid_sgid),
         },
         {
-                .id = "RestrictNamespaces=~CLONE_NEWUSER",
+                .id = "RestrictNamespaces=~user",
+                .json_field = "RestrictNamespaces_user",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
                 .description_good = "Service cannot create user namespaces",
                 .description_bad = "Service may create user namespaces",
@@ -1296,7 +1356,8 @@ static const struct security_assessor security_assessor_table[] = {
                 .parameter = CLONE_NEWUSER,
         },
         {
-                .id = "RestrictNamespaces=~CLONE_NEWNS",
+                .id = "RestrictNamespaces=~mnt",
+                .json_field = "RestrictNamespaces_mnt",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
                 .description_good = "Service cannot create file system namespaces",
                 .description_bad = "Service may create file system namespaces",
@@ -1306,7 +1367,8 @@ static const struct security_assessor security_assessor_table[] = {
                 .parameter = CLONE_NEWNS,
         },
         {
-                .id = "RestrictNamespaces=~CLONE_NEWIPC",
+                .id = "RestrictNamespaces=~ipc",
+                .json_field = "RestrictNamespaces_ipc",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
                 .description_good = "Service cannot create IPC namespaces",
                 .description_bad = "Service may create IPC namespaces",
@@ -1316,7 +1378,8 @@ static const struct security_assessor security_assessor_table[] = {
                 .parameter = CLONE_NEWIPC,
         },
         {
-                .id = "RestrictNamespaces=~CLONE_NEWPID",
+                .id = "RestrictNamespaces=~pid",
+                .json_field = "RestrictNamespaces_pid",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
                 .description_good = "Service cannot create process namespaces",
                 .description_bad = "Service may create process namespaces",
@@ -1326,7 +1389,8 @@ static const struct security_assessor security_assessor_table[] = {
                 .parameter = CLONE_NEWPID,
         },
         {
-                .id = "RestrictNamespaces=~CLONE_NEWCGROUP",
+                .id = "RestrictNamespaces=~cgroup",
+                .json_field = "RestrictNamespaces_cgroup",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
                 .description_good = "Service cannot create cgroup namespaces",
                 .description_bad = "Service may create cgroup namespaces",
@@ -1336,7 +1400,8 @@ static const struct security_assessor security_assessor_table[] = {
                 .parameter = CLONE_NEWCGROUP,
         },
         {
-                .id = "RestrictNamespaces=~CLONE_NEWNET",
+                .id = "RestrictNamespaces=~net",
+                .json_field = "RestrictNamespaces_net",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
                 .description_good = "Service cannot create network namespaces",
                 .description_bad = "Service may create network namespaces",
@@ -1346,7 +1411,8 @@ static const struct security_assessor security_assessor_table[] = {
                 .parameter = CLONE_NEWNET,
         },
         {
-                .id = "RestrictNamespaces=~CLONE_NEWUTS",
+                .id = "RestrictNamespaces=~uts",
+                .json_field = "RestrictNamespaces_uts",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
                 .description_good = "Service cannot create hostname namespaces",
                 .description_bad = "Service may create hostname namespaces",
@@ -1357,6 +1423,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "RestrictAddressFamilies=~AF_(INET|INET6)",
+                .json_field = "RestrictAddressFamilies_AF_INET_INET6",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
                 .description_good = "Service cannot allocate Internet sockets",
                 .description_bad = "Service may allocate Internet sockets",
@@ -1367,6 +1434,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "RestrictAddressFamilies=~AF_UNIX",
+                .json_field = "RestrictAddressFamilies_AF_UNIX",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
                 .description_good = "Service cannot allocate local sockets",
                 .description_bad = "Service may allocate local sockets",
@@ -1377,6 +1445,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "RestrictAddressFamilies=~AF_NETLINK",
+                .json_field = "RestrictAddressFamilies_AF_NETLINK",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
                 .description_good = "Service cannot allocate netlink sockets",
                 .description_bad = "Service may allocate netlink sockets",
@@ -1387,6 +1456,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "RestrictAddressFamilies=~AF_PACKET",
+                .json_field = "RestrictAddressFamilies_AF_PACKET",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
                 .description_good = "Service cannot allocate packet sockets",
                 .description_bad = "Service may allocate packet sockets",
@@ -1397,6 +1467,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "RestrictAddressFamilies=~…",
+                .json_field = "RestrictAddressFamilies_OTHER",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
                 .description_good = "Service cannot allocate exotic sockets",
                 .description_bad = "Service may allocate exotic sockets",
@@ -1407,6 +1478,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "SystemCallArchitectures=",
+                .json_field = "SystemCallArchitectures",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallArchitectures=",
                 .weight = 1000,
                 .range = 10,
@@ -1415,6 +1487,7 @@ static const struct security_assessor security_assessor_table[] = {
 #if HAVE_SECCOMP
         {
                 .id = "SystemCallFilter=~@swap",
+                .json_field = "SystemCallFilter_swap",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
                 .weight = 1000,
                 .range = 10,
@@ -1423,6 +1496,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "SystemCallFilter=~@obsolete",
+                .json_field = "SystemCallFilter_obsolete",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
                 .weight = 250,
                 .range = 10,
@@ -1431,6 +1505,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "SystemCallFilter=~@clock",
+                .json_field = "SystemCallFilter_clock",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
                 .weight = 1000,
                 .range = 10,
@@ -1439,6 +1514,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "SystemCallFilter=~@cpu-emulation",
+                .json_field = "SystemCallFilter_cpu_emulation",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
                 .weight = 250,
                 .range = 10,
@@ -1447,6 +1523,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "SystemCallFilter=~@debug",
+                .json_field = "SystemCallFilter_debug",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
                 .weight = 1000,
                 .range = 10,
@@ -1455,6 +1532,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "SystemCallFilter=~@mount",
+                .json_field = "SystemCallFilter_mount",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
                 .weight = 1000,
                 .range = 10,
@@ -1463,6 +1541,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "SystemCallFilter=~@module",
+                .json_field = "SystemCallFilter_module",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
                 .weight = 1000,
                 .range = 10,
@@ -1471,6 +1550,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "SystemCallFilter=~@raw-io",
+                .json_field = "SystemCallFilter_raw_io",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
                 .weight = 1000,
                 .range = 10,
@@ -1479,6 +1559,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "SystemCallFilter=~@reboot",
+                .json_field = "SystemCallFilter_reboot",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
                 .weight = 1000,
                 .range = 10,
@@ -1487,6 +1568,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "SystemCallFilter=~@privileged",
+                .json_field = "SystemCallFilter_privileged",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
                 .weight = 700,
                 .range = 10,
@@ -1495,6 +1577,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "SystemCallFilter=~@resources",
+                .json_field = "SystemCallFilter_resources",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
                 .weight = 700,
                 .range = 10,
@@ -1504,6 +1587,7 @@ static const struct security_assessor security_assessor_table[] = {
 #endif
         {
                 .id = "IPAddressDeny=",
+                .json_field = "IPAddressDeny",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#IPAddressDeny=",
                 .weight = 1000,
                 .range = 10,
@@ -1511,6 +1595,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "DeviceAllow=",
+                .json_field = "DeviceAllow",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#DeviceAllow=",
                 .weight = 1000,
                 .range = 10,
@@ -1518,6 +1603,7 @@ static const struct security_assessor security_assessor_table[] = {
         },
         {
                 .id = "AmbientCapabilities=",
+                .json_field = "AmbientCapabilities",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#AmbientCapabilities=",
                 .description_good = "Service process does not receive ambient capabilities",
                 .description_bad = "Service process receives ambient capabilities",
@@ -1527,7 +1613,111 @@ static const struct security_assessor security_assessor_table[] = {
         },
 };
 
-static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecurityFlags flags, unsigned threshold) {
+static JsonVariant* security_assessor_find_in_policy(const struct security_assessor *a, JsonVariant *policy, const char *name) {
+        JsonVariant *item;
+        assert(a);
+
+        if (!policy)
+                return NULL;
+        if (!json_variant_is_object(policy)) {
+                log_debug("Specified policy is not a JSON object, ignoring.");
+                return NULL;
+        }
+
+        item = json_variant_by_key(policy, a->json_field);
+        if (!item)
+                return NULL;
+        if (!json_variant_is_object(item)) {
+                log_debug("Item for '%s' in policy JSON object is not an object, ignoring.", a->id);
+                return NULL;
+        }
+
+        return name ? json_variant_by_key(item, name) : item;
+}
+
+static uint64_t access_weight(const struct security_assessor *a, JsonVariant *policy) {
+        JsonVariant *val;
+
+        assert(a);
+
+        val = security_assessor_find_in_policy(a, policy, "weight");
+        if  (val) {
+                if (json_variant_is_unsigned(val))
+                        return json_variant_unsigned(val);
+                log_debug("JSON field 'weight' of policy for %s is not an unsigned integer, ignoring.", a->id);
+        }
+
+        return a->weight;
+}
+
+static uint64_t access_range(const struct security_assessor *a, JsonVariant *policy) {
+        JsonVariant *val;
+
+        assert(a);
+
+        val = security_assessor_find_in_policy(a, policy, "range");
+        if  (val) {
+                if (json_variant_is_unsigned(val))
+                        return json_variant_unsigned(val);
+                log_debug("JSON field 'range' of policy for %s is not an unsigned integer, ignoring.", a->id);
+        }
+
+        return a->range;
+}
+
+static const char *access_description_na(const struct security_assessor *a, JsonVariant *policy) {
+        JsonVariant *val;
+
+        assert(a);
+
+        val = security_assessor_find_in_policy(a, policy, "description_na");
+        if  (val) {
+                if (json_variant_is_string(val))
+                        return json_variant_string(val);
+                log_debug("JSON field 'description_na' of policy for %s is not a string, ignoring.", a->id);
+        }
+
+        return a->description_na;
+}
+
+static const char *access_description_good(const struct security_assessor *a, JsonVariant *policy) {
+        JsonVariant *val;
+
+        assert(a);
+
+        val = security_assessor_find_in_policy(a, policy, "description_good");
+        if  (val) {
+                if (json_variant_is_string(val))
+                        return json_variant_string(val);
+                log_debug("JSON field 'description_good' of policy for %s is not a string, ignoring.", a->id);
+        }
+
+        return a->description_good;
+}
+
+static const char *access_description_bad(const struct security_assessor *a, JsonVariant *policy) {
+        JsonVariant *val;
+
+        assert(a);
+
+        val = security_assessor_find_in_policy(a, policy, "description_bad");
+        if  (val) {
+                if (json_variant_is_string(val))
+                        return json_variant_string(val);
+                log_debug("JSON field 'description_bad' of policy for %s is not a string, ignoring.", a->id);
+        }
+
+        return a->description_bad;
+}
+
+static int assess(const SecurityInfo *info,
+                  Table *overview_table,
+                  AnalyzeSecurityFlags flags,
+                  unsigned threshold,
+                  JsonVariant *policy,
+                  PagerFlags pager_flags,
+                  JsonFormatFlags json_format_flags) {
+
         static const struct {
                 uint64_t exposure;
                 const char *name;
@@ -1549,15 +1739,19 @@ static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecuri
         int r;
 
         if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT)) {
-                details_table = table_new(" ", "name", "description", "weight", "badness", "range", "exposure");
+                details_table = table_new(" ", "name", "json_field", "description", "weight", "badness", "range", "exposure");
                 if (!details_table)
                         return log_oom();
 
+                r = table_set_json_field_name(details_table, 0, "set");
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set JSON field name of column 0: %m");
+
                 (void) table_set_sort(details_table, (size_t) 3, (size_t) 1);
                 (void) table_set_reverse(details_table, 3, true);
 
                 if (getenv_bool("SYSTEMD_ANALYZE_DEBUG") <= 0)
-                        (void) table_set_display(details_table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 6);
+                        (void) table_set_display(details_table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 7);
         }
 
         for (i = 0; i < ELEMENTSOF(security_assessor_table); i++) {
@@ -1565,6 +1759,8 @@ static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecuri
                 _cleanup_free_ char *d = NULL;
                 uint64_t badness;
                 void *data;
+                uint64_t weight = access_weight(a, policy);
+                uint64_t range = access_range(a, policy);
 
                 data = (uint8_t *) info + a->offset;
 
@@ -1573,38 +1769,44 @@ static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecuri
                         d = strdup("Service runs in special boot phase, option is not appropriate");
                         if (!d)
                                 return log_oom();
+                } else if (weight == 0) {
+                        badness = UINT64_MAX;
+                        d = strdup("Option excluded by policy, skipping");
+                        if (!d)
+                                return log_oom();
                 } else {
                         r = a->assess(a, info, data, &badness, &d);
                         if (r < 0)
                                 return r;
                 }
 
-                assert(a->range > 0);
+                assert(range > 0);
 
                 if (badness != UINT64_MAX) {
-                        assert(badness <= a->range);
+                        assert(badness <= range);
 
-                        badness_sum += DIV_ROUND_UP(badness * a->weight, a->range);
-                        weight_sum += a->weight;
+                        badness_sum += DIV_ROUND_UP(badness * weight, range);
+                        weight_sum += weight;
                 }
 
                 if (details_table) {
-                        const char *checkmark, *description, *color = NULL;
+                        const char *description, *color = NULL;
+                        int checkmark;
 
                         if (badness == UINT64_MAX) {
-                                checkmark = " ";
-                                description = a->description_na;
+                                checkmark = -1;
+                                description = access_description_na(a, policy);
                                 color = NULL;
                         } else if (badness == a->range) {
-                                checkmark = special_glyph(SPECIAL_GLYPH_CROSS_MARK);
-                                description = a->description_bad;
+                                checkmark = 0;
+                                description = access_description_bad(a, policy);
                                 color = ansi_highlight_red();
                         } else if (badness == 0) {
-                                checkmark = special_glyph(SPECIAL_GLYPH_CHECK_MARK);
-                                description = a->description_good;
+                                checkmark = 1;
+                                description = access_description_good(a, policy);
                                 color = ansi_highlight_green();
                         } else {
-                                checkmark = special_glyph(SPECIAL_GLYPH_CROSS_MARK);
+                                checkmark = 0;
                                 description = NULL;
                                 color = ansi_highlight_red();
                         }
@@ -1612,17 +1814,28 @@ static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecuri
                         if (d)
                                 description = d;
 
+                        if (checkmark < 0) {
+                                r = table_add_many(details_table, TABLE_EMPTY);
+                                if (r < 0)
+                                        return table_log_add_error(r);
+                        } else {
+                                r = table_add_many(details_table,
+                                                   TABLE_BOOLEAN_CHECKMARK, checkmark > 0,
+                                                   TABLE_SET_MINIMUM_WIDTH, 1,
+                                                   TABLE_SET_MAXIMUM_WIDTH, 1,
+                                                   TABLE_SET_ELLIPSIZE_PERCENT, 0,
+                                                   TABLE_SET_COLOR, color);
+                                if (r < 0)
+                                        return table_log_add_error(r);
+                        }
+
                         r = table_add_many(details_table,
-                                           TABLE_STRING, checkmark,
-                                           TABLE_SET_MINIMUM_WIDTH, 1,
-                                           TABLE_SET_MAXIMUM_WIDTH, 1,
-                                           TABLE_SET_ELLIPSIZE_PERCENT, 0,
-                                           TABLE_SET_COLOR, color,
                                            TABLE_STRING, a->id, TABLE_SET_URL, a->url,
+                                           TABLE_STRING, a->json_field,
                                            TABLE_STRING, description,
-                                           TABLE_UINT64, a->weight, TABLE_SET_ALIGN_PERCENT, 100,
+                                           TABLE_UINT64, weight, TABLE_SET_ALIGN_PERCENT, 100,
                                            TABLE_UINT64, badness, TABLE_SET_ALIGN_PERCENT, 100,
-                                           TABLE_UINT64, a->range, TABLE_SET_ALIGN_PERCENT, 100,
+                                           TABLE_UINT64, range, TABLE_SET_ALIGN_PERCENT, 100,
                                            TABLE_EMPTY, TABLE_SET_ALIGN_PERCENT, 100);
                         if (r < 0)
                                 return table_log_add_error(r);
@@ -1640,14 +1853,14 @@ static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecuri
                         TableCell *cell;
                         uint64_t x;
 
-                        assert_se(weight = table_get_at(details_table, row, 3));
-                        assert_se(badness = table_get_at(details_table, row, 4));
-                        assert_se(range = table_get_at(details_table, row, 5));
+                        assert_se(weight = table_get_at(details_table, row, 4));
+                        assert_se(badness = table_get_at(details_table, row, 5));
+                        assert_se(range = table_get_at(details_table, row, 6));
 
                         if (*badness == UINT64_MAX || *badness == 0)
                                 continue;
 
-                        assert_se(cell = table_get_cell(details_table, row, 6));
+                        assert_se(cell = table_get_cell(details_table, row, 7));
 
                         x = DIV_ROUND_UP(DIV_ROUND_UP(*badness * *weight * 100U, *range), weight_sum);
                         xsprintf(buf, "%" PRIu64 ".%" PRIu64, x / 10, x % 10);
@@ -1657,7 +1870,13 @@ static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecuri
                                 return log_error_errno(r, "Failed to update cell in table: %m");
                 }
 
-                r = table_print(details_table, stdout);
+                if (json_format_flags & JSON_FORMAT_OFF) {
+                        r = table_hide_column_from_display(details_table, (size_t) 2);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set columns to display: %m");
+                }
+
+                r = table_print_with_pager(details_table, json_format_flags, pager_flags, /* show_header= */true);
                 if (r < 0)
                         return log_error_errno(r, "Failed to output table: %m");
         }
@@ -1670,7 +1889,7 @@ static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecuri
 
         assert(i < ELEMENTSOF(badness_table));
 
-        if (details_table) {
+        if (details_table && (json_format_flags & JSON_FORMAT_OFF)) {
                 _cleanup_free_ char *clickable = NULL;
                 const char *name;
 
@@ -1686,7 +1905,7 @@ static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecuri
                         name = info->id;
 
                 printf("\n%s %sOverall exposure level for %s%s: %s%" PRIu64 ".%" PRIu64 " %s%s %s\n",
-                       special_glyph(SPECIAL_GLYPH_ARROW),
+                       special_glyph(SPECIAL_GLYPH_ARROW_RIGHT),
                        ansi_highlight(),
                        name,
                        ansi_normal(),
@@ -2192,8 +2411,14 @@ static int acquire_security_info(sd_bus *bus, const char *name, SecurityInfo *in
         return 0;
 }
 
-static int analyze_security_one(sd_bus *bus, const char *name, Table *overview_table,
-                                AnalyzeSecurityFlags flags, unsigned threshold) {
+static int analyze_security_one(sd_bus *bus,
+                                const char *name,
+                                Table *overview_table,
+                                AnalyzeSecurityFlags flags,
+                                unsigned threshold,
+                                JsonVariant *policy,
+                                PagerFlags pager_flags,
+                                JsonFormatFlags json_format_flags) {
 
         _cleanup_(security_info_freep) SecurityInfo *info = security_info_new();
         if (!info)
@@ -2210,7 +2435,7 @@ static int analyze_security_one(sd_bus *bus, const char *name, Table *overview_t
         if (r < 0)
                 return r;
 
-        r = assess(info, overview_table, flags, threshold);
+        r = assess(info, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
         if (r < 0)
                 return r;
 
@@ -2367,10 +2592,10 @@ static int get_security_info(Unit *u, ExecContext *c, CGroupContext *g, Security
                                 return log_oom();
                 }
 
-                IPAddressAccessItem *i;
+                struct in_addr_prefix *i;
                 bool deny_ipv4 = false, deny_ipv6 = false;
 
-                LIST_FOREACH(items, i, g->ip_address_deny) {
+                SET_FOREACH(i, g->ip_address_deny) {
                         if (i->family == AF_INET && i->prefixlen == 0)
                                 deny_ipv4 = true;
                         else if (i->family == AF_INET6 && i->prefixlen == 0)
@@ -2379,7 +2604,7 @@ static int get_security_info(Unit *u, ExecContext *c, CGroupContext *g, Security
                 info->ip_address_deny_all = deny_ipv4 && deny_ipv6;
 
                 info->ip_address_allow_localhost = info->ip_address_allow_other = false;
-                LIST_FOREACH(items, i, g->ip_address_allow) {
+                SET_FOREACH(i, g->ip_address_allow) {
                         if (in_addr_is_localhost(i->family, &i->address))
                                 info->ip_address_allow_localhost = true;
                         else
@@ -2396,7 +2621,12 @@ static int get_security_info(Unit *u, ExecContext *c, CGroupContext *g, Security
         return 0;
 }
 
-static int offline_security_check(Unit *u, unsigned threshold) {
+static int offline_security_check(Unit *u,
+                                  unsigned threshold,
+                                  JsonVariant *policy,
+                                  PagerFlags pager_flags,
+                                  JsonFormatFlags json_format_flags) {
+
         _cleanup_(table_unrefp) Table *overview_table = NULL;
         AnalyzeSecurityFlags flags = 0;
         _cleanup_(security_info_freep) SecurityInfo *info = NULL;
@@ -2411,13 +2641,24 @@ static int offline_security_check(Unit *u, unsigned threshold) {
         if (r < 0)
               return r;
 
-        return assess(info, overview_table, flags, threshold);
+        return assess(info, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
 }
 
-static int offline_security_checks(char **filenames, UnitFileScope scope, bool check_man, bool run_generators, unsigned threshold, const char *root) {
+static int offline_security_checks(char **filenames,
+                                   JsonVariant *policy,
+                                   UnitFileScope scope,
+                                   bool check_man,
+                                   bool run_generators,
+                                   unsigned threshold,
+                                   const char *root,
+                                   const char *profile,
+                                   PagerFlags pager_flags,
+                                   JsonFormatFlags json_format_flags) {
+
         const ManagerTestRunFlags flags =
                 MANAGER_TEST_RUN_MINIMAL |
                 MANAGER_TEST_RUN_ENV_GENERATORS |
+                MANAGER_TEST_RUN_IGNORE_DEPENDENCIES |
                 run_generators * MANAGER_TEST_RUN_GENERATORS;
 
         _cleanup_(manager_freep) Manager *m = NULL;
@@ -2425,7 +2666,6 @@ static int offline_security_checks(char **filenames, UnitFileScope scope, bool c
         _cleanup_free_ char *var = NULL;
         int r, k;
         size_t count = 0;
-        char **filename;
 
         if (strv_isempty(filenames))
                 return 0;
@@ -2447,6 +2687,13 @@ static int offline_security_checks(char **filenames, UnitFileScope scope, bool c
         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) {
@@ -2462,6 +2709,33 @@ static int offline_security_checks(char **filenames, UnitFileScope scope, bool c
                         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)
@@ -2473,7 +2747,7 @@ static int offline_security_checks(char **filenames, UnitFileScope scope, bool c
         }
 
         for (size_t i = 0; i < count; i++) {
-                k = offline_security_check(units[i], threshold);
+                k = offline_security_check(units[i], threshold, policy, pager_flags, json_format_flags);
                 if (k < 0 && r == 0)
                         r = k;
         }
@@ -2481,16 +2755,27 @@ static int offline_security_checks(char **filenames, UnitFileScope scope, bool c
         return r;
 }
 
-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 int analyze_security(sd_bus *bus,
+                     char **units,
+                     JsonVariant *policy,
+                     UnitFileScope scope,
+                     bool check_man,
+                     bool run_generators,
+                     bool offline,
+                     unsigned threshold,
+                     const char *root,
+                     const char *profile,
+                     JsonFormatFlags json_format_flags,
+                     PagerFlags pager_flags,
+                     AnalyzeSecurityFlags flags) {
 
         _cleanup_(table_unrefp) Table *overview_table = NULL;
         int ret = 0, r;
 
-        assert(bus);
+        assert(!!bus != offline);
 
         if (offline)
-                return offline_security_checks(units, scope, check_man, run_generators, threshold, root);
+                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");
@@ -2503,7 +2788,6 @@ int analyze_security(sd_bus *bus, char **units, UnitFileScope scope, bool check_
                 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
                 _cleanup_strv_free_ char **list = NULL;
                 size_t n = 0;
-                char **i;
 
                 r = sd_bus_call_method(
                                 bus,
@@ -2550,14 +2834,12 @@ int analyze_security(sd_bus *bus, char **units, UnitFileScope scope, bool check_
                 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, threshold);
+                        r = analyze_security_one(bus, *i, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
                         if (r < 0 && ret >= 0)
                                 ret = r;
                 }
 
-        } else {
-                char **i;
-
+        } else
                 STRV_FOREACH(i, units) {
                         _cleanup_free_ char *mangled = NULL, *instance = NULL;
                         const char *name;
@@ -2585,11 +2867,10 @@ int analyze_security(sd_bus *bus, char **units, UnitFileScope scope, bool check_
                         } else
                                 name = mangled;
 
-                        r = analyze_security_one(bus, name, overview_table, flags, threshold);
+                        r = analyze_security_one(bus, name, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
                         if (r < 0 && ret >= 0)
                                 ret = r;
                 }
-        }
 
         if (overview_table) {
                 if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT)) {
@@ -2597,10 +2878,57 @@ int analyze_security(sd_bus *bus, char **units, UnitFileScope scope, bool check_
                         fflush(stdout);
                 }
 
-                r = table_print(overview_table, stdout);
+                r = table_print_with_pager(overview_table, json_format_flags, pager_flags, /* show_header= */true);
                 if (r < 0)
                         return log_error_errno(r, "Failed to output table: %m");
         }
-
         return ret;
 }
+
+int verb_security(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *policy = NULL;
+        int r;
+        unsigned line, column;
+
+        if (!arg_offline) {
+                r = acquire_bus(&bus, NULL);
+                if (r < 0)
+                        return bus_log_connect_error(r, arg_transport);
+        }
+
+        pager_open(arg_pager_flags);
+
+        if (arg_security_policy) {
+                r = json_parse_file(/*f=*/ NULL, arg_security_policy, /*flags=*/ 0, &policy, &line, &column);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse '%s' at %u:%u: %m", arg_security_policy, line, column);
+        } else {
+                _cleanup_fclose_ FILE *f = NULL;
+                _cleanup_free_ char *pp = NULL;
+
+                r = search_and_fopen_nulstr("systemd-analyze-security.policy", "re", /*root=*/ NULL, CONF_PATHS_NULSTR("systemd"), &f, &pp);
+                if (r < 0 && r != -ENOENT)
+                        return r;
+
+                if (f) {
+                        r = json_parse_file(f, pp, /*flags=*/ 0, &policy, &line, &column);
+                        if (r < 0)
+                                return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON policy: %m", pp, line, column);
+                }
+        }
+
+        return analyze_security(bus,
+                                strv_skip(argv, 1),
+                                policy,
+                                arg_scope,
+                                arg_man,
+                                arg_generators,
+                                arg_offline,
+                                arg_threshold,
+                                arg_root,
+                                arg_profile,
+                                arg_json_format_flags,
+                                arg_pager_flags,
+                                /*flags=*/ 0);
+}