cem_acpt 0.11.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +93 -0
- data/docs/ARCHITECTURE.md +10 -16
- data/exe/cem_acpt_scan +16 -0
- data/lib/cem_acpt/cli.rb +24 -0
- data/lib/cem_acpt/config/base.rb +10 -3
- data/lib/cem_acpt/config/cem_acpt_scan.rb +112 -0
- data/lib/cem_acpt/config.rb +1 -0
- data/lib/cem_acpt/platform/gcp.rb +6 -9
- data/lib/cem_acpt/provision/terraform/linux.rb +52 -6
- data/lib/cem_acpt/provision/terraform.rb +147 -10
- data/lib/cem_acpt/scan/daemon_client.rb +91 -0
- data/lib/cem_acpt/scan/errors.rb +44 -0
- data/lib/cem_acpt/scan/result.rb +89 -0
- data/lib/cem_acpt/scan.rb +17 -0
- data/lib/cem_acpt/test_data.rb +59 -3
- data/lib/cem_acpt/test_runner/log_formatter/scan_result_formatter.rb +72 -0
- data/lib/cem_acpt/test_runner/log_formatter.rb +4 -1
- data/lib/cem_acpt/test_runner.rb +103 -5
- data/lib/cem_acpt/version.rb +1 -1
- data/lib/cem_acpt.rb +18 -0
- data/lib/terraform/gcp/linux/main.tf +129 -1
- data/lib/terraform/gcp/linux/scan/scan_service.rb +148 -0
- data/lib/terraform/gcp/linux/scan/scan_service.service +12 -0
- data/lib/terraform/gcp/windows/main.tf +1 -1
- data/lib/terraform/image/gcp/linux/main.tf +1 -1
- data/specifications/CEM-6511.md +286 -0
- data/specifications/CEM-6720.md +187 -0
- data/specifications/CEM-6759.md +168 -0
- data/specifications/CEM-6760.md +120 -0
- data/specifications/CEM-6761.md +136 -0
- data/specifications/CEM-6762.md +163 -0
- data/specifications/CEM-6765.md +101 -0
- metadata +23 -4
- data/lib/cem_acpt/provision.rb +0 -20
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# CEM-6760 — Fix invalid CIS-CAT Pro Assessor flags in on-node scan daemon
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
The on-node scan daemon's `run_ciscat` helper in
|
|
6
|
+
`lib/terraform/gcp/linux/scan/scan_service.rb` passes two flags that
|
|
7
|
+
CIS-CAT Pro v4's `Assessor-CLI.sh` does not accept: `-rn ciscat-results`
|
|
8
|
+
and `-rjson`. Both are silently ignored — the assessor falls back to a
|
|
9
|
+
hostname-prefixed default filename instead of `ciscat-results.*`, and
|
|
10
|
+
no JSON report is ever written. Only the canonical ARF.xml lands in
|
|
11
|
+
the reports directory, after which the daemon 500s when its parser
|
|
12
|
+
tries to read the JSON file at the expected path. Swap the flags to
|
|
13
|
+
their real names (`-rp`, `-json`) and add `-nts` to suppress the
|
|
14
|
+
auto-appended timestamp so the file lands at the literal path the
|
|
15
|
+
parser already reads.
|
|
16
|
+
|
|
17
|
+
## Functional Behavior
|
|
18
|
+
|
|
19
|
+
### `lib/terraform/gcp/linux/scan/scan_service.rb#run_ciscat`
|
|
20
|
+
|
|
21
|
+
The method currently constructs the assessor command as
|
|
22
|
+
(`scan_service.rb:73-79`):
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
cmd = [
|
|
26
|
+
'sudo', '/opt/cis-cat-pro/Assessor-CLI.sh',
|
|
27
|
+
'-rd', REPORT_DIR,
|
|
28
|
+
'-rn', 'ciscat-results',
|
|
29
|
+
'-rjson',
|
|
30
|
+
'-p', profile,
|
|
31
|
+
]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Replace `-rn` with `-rp` (the real `--report-prefix` flag), replace
|
|
35
|
+
`-rjson` with `-json` (the real JSON-output flag), and append `-nts`
|
|
36
|
+
(`--no-timestamp`) so the assessor does not insert a timestamp segment
|
|
37
|
+
in the filename:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
cmd = [
|
|
41
|
+
'sudo', '/opt/cis-cat-pro/Assessor-CLI.sh',
|
|
42
|
+
'-rd', REPORT_DIR,
|
|
43
|
+
'-rp', 'ciscat-results',
|
|
44
|
+
'-nts',
|
|
45
|
+
'-json',
|
|
46
|
+
'-p', profile,
|
|
47
|
+
]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
With these three changes the assessor writes JSON to
|
|
51
|
+
`/opt/cem_acpt/scan/reports/ciscat-results.json` — the literal path
|
|
52
|
+
`json_out` at `scan_service.rb:72` already references and
|
|
53
|
+
`parse_ciscat_json` already reads.
|
|
54
|
+
|
|
55
|
+
The `-l <level>` conditional at `scan_service.rb:81` is unchanged.
|
|
56
|
+
|
|
57
|
+
## Input/Output Contracts
|
|
58
|
+
|
|
59
|
+
No method signature changes. The command array passed to
|
|
60
|
+
`Open3.capture3` changes; everything downstream — `parse_ciscat_json`,
|
|
61
|
+
the `error` handling at `scan_service.rb:84`, and the `/scan` HTTP
|
|
62
|
+
response shape — sees the same data flow.
|
|
63
|
+
|
|
64
|
+
## Constraints / Invariants
|
|
65
|
+
|
|
66
|
+
- `-rp`, `-json`, and `-nts` are all valid in CIS-CAT Pro v4.x
|
|
67
|
+
(confirmed against `Assessor-CLI.sh -h` on a v4.63.0-provisioned
|
|
68
|
+
node). The shipping `cem_acpt_scan` flow installs the assessor from
|
|
69
|
+
the operator-supplied bundle, so any v4.x release works.
|
|
70
|
+
- ARF.xml continues to be emitted as a side effect — we do not pass
|
|
71
|
+
`-narf`. Keeping ARF makes it easier to diagnose future scan
|
|
72
|
+
failures by hand on the node.
|
|
73
|
+
- The `json_out` path constant at `scan_service.rb:72` does not change.
|
|
74
|
+
|
|
75
|
+
## Tests
|
|
76
|
+
|
|
77
|
+
Add a text-grep regression spec at
|
|
78
|
+
`spec/terraform/gcp/linux/scan/scan_service_spec.rb` (new path,
|
|
79
|
+
mirroring `lib/`). The spec reads `scan_service.rb` as a string and
|
|
80
|
+
asserts the corrected flag tokens are present and the old ones absent:
|
|
81
|
+
|
|
82
|
+
- `'-rp', 'ciscat-results'` present, `'-rn', 'ciscat-results'` absent.
|
|
83
|
+
- `'-json'` present, `'-rjson'` absent.
|
|
84
|
+
- `'-nts'` present.
|
|
85
|
+
|
|
86
|
+
No execution, no `Open3` stubbing, no eval-slicing of the script. The
|
|
87
|
+
behavioral validation (the assessor actually accepting these flags)
|
|
88
|
+
was performed by hand on a v4.63.0 node; the spec's only job is to
|
|
89
|
+
catch an accidental revert.
|
|
90
|
+
|
|
91
|
+
## Non-Goals
|
|
92
|
+
|
|
93
|
+
- **Stderr capture on scan failure.** Tracked by CEM-6761.
|
|
94
|
+
- **Missing `-b <benchmark>` flag.** Tracked by CEM-6759. Without it
|
|
95
|
+
the assessor still exits without scanning anything; this spec only
|
|
96
|
+
addresses flag-name correctness, not benchmark identification.
|
|
97
|
+
- **JSON key-name mismatch in `parse_ciscat_json`.** Tracked by
|
|
98
|
+
CEM-6762.
|
|
99
|
+
- **Refactoring `scan_service.rb` for broader testability beyond what
|
|
100
|
+
is needed to assert the new flag set.**
|
|
101
|
+
|
|
102
|
+
## Acceptance Criteria
|
|
103
|
+
|
|
104
|
+
- [ ] `run_ciscat` passes `-rp ciscat-results` instead of
|
|
105
|
+
`-rn ciscat-results`.
|
|
106
|
+
- [ ] `run_ciscat` passes `-json` instead of `-rjson`.
|
|
107
|
+
- [ ] `run_ciscat` passes `-nts` so the output filename has no
|
|
108
|
+
timestamp segment.
|
|
109
|
+
- [ ] RSpec regression coverage at
|
|
110
|
+
`spec/terraform/gcp/linux/scan/scan_service_spec.rb` asserts the
|
|
111
|
+
corrected flag tokens are present and the old ones absent.
|
|
112
|
+
- [ ] `bundle exec rake spec` passes.
|
|
113
|
+
|
|
114
|
+
## Files Touched
|
|
115
|
+
|
|
116
|
+
- `lib/terraform/gcp/linux/scan/scan_service.rb` — `run_ciscat` flag
|
|
117
|
+
fix.
|
|
118
|
+
- `spec/terraform/gcp/linux/scan/scan_service_spec.rb` — new
|
|
119
|
+
text-grep regression spec.
|
|
120
|
+
- `specifications/CEM-6760.md` — this file.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# CEM-6761 — Surface assessor stderr when result file is missing
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
`run_ciscat` and `run_openscap` in `lib/terraform/gcp/linux/scan/scan_service.rb` use
|
|
6
|
+
`||=` to attach the assessor/oscap exit-code message to `parsed['error']`. Because
|
|
7
|
+
`parse_ciscat_json` / `parse_openscap_xml` set `parsed['error']` to a "Result file
|
|
8
|
+
missing" string whenever the output file is absent, `||=` short-circuits and the
|
|
9
|
+
actual assessor stderr — the reason the file was never written — is silently discarded.
|
|
10
|
+
Engineers debugging a failed scan see only `"Result file missing: …"` with no indication
|
|
11
|
+
of what the scanner printed, making root-cause analysis guesswork.
|
|
12
|
+
|
|
13
|
+
## Functional Behavior
|
|
14
|
+
|
|
15
|
+
### `run_ciscat` (`scan_service.rb:71–86`)
|
|
16
|
+
|
|
17
|
+
**Before:**
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
_stdout, stderr, status = Open3.capture3(*cmd)
|
|
21
|
+
parsed = parse_ciscat_json(json_out)
|
|
22
|
+
parsed['error'] ||= "Assessor-CLI exited #{status.exitstatus}: #{stderr}" unless status.success? || File.exist?(json_out)
|
|
23
|
+
parsed
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**After:**
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
_stdout, stderr, status = Open3.capture3(*cmd)
|
|
30
|
+
parsed = parse_ciscat_json(json_out)
|
|
31
|
+
unless status.success? || File.exist?(json_out)
|
|
32
|
+
assessor_msg = "Assessor-CLI exited #{status.exitstatus}: #{stderr.strip}"
|
|
33
|
+
parsed['error'] = parsed['error'] ? "#{assessor_msg}; #{parsed['error']}" : assessor_msg
|
|
34
|
+
end
|
|
35
|
+
parsed
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### `run_openscap` (`scan_service.rb:31–45`)
|
|
39
|
+
|
|
40
|
+
**Before:**
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
_stdout, stderr, status = Open3.capture3(*cmd)
|
|
44
|
+
parsed = parse_openscap_xml(xml_out)
|
|
45
|
+
parsed['error'] ||= "oscap exited #{status.exitstatus}: #{stderr}" unless status.success? || File.exist?(xml_out)
|
|
46
|
+
parsed
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**After:**
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
_stdout, stderr, status = Open3.capture3(*cmd)
|
|
53
|
+
parsed = parse_openscap_xml(xml_out)
|
|
54
|
+
unless status.success? || File.exist?(xml_out)
|
|
55
|
+
oscap_msg = "oscap exited #{status.exitstatus}: #{stderr.strip}"
|
|
56
|
+
parsed['error'] = parsed['error'] ? "#{oscap_msg}; #{parsed['error']}" : oscap_msg
|
|
57
|
+
end
|
|
58
|
+
parsed
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Key changes:
|
|
62
|
+
- `||=` replaced with explicit conditional assignment so both messages are preserved.
|
|
63
|
+
- `stderr.strip` trims trailing newlines from the assessor output before embedding it.
|
|
64
|
+
- Assessor/oscap message is prepended, separated by `"; "`, so the tool-level failure
|
|
65
|
+
is the first thing the reader sees.
|
|
66
|
+
|
|
67
|
+
## Input/Output Contracts
|
|
68
|
+
|
|
69
|
+
**Failure path input:** `Open3.capture3` returns a non-zero exit status *and* the
|
|
70
|
+
result file was not written (the normal failure mode when the assessor crashes early).
|
|
71
|
+
|
|
72
|
+
**Output:** The `'error'` key in the returned hash contains both messages joined by
|
|
73
|
+
`"; "`:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
"Assessor-CLI exited 1: <stderr text>; Result file missing: /opt/cem_acpt/scan/reports/ciscat-results.json"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This string flows back through `perform_scan` → `/scan` endpoint → HTTP 500 response
|
|
80
|
+
body, where the host-side `DaemonClient` surfaces it in the test failure output.
|
|
81
|
+
|
|
82
|
+
## Edge Cases
|
|
83
|
+
|
|
84
|
+
- **Assessor exits non-zero but file was written anyway** — the `|| File.exist?` guard
|
|
85
|
+
is unchanged; stderr is not attached in this case. The parse result stands on its own.
|
|
86
|
+
- **`stderr` is empty or only whitespace** — `.strip` collapses it to `""`, so the
|
|
87
|
+
message reads `"Assessor-CLI exited 1: "` — still correct, not a crash.
|
|
88
|
+
- **`parse_ciscat_json` returns no `'error'` key** — `parsed['error']` is `nil`;
|
|
89
|
+
the ternary falls through to the bare `assessor_msg` assignment. Equivalent to
|
|
90
|
+
the original `||=` behaviour.
|
|
91
|
+
|
|
92
|
+
## Constraints / Invariants
|
|
93
|
+
|
|
94
|
+
- The guard condition `unless status.success? || File.exist?(json_out)` is unchanged.
|
|
95
|
+
This condition defines when stderr is surfaced and must not be relaxed.
|
|
96
|
+
- The normalized output hash shape remains compatible with `CemAcpt::Scan::Result`.
|
|
97
|
+
No callers change.
|
|
98
|
+
|
|
99
|
+
## Non-Goals
|
|
100
|
+
|
|
101
|
+
- Adding a `-b <benchmark>` flag to `run_ciscat` (CEM-6759).
|
|
102
|
+
- Fixing the CIS-CAT Pro JSON key names in `parse_ciscat_json` (CEM-6762 — already done).
|
|
103
|
+
- Capturing assessor stdout; only stderr is relevant for error reporting.
|
|
104
|
+
|
|
105
|
+
## Tests
|
|
106
|
+
|
|
107
|
+
Extend `spec/terraform/gcp/linux/scan/scan_service_spec.rb` with two new `describe`
|
|
108
|
+
blocks — one for `run_ciscat` and one for `run_openscap` — that stub `Open3.capture3`
|
|
109
|
+
and exercise the combined-error-message path. The script is already loaded via the
|
|
110
|
+
`SCAN_SERVICE_LOADED` guard, so `run_ciscat` and `run_openscap` are available directly.
|
|
111
|
+
|
|
112
|
+
For `run_ciscat`: stub `Open3.capture3` to return `['', 'Permission denied', double(exitstatus: 1, success?: false)]`
|
|
113
|
+
and pass a nonexistent `json_out` path (the stub never writes a file). Assert that the
|
|
114
|
+
returned `'error'` string contains both `"Assessor-CLI exited 1"` and `"Result file missing"`.
|
|
115
|
+
|
|
116
|
+
For `run_openscap`: same pattern with `oscap` stderr. Assert that `'error'` contains
|
|
117
|
+
both `"oscap exited 1"` and `"Result file missing"`.
|
|
118
|
+
|
|
119
|
+
## Acceptance Criteria
|
|
120
|
+
|
|
121
|
+
- [ ] `run_ciscat` uses explicit conditional assignment instead of `||=`.
|
|
122
|
+
- [ ] `run_openscap` uses explicit conditional assignment instead of `||=`.
|
|
123
|
+
- [ ] When the result file is absent and the assessor exits non-zero, the `'error'`
|
|
124
|
+
value includes both the exit-code/stderr message and the "Result file missing" message.
|
|
125
|
+
- [ ] `.strip` is applied to `stderr` before embedding.
|
|
126
|
+
- [ ] All existing specs pass.
|
|
127
|
+
- [ ] New unit tests for the combined-error path pass for both `run_ciscat` and
|
|
128
|
+
`run_openscap`.
|
|
129
|
+
|
|
130
|
+
## Files Touched
|
|
131
|
+
|
|
132
|
+
- `lib/terraform/gcp/linux/scan/scan_service.rb` — fix `run_ciscat` and `run_openscap`.
|
|
133
|
+
- `spec/fixtures/config_testing/user_config_dir/terraform/gcp/linux/scan/scan_service.rb` — mirror update.
|
|
134
|
+
- `spec/fixtures/config_testing/user_config_dir/terraform_checksum.txt` — checksum update.
|
|
135
|
+
- `spec/terraform/gcp/linux/scan/scan_service_spec.rb` — new unit-test contexts for
|
|
136
|
+
both methods.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# CEM-6762 — Parse CIS-CAT Pro v4 rules JSON keys correctly
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
`parse_ciscat_json` in `lib/terraform/gcp/linux/scan/scan_service.rb` reads the wrong
|
|
6
|
+
top-level array key and wrong per-entry keys out of the CIS-CAT Pro Assessor v4 JSON
|
|
7
|
+
report. The parser expects `raw['results']` at the top level, but v4 emits `raw['rules']`.
|
|
8
|
+
Per-entry, it tries `r['id'] || r['rule_id']` for the rule identifier and `r['severity']`
|
|
9
|
+
for severity; v4 uses `r['rule-id']` (hyphen, not underscore) and emits no `severity` field.
|
|
10
|
+
The result-value count bucketing maps `'notapplicable'` to `not_applicable_count`, but v4
|
|
11
|
+
emits `'notchecked'` and `'informational'` instead.
|
|
12
|
+
|
|
13
|
+
Because the key names don't match, `raw['rules']` resolves to nil, the parser falls back to
|
|
14
|
+
an empty array, and every CIS-CAT Pro scan reports zero passes and zero fails regardless of
|
|
15
|
+
what the assessor found. The top-level `score` key is correct and `.to_f` handles the string
|
|
16
|
+
`"85.95"` format v4 uses, so the score is not broken — this masks the breakage in smoke
|
|
17
|
+
tests. Confirmed against actual `Assessor-CLI.sh -json` output on RHEL 8 while validating
|
|
18
|
+
CEM-6511.
|
|
19
|
+
|
|
20
|
+
## Functional Behavior
|
|
21
|
+
|
|
22
|
+
### `parse_ciscat_json` (`scan_service.rb:88–109`)
|
|
23
|
+
|
|
24
|
+
**Before:**
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
results = (raw['results'] || []).map do |r|
|
|
28
|
+
{
|
|
29
|
+
'id' => r['id'] || r['rule_id'],
|
|
30
|
+
'severity' => r['severity'],
|
|
31
|
+
'result' => r['result'] || r['status'],
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
score = (raw['score'] || raw['compliance_score'] || 0.0).to_f
|
|
35
|
+
counts = results.group_by { |r| (r['result'] || '').to_s.downcase }.transform_values(&:size)
|
|
36
|
+
{
|
|
37
|
+
'score' => score,
|
|
38
|
+
'passed_count' => counts['pass'] || 0,
|
|
39
|
+
'failed_count' => counts['fail'] || 0,
|
|
40
|
+
'not_applicable_count' => counts['notapplicable'] || 0,
|
|
41
|
+
'error_count' => counts['error'] || 0,
|
|
42
|
+
'rules' => results,
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**After:**
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
results = (raw['rules'] || []).map do |r|
|
|
50
|
+
{
|
|
51
|
+
'id' => r['rule-id'],
|
|
52
|
+
'result' => r['result'],
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
score = (raw['score'] || 0.0).to_f
|
|
56
|
+
counts = results.group_by { |r| (r['result'] || '').to_s.downcase }.transform_values(&:size)
|
|
57
|
+
{
|
|
58
|
+
'score' => score,
|
|
59
|
+
'passed_count' => counts['pass'] || 0,
|
|
60
|
+
'failed_count' => counts['fail'] || 0,
|
|
61
|
+
'not_applicable_count' => (counts['notchecked'] || 0) + (counts['notapplicable'] || 0),
|
|
62
|
+
'error_count' => (counts['error'] || 0) + (counts['informational'] || 0),
|
|
63
|
+
'rules' => results,
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Key changes:
|
|
68
|
+
- `raw['results']` → `raw['rules']` (v4 top-level key).
|
|
69
|
+
- `r['id'] || r['rule_id']` → `r['rule-id']` (v4 per-entry key; hyphen, not underscore).
|
|
70
|
+
- `r['result'] || r['status']` → `r['result']` (v4 only uses `result`).
|
|
71
|
+
- `severity` field dropped; v4 does not emit it.
|
|
72
|
+
- `not_applicable_count` sums `notchecked` + `notapplicable` — v4 uses `notchecked`; the
|
|
73
|
+
`notapplicable` term is kept as a defensive fallback for format variants.
|
|
74
|
+
- `error_count` sums `error` + `informational` — v4 uses `informational` for rules that are
|
|
75
|
+
advisory only.
|
|
76
|
+
- `raw['compliance_score']` fallback removed; not present in v4.
|
|
77
|
+
|
|
78
|
+
## Input/Output Contracts
|
|
79
|
+
|
|
80
|
+
**Input:** Path to a CIS-CAT Pro v4 JSON report file. Confirmed v4 shape:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"benchmark-id": "xccdf_org.cisecurity.benchmarks_benchmark_4.0.0_CIS_...",
|
|
85
|
+
"benchmark-title": "CIS Red Hat Enterprise Linux 8 Benchmark",
|
|
86
|
+
"profile-id": "xccdf_org.cisecurity.benchmarks_profile_Level_2_-_Server",
|
|
87
|
+
"score": "85.95",
|
|
88
|
+
"rules": [
|
|
89
|
+
{ "rule-id": "xccdf_org.cisecurity.benchmarks_rule_1.1.1.1_...", "rule-title": "...", "result": "pass" },
|
|
90
|
+
{ "rule-id": "xccdf_org.cisecurity.benchmarks_rule_6.1.2_...", "rule-title": "...", "result": "fail" },
|
|
91
|
+
{ "rule-id": "xccdf_org.cisecurity.benchmarks_rule_6.2.1.1.2_...","rule-title": "...", "result": "notchecked" },
|
|
92
|
+
{ "rule-id": "xccdf_org.cisecurity.benchmarks_rule_5.4.1.2_...", "rule-title": "...", "result": "informational" }
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Output:** Hash compatible with the host-side `Scan::Result` constructor (shape unchanged):
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
{
|
|
101
|
+
'score' => 85.95, # Float; score string coerced with .to_f
|
|
102
|
+
'passed_count' => Integer,
|
|
103
|
+
'failed_count' => Integer,
|
|
104
|
+
'not_applicable_count' => Integer, # notchecked + notapplicable
|
|
105
|
+
'error_count' => Integer, # error + informational
|
|
106
|
+
'rules' => [{ 'id' => String, 'result' => String }, ...],
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Edge Cases
|
|
111
|
+
|
|
112
|
+
- **File missing:** returns `{ 'error' => "Result file missing: #{path}" }` — unchanged.
|
|
113
|
+
- **Empty `rules` array:** all counts zero, `rules` is `[]`; no crash.
|
|
114
|
+
- **`score` as string:** `.to_f` on `"85.95"` returns `85.95`; handled correctly.
|
|
115
|
+
- **`notapplicable` in future format variants:** included in `not_applicable_count` sum as a defensive fallback.
|
|
116
|
+
|
|
117
|
+
## Constraints / Invariants
|
|
118
|
+
|
|
119
|
+
- Normalized output hash shape must remain compatible with `CemAcpt::Scan::Result#initialize`. No callers change.
|
|
120
|
+
- `rule-title` is not included in normalized rule entries; `rule-id` is sufficient as the unique identifier.
|
|
121
|
+
|
|
122
|
+
## Non-Goals
|
|
123
|
+
|
|
124
|
+
- Adding the `-b <benchmark>` flag to `run_ciscat` (CEM-6759).
|
|
125
|
+
- Surfacing assessor stderr on failure (CEM-6761).
|
|
126
|
+
- Parsing OpenSCAP XML results (`parse_openscap_xml` is a separate method and is unaffected).
|
|
127
|
+
|
|
128
|
+
## Tests
|
|
129
|
+
|
|
130
|
+
Extend `spec/terraform/gcp/linux/scan/scan_service_spec.rb` with a new `describe` context
|
|
131
|
+
for `parse_ciscat_json`. Because `scan_service.rb` is a standalone script that starts a
|
|
132
|
+
WEBrick server at load time, the spec loads it via `load` after stubbing
|
|
133
|
+
`WEBrick::HTTPServer.new` (returning a double that silently absorbs `mount_proc`, `start`,
|
|
134
|
+
and `shutdown`) and `FileUtils.mkdir_p` (to avoid creating `/opt/cem_acpt/scan/reports` on
|
|
135
|
+
the test host). After loading, `parse_ciscat_json` is defined at the top level and callable
|
|
136
|
+
directly.
|
|
137
|
+
|
|
138
|
+
The fixture is a small inline JSON string mirroring the confirmed v4 shape, written to a
|
|
139
|
+
`Tempfile`. Tests assert:
|
|
140
|
+
|
|
141
|
+
- All four result values (`pass`, `fail`, `notchecked`, `informational`) are counted in the
|
|
142
|
+
correct bucket.
|
|
143
|
+
- `rules` entries carry `id` from `rule-id` and `result` from `result`; no `severity` key.
|
|
144
|
+
- A non-existent path returns `{ 'error' => /Result file missing/ }`.
|
|
145
|
+
|
|
146
|
+
The existing CEM-6760 text-grep assertions are untouched.
|
|
147
|
+
|
|
148
|
+
## Acceptance Criteria
|
|
149
|
+
|
|
150
|
+
- [ ] `parse_ciscat_json` reads `raw['rules']` instead of `raw['results']`.
|
|
151
|
+
- [ ] Rule entries use `r['rule-id']` for `id`; `r['rule_id']` and `r['id']` are no longer referenced.
|
|
152
|
+
- [ ] `severity` is no longer extracted from rule entries.
|
|
153
|
+
- [ ] `not_applicable_count` counts `notchecked` and `notapplicable` result values.
|
|
154
|
+
- [ ] `error_count` counts `error` and `informational` result values.
|
|
155
|
+
- [ ] All existing specs pass.
|
|
156
|
+
- [ ] New unit tests for `parse_ciscat_json` pass with a v4-shaped fixture.
|
|
157
|
+
|
|
158
|
+
## Files Touched
|
|
159
|
+
|
|
160
|
+
- `lib/terraform/gcp/linux/scan/scan_service.rb` — fix `parse_ciscat_json`.
|
|
161
|
+
- `spec/fixtures/config_testing/user_config_dir/terraform/gcp/linux/scan/scan_service.rb` — mirror update.
|
|
162
|
+
- `spec/fixtures/config_testing/user_config_dir/terraform_checksum.txt` — checksum update.
|
|
163
|
+
- `spec/terraform/gcp/linux/scan/scan_service_spec.rb` — new `parse_ciscat_json` unit-test context.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# CEM-6765 — Remove stray `-l` flag from CIS-CAT Pro Assessor invocation
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
The on-node scan daemon's `run_ciscat` helper in `lib/terraform/gcp/linux/scan/scan_service.rb` appends `-l <level>` to the assessor command. In CIS-CAT Pro v4, `-l` is `--list` ("List available assessment content"), not a level argument. The assessor parses `-l 2`, ignores the `2`, treats the invocation as a list request, prints the numbered benchmark catalog, exits zero, and writes no JSON report. This silently hijacks every CIS-CAT Pro scan regardless of how the rest of the pipeline is configured — masking CEM-6759's `-b` wiring and every prior blocker fix. Confirmed against `Assessor-CLI.sh -h` on a v4.63.0-provisioned node: `-l` has no level meaning. Scan level is encoded in the profile id (`Level_2_-_Server`), not a separate flag.
|
|
6
|
+
|
|
7
|
+
Drop the `-l` flag, drop the now-unused `level` parameter from `run_ciscat`, and drop the corresponding argument from the `perform_scan` call site.
|
|
8
|
+
|
|
9
|
+
## Functional Behavior
|
|
10
|
+
|
|
11
|
+
### `lib/terraform/gcp/linux/scan/scan_service.rb#run_ciscat`
|
|
12
|
+
|
|
13
|
+
The method currently constructs the assessor command as (`scan_service.rb:74-85`):
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
def run_ciscat(profile, level, benchmark)
|
|
17
|
+
json_out = File.join(REPORT_DIR, 'ciscat-results.json')
|
|
18
|
+
cmd = [
|
|
19
|
+
'sudo', '/opt/cis-cat-pro/Assessor-CLI.sh',
|
|
20
|
+
'-rd', REPORT_DIR,
|
|
21
|
+
'-rp', 'ciscat-results',
|
|
22
|
+
'-nts',
|
|
23
|
+
'-json',
|
|
24
|
+
'-p', profile,
|
|
25
|
+
]
|
|
26
|
+
cmd += ['-b', "/opt/cis-cat-pro/benchmarks/#{benchmark}"] if benchmark
|
|
27
|
+
cmd += ['-l', level.to_s] if level
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Drop the `level` parameter and the `-l` append:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
def run_ciscat(profile, benchmark)
|
|
34
|
+
json_out = File.join(REPORT_DIR, 'ciscat-results.json')
|
|
35
|
+
cmd = [
|
|
36
|
+
'sudo', '/opt/cis-cat-pro/Assessor-CLI.sh',
|
|
37
|
+
'-rd', REPORT_DIR,
|
|
38
|
+
'-rp', 'ciscat-results',
|
|
39
|
+
'-nts',
|
|
40
|
+
'-json',
|
|
41
|
+
'-p', profile,
|
|
42
|
+
]
|
|
43
|
+
cmd += ['-b', "/opt/cis-cat-pro/benchmarks/#{benchmark}"] if benchmark
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### `lib/terraform/gcp/linux/scan/scan_service.rb#perform_scan`
|
|
47
|
+
|
|
48
|
+
Update the call site (`scan_service.rb:124`):
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# before:
|
|
52
|
+
when 'ciscat'
|
|
53
|
+
run_ciscat(cfg['profile'], cfg['level'], cfg['benchmark'])
|
|
54
|
+
|
|
55
|
+
# after:
|
|
56
|
+
when 'ciscat'
|
|
57
|
+
run_ciscat(cfg['profile'], cfg['benchmark'])
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The `cfg['level']` key remains in the daemon's `scan_config.json` payload — it is unused by `run_ciscat` after this change but harmless. Removing it from the host-side JSON payload is left as a follow-up cleanup, not a blocker.
|
|
61
|
+
|
|
62
|
+
## Input/Output Contracts
|
|
63
|
+
|
|
64
|
+
`run_ciscat` loses its `level` positional argument; signature shrinks from three positional args to two. Everything downstream (`parse_ciscat_json`, the error handling at `scan_service.rb:87-90`, and the `/scan` HTTP response shape) is unchanged.
|
|
65
|
+
|
|
66
|
+
## Constraints / Invariants
|
|
67
|
+
|
|
68
|
+
- `-l` is `--list` in CIS-CAT Pro v4.x and has no level meaning — confirmed from `Assessor-CLI.sh -h` on a v4.63.0 node during CEM-6765's investigation. There is no separate level flag in the v4 CLI.
|
|
69
|
+
- Scan level is already encoded in the profile id (e.g. `xccdf_org.cisecurity.benchmarks_profile_Level_2_-_Server`). Removing the redundant `-l` does not change which controls the assessor evaluates.
|
|
70
|
+
- `test_data[:scan][:level]` is unaffected. It is still extracted by `test_data.rb`'s `vars_post_processing` from the test-case name pattern and remains available in the scan config payload for logging and openscap.
|
|
71
|
+
|
|
72
|
+
## Tests
|
|
73
|
+
|
|
74
|
+
Add a text-grep regression to `spec/terraform/gcp/linux/scan/scan_service_spec.rb` asserting `'-l'` is absent from `run_ciscat`'s command array.
|
|
75
|
+
|
|
76
|
+
Update the existing CEM-6761 `run_ciscat` stub tests at `spec/terraform/gcp/linux/scan/scan_service_spec.rb:171` to drop the level positional argument — `run_ciscat('profile-id', 'benchmark.xml')` instead of `run_ciscat('profile-id', 2, 'benchmark.xml')`.
|
|
77
|
+
|
|
78
|
+
Behavioral validation (the assessor producing a JSON report with the corrected command) was performed by hand on a v4.63.0 node during CEM-6765's investigation. That evidence is stronger than any unit test we could write without a live assessor binary.
|
|
79
|
+
|
|
80
|
+
## Non-Goals
|
|
81
|
+
|
|
82
|
+
- **Removing `cfg['level']` from `scan_config.json`.** The level remains in the payload as a harmless metadata field. Removing it would touch `provision/terraform.rb` and ripple through CEM-6759's tests for no functional gain.
|
|
83
|
+
- **Dropping `level` extraction from `test_data.rb`.** It is still useful for logging and openscap.
|
|
84
|
+
- **Refactoring `run_ciscat` further.**
|
|
85
|
+
|
|
86
|
+
## Acceptance Criteria
|
|
87
|
+
|
|
88
|
+
- [ ] `run_ciscat` no longer passes `-l` to `Assessor-CLI.sh`.
|
|
89
|
+
- [ ] `run_ciscat`'s signature is `(profile, benchmark)` — no `level` parameter.
|
|
90
|
+
- [ ] `perform_scan` calls `run_ciscat(cfg['profile'], cfg['benchmark'])`.
|
|
91
|
+
- [ ] RSpec regression coverage at `spec/terraform/gcp/linux/scan/scan_service_spec.rb` asserts `'-l'` is not in the script.
|
|
92
|
+
- [ ] CEM-6761 `run_ciscat` stub tests pass the new two-arg signature.
|
|
93
|
+
- [ ] `bundle exec rake spec` passes.
|
|
94
|
+
|
|
95
|
+
## Files Touched
|
|
96
|
+
|
|
97
|
+
- `lib/terraform/gcp/linux/scan/scan_service.rb` — drop `-l` flag and `level` parameter.
|
|
98
|
+
- `spec/fixtures/config_testing/user_config_dir/terraform/gcp/linux/scan/scan_service.rb` — fixture mirror.
|
|
99
|
+
- `spec/fixtures/config_testing/user_config_dir/terraform_checksum.txt` — updated checksum.
|
|
100
|
+
- `spec/terraform/gcp/linux/scan/scan_service_spec.rb` — `-l` absence text-grep, two-arg `run_ciscat` calls.
|
|
101
|
+
- `specifications/CEM-6765.md` — this file.
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cem_acpt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- puppetlabs
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-06-08 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: async-http
|
|
@@ -219,6 +220,7 @@ email:
|
|
|
219
220
|
executables:
|
|
220
221
|
- cem_acpt
|
|
221
222
|
- cem_acpt_image
|
|
223
|
+
- cem_acpt_scan
|
|
222
224
|
extensions: []
|
|
223
225
|
extra_rdoc_files: []
|
|
224
226
|
files:
|
|
@@ -252,6 +254,7 @@ files:
|
|
|
252
254
|
- docs/rfcs/README.md
|
|
253
255
|
- exe/cem_acpt
|
|
254
256
|
- exe/cem_acpt_image
|
|
257
|
+
- exe/cem_acpt_scan
|
|
255
258
|
- lib/cem_acpt.rb
|
|
256
259
|
- lib/cem_acpt/actions.rb
|
|
257
260
|
- lib/cem_acpt/bolt.rb
|
|
@@ -272,6 +275,7 @@ files:
|
|
|
272
275
|
- lib/cem_acpt/config/base.rb
|
|
273
276
|
- lib/cem_acpt/config/cem_acpt.rb
|
|
274
277
|
- lib/cem_acpt/config/cem_acpt_image.rb
|
|
278
|
+
- lib/cem_acpt/config/cem_acpt_scan.rb
|
|
275
279
|
- lib/cem_acpt/core_ext.rb
|
|
276
280
|
- lib/cem_acpt/goss.rb
|
|
277
281
|
- lib/cem_acpt/goss/api.rb
|
|
@@ -286,12 +290,15 @@ files:
|
|
|
286
290
|
- lib/cem_acpt/platform.rb
|
|
287
291
|
- lib/cem_acpt/platform/base.rb
|
|
288
292
|
- lib/cem_acpt/platform/gcp.rb
|
|
289
|
-
- lib/cem_acpt/provision.rb
|
|
290
293
|
- lib/cem_acpt/provision/terraform.rb
|
|
291
294
|
- lib/cem_acpt/provision/terraform/linux.rb
|
|
292
295
|
- lib/cem_acpt/provision/terraform/os_data.rb
|
|
293
296
|
- lib/cem_acpt/provision/terraform/terraform_cmd.rb
|
|
294
297
|
- lib/cem_acpt/provision/terraform/windows.rb
|
|
298
|
+
- lib/cem_acpt/scan.rb
|
|
299
|
+
- lib/cem_acpt/scan/daemon_client.rb
|
|
300
|
+
- lib/cem_acpt/scan/errors.rb
|
|
301
|
+
- lib/cem_acpt/scan/result.rb
|
|
295
302
|
- lib/cem_acpt/test_data.rb
|
|
296
303
|
- lib/cem_acpt/test_runner.rb
|
|
297
304
|
- lib/cem_acpt/test_runner/log_formatter.rb
|
|
@@ -299,6 +306,7 @@ files:
|
|
|
299
306
|
- lib/cem_acpt/test_runner/log_formatter/bolt_summary_results_formatter.rb
|
|
300
307
|
- lib/cem_acpt/test_runner/log_formatter/goss_action_response.rb
|
|
301
308
|
- lib/cem_acpt/test_runner/log_formatter/goss_error_formatter.rb
|
|
309
|
+
- lib/cem_acpt/test_runner/log_formatter/scan_result_formatter.rb
|
|
302
310
|
- lib/cem_acpt/test_runner/log_formatter/standard_error_formatter.rb
|
|
303
311
|
- lib/cem_acpt/test_runner/test_results.rb
|
|
304
312
|
- lib/cem_acpt/utils.rb
|
|
@@ -314,6 +322,8 @@ files:
|
|
|
314
322
|
- lib/terraform/gcp/linux/goss/puppet_noop.yaml
|
|
315
323
|
- lib/terraform/gcp/linux/log_service/log_service.rb
|
|
316
324
|
- lib/terraform/gcp/linux/main.tf
|
|
325
|
+
- lib/terraform/gcp/linux/scan/scan_service.rb
|
|
326
|
+
- lib/terraform/gcp/linux/scan/scan_service.service
|
|
317
327
|
- lib/terraform/gcp/linux/systemd/goss-acpt.service
|
|
318
328
|
- lib/terraform/gcp/linux/systemd/goss-idempotent.service
|
|
319
329
|
- lib/terraform/gcp/linux/systemd/goss-noop.service
|
|
@@ -324,6 +334,7 @@ files:
|
|
|
324
334
|
- lib/terraform/image/gcp/linux/main.tf
|
|
325
335
|
- lib/terraform/image/gcp/windows/.keep
|
|
326
336
|
- sample_config.yaml
|
|
337
|
+
- specifications/CEM-6511.md
|
|
327
338
|
- specifications/CEM-6713.md
|
|
328
339
|
- specifications/CEM-6714.md
|
|
329
340
|
- specifications/CEM-6715.md
|
|
@@ -331,6 +342,12 @@ files:
|
|
|
331
342
|
- specifications/CEM-6717.md
|
|
332
343
|
- specifications/CEM-6718.md
|
|
333
344
|
- specifications/CEM-6719.md
|
|
345
|
+
- specifications/CEM-6720.md
|
|
346
|
+
- specifications/CEM-6759.md
|
|
347
|
+
- specifications/CEM-6760.md
|
|
348
|
+
- specifications/CEM-6761.md
|
|
349
|
+
- specifications/CEM-6762.md
|
|
350
|
+
- specifications/CEM-6765.md
|
|
334
351
|
homepage: https://github.com/puppetlabs/cem_acpt
|
|
335
352
|
licenses:
|
|
336
353
|
- proprietary
|
|
@@ -338,6 +355,7 @@ metadata:
|
|
|
338
355
|
homepage_uri: https://github.com/puppetlabs/cem_acpt
|
|
339
356
|
source_code_uri: https://github.com/puppetlabs/cem_acpt
|
|
340
357
|
changelog_uri: https://github.com/puppetlabs/cem_acpt
|
|
358
|
+
post_install_message:
|
|
341
359
|
rdoc_options: []
|
|
342
360
|
require_paths:
|
|
343
361
|
- lib
|
|
@@ -352,7 +370,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
352
370
|
- !ruby/object:Gem::Version
|
|
353
371
|
version: '0'
|
|
354
372
|
requirements: []
|
|
355
|
-
rubygems_version: 3.
|
|
373
|
+
rubygems_version: 3.5.22
|
|
374
|
+
signing_key:
|
|
356
375
|
specification_version: 4
|
|
357
376
|
summary: CEM Acceptance Tests
|
|
358
377
|
test_files: []
|
data/lib/cem_acpt/provision.rb
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'logging'
|
|
4
|
-
require_relative 'provision/terraform'
|
|
5
|
-
|
|
6
|
-
module CemAcpt
|
|
7
|
-
module Provision
|
|
8
|
-
include CemAcpt::Logging
|
|
9
|
-
|
|
10
|
-
def self.new_provisioner(config, provision_data)
|
|
11
|
-
case config.get('provisioner')
|
|
12
|
-
when 'terraform'
|
|
13
|
-
logger.debug('CemAcpt::Provision') { 'Using Terraform provisioner' }
|
|
14
|
-
CemAcpt::Provision::Terraform.new(config, provision_data)
|
|
15
|
-
else
|
|
16
|
-
raise ArgumentError, "Unknown provisioner #{config.get('provisioner')}"
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|