| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
|
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
|
| 23 |
|
| 24 |
|
| 25 |
|
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
|
| 30 |
|
| 31 |
|
| 32 |
|
| 33 |
|
| 34 |
|
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
|
| 51 |
|
| 52 |
|
| 53 |
|
| 54 |
|
| 55 |
|
| 56 |
|
| 57 |
|
| 58 |
|
| 59 |
|
| 60 |
|
| 61 |
|
| 62 |
|
| 63 |
|
| 64 |
|
| 65 |
|
| 66 |
|
| 67 |
|
| 68 |
|
| 69 |
|
| 70 |
|
| 71 |
|
| 72 |
|
| 73 |
|
| 74 |
|
| 75 |
|
| 76 |
|
| 77 |
|
| 78 |
|
| 79 |
|
| 80 |
set -Eeuo pipefail |
| 81 |
|
| 82 |
|
| 83 |
|
| 84 |
|
| 85 |
|
| 86 |
|
| 87 |
|
| 88 |
|
| 89 |
|
| 90 |
|
| 91 |
|
| 92 |
IGNORE='/etc/selinux/[^/ ]+/semanage\.[^/ ]*LOCK' |
| 93 |
|
| 94 |
REPAIR='sudo restorecon -R /etc' |
| 95 |
|
| 96 |
die() { printf 'error: %s\n' "$*" >&2; exit 3; } |
| 97 |
|
| 98 |
trap 'rc=$?; [ "$rc" -eq 1 ] && exit 1; [ "$rc" -eq 3 ] && exit 3; die "unexpected failure at line ${LINENO}"' ERR |
| 99 |
|
| 100 |
|
| 101 |
|
| 102 |
filter_findings() { grep -Ev "$IGNORE" || true; } |
| 103 |
|
| 104 |
|
| 105 |
|
| 106 |
|
| 107 |
self_test() { |
| 108 |
local fails=0 out |
| 109 |
|
| 110 |
out="$(printf '%s\n' \ |
| 111 |
'Would relabel /etc/passwd from unconfined_u:object_r:etc_t:s0 to system_u:object_r:passwd_file_t:s0' \ |
| 112 |
'Would relabel /etc/group from unconfined_u:object_r:etc_t:s0 to system_u:object_r:passwd_file_t:s0' \ |
| 113 |
'Would relabel /etc/.pwd.lock from unconfined_u:object_r:etc_t:s0 to system_u:object_r:passwd_file_t:s0' \ |
| 114 |
'Would relabel /etc/shadow from unconfined_u:object_r:etc_t:s0 to system_u:object_r:shadow_t:s0' \ |
| 115 |
'Would relabel /etc/gshadow from unconfined_u:object_r:etc_t:s0 to system_u:object_r:shadow_t:s0' \ |
| 116 |
'Would relabel /etc/hostname from unconfined_u:object_r:var_run_t:s0 to system_u:object_r:hostname_etc_t:s0' \ |
| 117 |
| filter_findings | wc -l)" |
| 118 |
if [ "$out" -ne 6 ]; then |
| 119 |
printf 'self-test: the six known mislabels should all survive the filter, %s did\n' "$out" >&2 |
| 120 |
fails=$((fails + 1)) |
| 121 |
fi |
| 122 |
|
| 123 |
out="$(printf '%s\n' \ |
| 124 |
'Would relabel /etc/selinux/targeted/semanage.read.LOCK from system_u:object_r:semanage_store_t:s0 to system_u:object_r:selinux_config_t:s0' \ |
| 125 |
'Would relabel /etc/selinux/targeted/semanage.trans.LOCK from system_u:object_r:semanage_store_t:s0 to system_u:object_r:selinux_config_t:s0' \ |
| 126 |
| filter_findings | wc -l)" |
| 127 |
if [ "$out" -ne 0 ]; then |
| 128 |
printf 'self-test: the semanage locks should be filtered, %s survived\n' "$out" >&2 |
| 129 |
fails=$((fails + 1)) |
| 130 |
fi |
| 131 |
|
| 132 |
|
| 133 |
|
| 134 |
|
| 135 |
out="$(printf '%s\n' \ |
| 136 |
'Would relabel /etc/semanage.trans.LOCK from system_u:object_r:etc_t:s0 to system_u:object_r:selinux_config_t:s0' \ |
| 137 |
| filter_findings | wc -l)" |
| 138 |
if [ "$out" -ne 1 ]; then |
| 139 |
printf 'self-test: a lock outside the policy store should survive the filter\n' >&2 |
| 140 |
fails=$((fails + 1)) |
| 141 |
fi |
| 142 |
|
| 143 |
if [ "$fails" -ne 0 ]; then |
| 144 |
printf 'self-test: %s failed\n' "$fails" >&2 |
| 145 |
exit 1 |
| 146 |
fi |
| 147 |
printf 'self-test: the filter is right about all three shapes\n' |
| 148 |
} |
| 149 |
|
| 150 |
|
| 151 |
|
| 152 |
|
| 153 |
|
| 154 |
|
| 155 |
|
| 156 |
|
| 157 |
|
| 158 |
|
| 159 |
|
| 160 |
|
| 161 |
|
| 162 |
|
| 163 |
|
| 164 |
|
| 165 |
|
| 166 |
|
| 167 |
|
| 168 |
|
| 169 |
|
| 170 |
|
| 171 |
|
| 172 |
|
| 173 |
|
| 174 |
POLICY_TABLE=' |
| 175 |
org.freedesktop.NetworkManager.network-control yes |
| 176 |
org.freedesktop.NetworkManager.enable-disable-wifi yes |
| 177 |
org.freedesktop.NetworkManager.enable-disable-network yes |
| 178 |
org.freedesktop.NetworkManager.settings.modify.system yes |
| 179 |
org.freedesktop.timedate1.set-timezone auth_admin_keep |
| 180 |
org.freedesktop.timedate1.set-ntp auth_admin_keep |
| 181 |
org.freedesktop.timedate1.set-time auth_admin_keep |
| 182 |
org.freedesktop.hostname1.set-static-hostname auth_admin_keep |
| 183 |
org.freedesktop.hostname1.set-machine-info auth_admin_keep |
| 184 |
org.freedesktop.locale1.set-locale auth_admin_keep |
| 185 |
org.freedesktop.locale1.set-keyboard auth_admin_keep |
| 186 |
org.projectatomic.rpmostree1.upgrade auth_admin_keep |
| 187 |
org.projectatomic.rpmostree1.rollback auth_admin_keep |
| 188 |
org.projectatomic.rpmostree1.install-uninstall-packages auth_admin_keep |
| 189 |
org.projectatomic.rpmostree1.client-management yes |
| 190 |
org.freedesktop.systemd1.manage-units auth_admin_keep |
| 191 |
' |
| 192 |
|
| 193 |
|
| 194 |
|
| 195 |
|
| 196 |
|
| 197 |
|
| 198 |
|
| 199 |
implicit_active() { |
| 200 |
local out |
| 201 |
out="$(pkaction --action-id "$1" --verbose 2>/dev/null)" || true |
| 202 |
[ -n "$out" ] || { printf 'undefined\n'; return; } |
| 203 |
printf '%s\n' "$out" | awk -F': *' '/implicit active:/ { print $2; found = 1 } |
| 204 |
END { if (!found) print "unreadable" }' |
| 205 |
} |
| 206 |
|
| 207 |
read_policy() { |
| 208 |
command -v pkaction >/dev/null 2>&1 || die "no pkaction: install polkit" |
| 209 |
|
| 210 |
local drift=0 absent=0 action expected actual mark |
| 211 |
while read -r action expected; do |
| 212 |
[ -n "$action" ] || continue |
| 213 |
actual="$(implicit_active "$action")" |
| 214 |
if [ "$actual" = "$expected" ]; then |
| 215 |
mark='ok ' |
| 216 |
elif [ "$actual" = undefined ]; then |
| 217 |
mark='ABSENT ' |
| 218 |
absent=$((absent + 1)) |
| 219 |
else |
| 220 |
mark='DRIFT ' |
| 221 |
drift=$((drift + 1)) |
| 222 |
fi |
| 223 |
printf '%s %-56s %s\n' "$mark" "$action" "$actual" |
| 224 |
done <<< "$POLICY_TABLE" |
| 225 |
|
| 226 |
|
| 227 |
|
| 228 |
|
| 229 |
|
| 230 |
|
| 231 |
if [ "$((drift + absent))" -ne 0 ]; then |
| 232 |
[ "$drift" -eq 0 ] || printf '\n%s action(s) read differently than wiki `alloy-privilege` records.\n' "$drift" |
| 233 |
[ "$absent" -eq 0 ] || printf '\n%s action(s) are not defined on this machine.\n' "$absent" |
| 234 |
printf 'That table decides which actions tier 1 grants, so correct the note before the rules file.\n' |
| 235 |
return 1 |
| 236 |
fi |
| 237 |
printf '\nEvery action reads the way wiki `alloy-privilege` records it.\n' |
| 238 |
} |
| 239 |
|
| 240 |
case "${1:-}" in |
| 241 |
--self-test) self_test; exit 0 ;; |
| 242 |
|
| 243 |
|
| 244 |
--policy) if read_policy; then exit 0; else exit 1; fi ;; |
| 245 |
'') ;; |
| 246 |
*) die "unknown argument: $1" ;; |
| 247 |
esac |
| 248 |
|
| 249 |
[ "$(id -u)" -eq 0 ] || die "run as root: restorecon cannot stat everything under /etc otherwise, and a partial read would pass" |
| 250 |
command -v restorecon >/dev/null 2>&1 || die "no restorecon: install policycoreutils" |
| 251 |
[ -d /sys/fs/selinux ] || die "SELinux is not enforcing on this machine, so there is nothing to compare against" |
| 252 |
|
| 253 |
status=0 |
| 254 |
|
| 255 |
|
| 256 |
|
| 257 |
|
| 258 |
|
| 259 |
findings="$(restorecon -nvR /etc /usr 2>/dev/null | filter_findings)" |
| 260 |
if [ -n "$findings" ]; then |
| 261 |
printf 'mislabelled, %s file(s):\n' "$(printf '%s\n' "$findings" | wc -l)" |
| 262 |
printf '%s\n' "$findings" |
| 263 |
printf '\nrepair: %s\n' "$REPAIR" |
| 264 |
status=1 |
| 265 |
else |
| 266 |
printf 'labels: /etc and /usr match the policy\n' |
| 267 |
fi |
| 268 |
|
| 269 |
|
| 270 |
|
| 271 |
|
| 272 |
if command -v systemd-run >/dev/null 2>&1; then |
| 273 |
if err="$(systemd-run -q --wait --property=DynamicUser=yes /bin/true 2>&1)"; then |
| 274 |
printf 'dynuser: a DynamicUser unit starts\n' |
| 275 |
else |
| 276 |
printf 'dynuser: a DynamicUser unit does NOT start, so rpm-ostreed cannot run and nothing can be layered\n' |
| 277 |
printf '%s\n' "$err" |
| 278 |
printf '\nrepair: %s\n' "$REPAIR" |
| 279 |
status=1 |
| 280 |
fi |
| 281 |
else |
| 282 |
|
| 283 |
printf 'dynuser: skipped, no systemd-run\n' |
| 284 |
fi |
| 285 |
|
| 286 |
exit "$status" |
| 287 |
|