fix(cis-1.12): do not report FAIL for file permission checks when the file is absent - #2127
fix(cis-1.12): do not report FAIL for file permission checks when the file is absent#2127Eljees wants to merge 2 commits into
Conversation
… file is absent
The file permission and ownership checks of the cis-1.12 benchmark wrap
their audit command in a file existence guard, for example
/bin/sh -c 'if test -e $apiserverconf; then stat -c permissions=%a $apiserverconf; fi'
When the file is absent that command produces no output at all, so the
test_item that looks for the "permissions" flag cannot match and the
check is reported as FAIL. The guard therefore has no effect: without it
stat would fail and the check would be reported as FAIL as well.
cis-1.12/node.yaml 4.1.2 already handles this correctly by echoing a
sentinel in the else branch and accepting it with bin_op: or. This
change applies the same pattern to the remaining 21 file permission and
ownership checks of the benchmark.
Checks where the absence of the file is the finding itself are left
untouched: 1.2.28 (encryption provider configuration) and 4.1.7 / 4.1.8
(client certificate authority file).
Fixes aquasecurity#1881
Signed-off-by: Eljees <[email protected]>
| - id: 1.1.1 | ||
| text: "Ensure that the API server pod specification file permissions are set to 600 or more restrictive (Automated)" | ||
| audit: "/bin/sh -c 'if test -e $apiserverconf; then stat -c permissions=%a $apiserverconf; fi'" | ||
| audit: "/bin/sh -c 'if test -e $apiserverconf; then stat -c permissions=%a $apiserverconf; else echo \"File not found\"; fi'" |
There was a problem hiding this comment.
| audit: "/bin/sh -c 'if test -e $apiserverconf; then stat -c permissions=%a $apiserverconf; else echo \"File not found\"; fi'" | |
| audit: "/bin/sh -c 'stat -c permissions=%a $apiserverconf'" |
Suggesting for this occurrence but this holds for all the related changes.
@Eljees I agree that providing better feedback to the user would improve debugging when an assessed file is missing. I'm not sure why test was introduced initially, but stat natively handles the "file not found" scenario—and offers more accurate errors overall.
Note: file1.yaml was used for all the below examples, nonexisting.yaml (does not exist)
sh-5.2$ tree
.
└── file1.yaml
1 directory, 1 file
Using test then stat (Custom error):
sh-5.2$ export file=file1.yaml && if test -e $file ; then stat -c permissions=%a $file; else echo "File not found"; fi
permissions=644
sh-5.2$ export file=nonexisting.yaml && if test -e $file ; then stat -c permissions=%a $file; else echo "File not found"; fi
File not foundUsing stat alone (Built-in error):
sh-5.2$ export file=nonexisting.yaml && stat -c permissions=%a $file
stat: cannot statx 'nonexisting.yaml': No such file or directory
sh-5.2$ export file=file1.yaml && stat -c permissions=%a $file
permissions=644Another issue with the proposed fix is how it handles restricted permissions. If a file exists but cannot be accessed due to a permissions error, using test results in a misleading "File not found" message. stat alone reports the exact issue correctly (kube-bench runs as root, but for instance LSM such as SELinux could restrict the access too).
(moving file1.yaml to /root/, trying to access it as a standard user)
Permission Denied with test (Misleading error):
sh-5.2$ export file=/root/file1.yaml && if test -e $file ; then stat -c permissions=%a $file; else echo "File not found"; fi
File not foundPermission Denied with stat alone (Accurate error):
sh-5.2$ export file=/root/file1.yaml && stat -c permissions=%a $file
stat: cannot statx '/root/file1.yaml': Permission denied@mozillazg Do you recall the historical context for using test in kube-bench? I could dig into the commit history, but my recommendation would be to simplify this and rely solely on stat.
Apply andypitcher's review suggestion to every wrapped check: drop the 'if test -e ... else echo' guards and let stat report a missing file itself. stat alone exits non-zero, and runAudit turns that into an error that fails the check before any test_item is evaluated, so the audits keep '|| true' to stay on the flag-matching path; the tests accept stat's own 'No such file or directory' (captured from stderr) via bin_op: or. The 4.1.2 upstream sentinel check is aligned to the same pattern, the multi-file 1.1.13/1.1.14 loops drop their found-sentinel, and the guard meta-test now enforces the new shape. A new unit test documents why the bare stat without '|| true' would regress to FAIL. Signed-off-by: Eljees <[email protected]>
|
Applied, thanks — you are right that the custom message threw away more than it added.
One deviation from your snippet that I want to flag rather than sneak in: I kept a trailing audit: "/bin/sh -c 'stat -c permissions=%a $apiserverconf || true'"The reason is the exit code, not the message — If you would rather have the bare form exactly as suggested, say so and I will drop the |
|
Done in 681a90a - applied to all of them, thanks for the pointer and the sample run. Every One deliberate deviation from the literal suggestion: the audits keep
|
|
Ping on this one and #2125 - both have been quiet since my last comment on 1 August. Happy to rebase either. |
|
@andypitcher — one item is still open from 1 August, and it's a one-word decision on your side. I kept a trailing If you'd rather have the bare form exactly as you suggested, say so and I'll drop it and re-measure. The branch is behind main now — I'll rebase it together with whichever form you pick, so you only have to look at it once. |
Hey @Eljees thanks for applying the changes. I'd prefer to avoid Note that it's quite easy then to investigate such an issue (file missing) by increasing the verbosity or using kube-bench --include-test-output. Maybe we could get another opinion @LaibaBareera @mozillazg or @afdesk ? The rest LGTM. |
Fixes #1881
Overview
The file permission and ownership checks of the
cis-1.12benchmark guard their audit command against a missing file:When the file is absent that command produces no output at all, so the
test_itemthat looks for thepermissionsflag can never match and the check is reported as FAIL — for a file that does not exist. The guard therefore has no effect: without itstatwould fail and the check would be reported as FAIL as well.cfg/cis-1.12/node.yaml4.1.2 already handles this correctly — it echoes a sentinel in theelsebranch and accepts it withbin_op: or. This PR applies that same pattern to the remaining file permission and ownership checks of the benchmark.While doing so it also fixes 4.1.3 and 4.1.4, where
bin_op: oris currently a no-op because there is only onetest_itemto combine.Changed (21 checks):
master.yaml1.1.1–1.1.8, 1.1.13–1.1.18;node.yaml4.1.1, 4.1.3–4.1.6, 4.1.9, 4.1.10.For 1.1.13 and 1.1.14, which loop over
admin.confandsuper-admin.confwithuse_multiple_values: true, the sentinel is only echoed when neither file exists, so that a mix of real output and sentinel lines cannot occur.Deliberately not changed, because there the missing file is the finding:
Happy to change those too if you prefer consistency over the current behaviour.
Results before and after
1. New config test (
check/controls_test.go, next toTestYamlFiles) — it walks the benchmark and requires every check that guards against a missing file to also tolerate the resulting empty output.Before:
After:
2. New engine-level cases in
TestCheck_Runthat pin the behaviour itself:3. The audit command itself, on a host without
/etc/kubernetes:and with the file present the output is unchanged, so a real permission violation is still reported:
Full suite:
go test ./check/... ./cmd/...passes.Note
The same defect exists in the older benchmarks (about 600 checks across all of
cfg/). This PR keeps the change to the current benchmark so the diff stays reviewable; the test has an explicit list of the benchmarks that have been audited, so extending it is a one-line change per benchmark. Glad to follow up with the rest if you want them.Written with AI assistance; reviewed, built and tested by me.