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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68da70c79c3346b5ce515014b2202746db3f2ef2395c60aa4891ea15635f7e7b
4
- data.tar.gz: 0273522771cc02caac4f07e7b7c150f7ab3b7bbc4fc3b9126309819a5f607608
3
+ metadata.gz: b82bc3c9709dc1486de6675c33508a532f8c036fde480e6fd967e5a2e7dfdae8
4
+ data.tar.gz: 4fad921e96175030f521ac21a898ee3488a57b109e309f696980e044efc28123
5
5
  SHA512:
6
- metadata.gz: eff07c3af3f0692706e5863825bb324b6f9b1f821cebd54c0baad9b6b4ea30850d7307342188eaa9c81de6833650cb472f74b8bc378a333886117320033dfbd2
7
- data.tar.gz: 5c5cf88592e47515349bde762ee85baf5f07000b024d20e6f5fa550780641049b5b9cf514ab3a74a804fbfb8685c73fd6227271a7d2ea2aa734e9abc819e26f6
6
+ metadata.gz: b711d5b4ee1cf1f4c712d38b12c68fd3e20939a69e161004b1b2ecc0256838c04473bf089b86e1c78097db848197b6218617cbc31fb53ba52a2bd7dc84910b07
7
+ data.tar.gz: bd425c7655b89ce251c32d239b0dd7ac1333d0d17f262e08a8c997b388a065a900f6849404e31fbdd969d3e8042aeb934b276b2b002611c72e190e4094245a83
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cem_acpt (0.11.2)
4
+ cem_acpt (0.12.0)
5
5
  async-http (>= 0.60, < 0.70)
6
6
  bcrypt_pbkdf (>= 1.0, < 2.0)
7
7
  deep_merge (>= 1.2, < 2.0)
data/README.md CHANGED
@@ -27,6 +27,7 @@ cem_acpt --config ./cem_acpt_config.yaml
27
27
  ```bash
28
28
  cem_acpt -h | --help
29
29
  cem_acpt_image -h | --help
30
+ cem_acpt_scan -h | --help
30
31
  ```
31
32
 
32
33
  > If you do not delete Gemfile.lock before running `bundle install`, you may encounter dependency version errors. Just delete Gemfile.lock and run `bundle install` again to get past these.
@@ -297,6 +298,98 @@ Much like `name_pattern_vars`, specifying the `image_name_builder` top-level key
297
298
  * Destroy the test nodes
298
299
  * Report the results of the tests
299
300
 
301
+ ## Automated benchmark scans (`cem_acpt_scan`)
302
+
303
+ `cem_acpt_scan` is a third binary that provisions test nodes the same way `cem_acpt` does, applies the acceptance test case's `manifest.pp`, then runs a real benchmark scan against the node — OpenSCAP for STIG profiles, CIS-CAT Pro for CIS profiles — instead of executing Goss assertions. It exists for AI-driven and human workflows that need to validate a manifest change against the actual published benchmark, not just developer-authored Goss tests.
304
+
305
+ Linux only in this version. A Windows test case requested via `-t` causes the run to fail before any node is provisioned.
306
+
307
+ ### Quickstart
308
+
309
+ From the root of the consuming module (e.g. `sce_linux`):
310
+
311
+ ```bash
312
+ cem_acpt_scan --config ./cem_acpt_scan_config.yaml -t cis_rhel-8_firewalld_server_2
313
+ ```
314
+
315
+ `cem_acpt_scan` discovers test cases the same way `cem_acpt` does, but only `manifest.pp` is required in each test directory — `goss.yaml` is optional.
316
+
317
+ ### CLI flags
318
+
319
+ In addition to the shared flags (`-c/--config`, `-I/--CI`, `-q/--quiet`, `-v/--verbose`, `-L/--log-file`, `-Y`, `-X`, `-D/--no-destroy-nodes`, etc.), `cem_acpt_scan` adds:
320
+
321
+ | Flag | Effect |
322
+ | --- | --- |
323
+ | `-m, --module-dir DIR` | Path to the consuming module root. Same semantics as `cem_acpt`. |
324
+ | `-t, --tests TESTS` | Comma-separated list of acceptance-test case names to scan. |
325
+ | `--scanner SCANNER` | Override the scanner. One of `openscap`, `ciscat`. Default: derived from the test-case framework (`stig_*` → openscap, `cis_*` → ciscat). |
326
+ | `--threshold N` | Override the global pass/fail score threshold (float, 0–100). |
327
+ | `--scan-output FILE` | Save the normalized JSON report to `FILE`. Multi-case runs fan out to `FILE.<test_case>.json` so reports don't collide. Without this flag, the JSON is printed to stdout only. |
328
+
329
+ ### Configuration
330
+
331
+ Scan-mode settings live under the top-level `cem_acpt_scan` config key:
332
+
333
+ ```yaml
334
+ cem_acpt_scan:
335
+ # Global pass/fail threshold (scanner-native pass-rate, 0-100).
336
+ threshold: 80.0
337
+
338
+ # Per-test-case threshold overrides. Optional.
339
+ test_thresholds:
340
+ cis_rhel-8_firewalld_server_2: 75.0
341
+
342
+ # Required for any test case that resolves to the ciscat scanner.
343
+ # Local file path or gs:// URI; gs:// URIs are downloaded to a per-run
344
+ # cache file before being passed to Terraform.
345
+ cis_cat_pro_source: gs://my-bucket/cis-cat-pro-4.x.tar.gz
346
+ # cis_cat_pro_source: /opt/cis-cat-pro/Assessor-CLI-4.5.0.tar.gz
347
+
348
+ # Scanner-native profile id mapping, keyed by full acceptance-test
349
+ # directory name. Required: a missing entry fails before provisioning.
350
+ profiles:
351
+ openscap:
352
+ stig_rhel-8_firewalld_public_3: xccdf_org.ssgproject.content_profile_stig
353
+ cis_cat:
354
+ cis_rhel-8_firewalld_server_2: xccdf_org.cisecurity.benchmarks_profile_Level_2_-_Server
355
+
356
+ # On-node scan daemon (a small WebRick service installed at provision time).
357
+ daemon:
358
+ port: 8084
359
+ ready_timeout: 60
360
+ ```
361
+
362
+ ### Output
363
+
364
+ For each scanned test case, `cem_acpt_scan` prints a normalized JSON document to stdout:
365
+
366
+ ```json
367
+ {
368
+ "test_case": "cis_rhel-8_firewalld_server_2",
369
+ "scanner": "ciscat",
370
+ "profile": "xccdf_org.cisecurity.benchmarks_profile_Level_2_-_Server",
371
+ "score": 87.4,
372
+ "threshold": 80.0,
373
+ "passed_count": 187,
374
+ "failed_count": 27,
375
+ "not_applicable_count": 14,
376
+ "error_count": 0,
377
+ "rules": [
378
+ { "id": "...", "title": "...", "severity": "high", "result": "pass" }
379
+ ],
380
+ "pass": true
381
+ }
382
+ ```
383
+
384
+ `--scan-output FILE` writes the same JSON to disk. The exit code is `0` if every test case scored at or above its threshold, `1` if any scored below.
385
+
386
+ ### How it differs from `cem_acpt`
387
+
388
+ * The runner registers a single `:scan` action group instead of `:goss` and `:bolt`.
389
+ * The acceptance-test discovery predicate flips from `goss.yaml` to `manifest.pp` — scan-only test directories don't need a Goss file.
390
+ * The on-node Goss systemd services are skipped; in their place an on-node scan daemon is installed (mirroring the Goss server pattern but serving `/scan` and `/health` endpoints).
391
+ * Pass/fail is decided by the configured threshold, not by per-rule assertions.
392
+
300
393
  ## Generating acceptance test node images
301
394
 
302
395
  CemAcpt provides the command `cem_acpt_image` that allows you to generate new acceptance test node images that are then used with the `cem_acpt` command. This is useful for when you want to test a new version of Puppet or a new OS.
data/docs/ARCHITECTURE.md CHANGED
@@ -62,7 +62,6 @@ cem_acpt/
62
62
  │ │ ├── logging/ # log line formatters
63
63
  │ │ ├── platform.rb # dynamic loader for platform implementations
64
64
  │ │ ├── platform/ # base + GCP platform module
65
- │ │ ├── provision.rb # provisioner factory
66
65
  │ │ ├── provision/ # Terraform driver + OS-specific backends
67
66
  │ │ ├── test_runner.rb # main TestRunner::Runner
68
67
  │ │ ├── test_runner/ # log_formatter/, test_results.rb
@@ -170,9 +169,9 @@ ordering visible in code (`base.rb:125-155`) is:
170
169
  6. **`add_static_options!`** runs last as a single phase; internally
171
170
  it does two things:
172
171
  - 6a. Sets the framework-owned keys `user_config.dir`,
173
- `user_config.file`, `provisioner = 'terraform'`, and
174
- `terraform.dir`. Only these four keys clobber anything earlier;
175
- the rest of the merged config is untouched.
172
+ `user_config.file`, and `terraform.dir`. Only these three keys
173
+ clobber anything earlier; the rest of the merged config is
174
+ untouched.
176
175
  - 6b. Calls `set_third_party_env_vars!`: `RUNNER_DEBUG=1` forces
177
176
  `log_level=debug` and `verbose=true`; `GITHUB_ACTIONS=true` or
178
177
  `CI=true` forces `ci_mode=true`.
@@ -402,12 +401,11 @@ name, test name).
402
401
 
403
402
  ## 7. Provisioner: Terraform
404
403
 
405
- There is currently one provisioner. `Provision.new_provisioner`
406
- (`lib/cem_acpt/provision.rb`) returns
407
- `Provision::Terraform` for `provisioner == 'terraform'` and raises
408
- otherwise. `provisioner` is currently force-set to `'terraform'` in
409
- `Config::Base#add_static_options!`, so this dispatch is effectively
410
- fixed today.
404
+ There is currently one provisioner. `TestRunner#provision_test_nodes`
405
+ instantiates `Provision::Terraform` directly. A factory was removed
406
+ in CEM-6720 because it signaled an extension surface that did not
407
+ exist; re-introduce one when there is a concrete second
408
+ implementation.
411
409
 
412
410
  ### `Provision::Terraform`
413
411
 
@@ -1017,17 +1015,13 @@ These are things the exploration surfaced that don't seem load-bearing
1017
1015
  in the current code but would warrant a quick decision before
1018
1016
  spec-driven refactors.
1019
1017
 
1020
- 1. **`provisioner` is statically forced to `'terraform'`** in
1021
- `Config::Base#add_static_options!`, even though the `Provision`
1022
- factory branches on it. If we want a non-Terraform path in the
1023
- future the static set would need to become a default.
1024
- 2. **Platform constant cache.** `Platform.platform_class` caches the
1018
+ 1. **Platform constant cache.** `Platform.platform_class` caches the
1025
1019
  created class under `CemAcpt::Platform` (e.g.
1026
1020
  `CemAcpt::Platform::Gcp`) and uses `const_defined?(name, false)`,
1027
1021
  so unrelated same-named constants elsewhere in the graph cannot
1028
1022
  collide. Mixins live alongside under `CemAcpt::Platform::Mixin`.
1029
1023
  Resolved by [CEM-6717](rfcs/0008-namespace-platform-classes.md).
1030
- 3. **`lib/terraform/image/gcp/windows/` is an empty directory.** It
1024
+ 2. **`lib/terraform/image/gcp/windows/` is an empty directory.** It
1031
1025
  contains only `.keep` — no `main.tf`. As of the fix for
1032
1026
  [CEM-6712](rfcs/0003-windows-image-builder-template.md),
1033
1027
  `TerraformBuilder#assert_template_present!` raises
data/exe/cem_acpt_scan ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'dotenv/load'
5
+ require 'cem_acpt'
6
+ require 'cem_acpt/cli'
7
+
8
+ command, options = CemAcpt::Cli.parse_opts_for(:cem_acpt_scan)
9
+ # Set CLI defaults
10
+ options[:module_dir] = Dir.pwd unless options[:module_dir]
11
+ if (options[:log_level] == 'debug' || options[:verbose]) && !options[:quiet]
12
+ puts '############################### RUNNING ###############################'
13
+ puts "Command #{File.basename(__FILE__)}:#{command} using options from command line: #{options}"
14
+ puts '#######################################################################'
15
+ end
16
+ CemAcpt.run(command, File.basename(__FILE__).to_sym, options)
data/lib/cem_acpt/cli.rb CHANGED
@@ -92,6 +92,30 @@ module CemAcpt
92
92
  opts.on('-F', '--filter FILTER', 'Filter images to build by name with regex. Example: -F "(alma|rhel)-8"') do |f|
93
93
  options[:image_name_filter] = Regexp.new(f)
94
94
  end
95
+ when :cem_acpt_scan
96
+ opts.on('-m', '--module-dir DIR', 'Set module directory. Example: -m "/tmp/module"') do |m|
97
+ options[:module_dir] = m
98
+ end
99
+
100
+ opts.on('-t', '--tests TESTS', 'Set tests. Example: -t "test1,test2"') do |t|
101
+ options[:tests] = t.split(',')
102
+ end
103
+
104
+ opts.on('--scanner SCANNER', %w[openscap ciscat],
105
+ 'Override scanner choice. One of: openscap, ciscat. Default: derived from test-case framework.') do |s|
106
+ options[:cem_acpt_scan] ||= {}
107
+ options[:cem_acpt_scan][:scanner] = s
108
+ end
109
+
110
+ opts.on('--threshold N', Float, 'Override the global pass/fail score threshold (0-100).') do |n|
111
+ options[:cem_acpt_scan] ||= {}
112
+ options[:cem_acpt_scan][:threshold] = n
113
+ end
114
+
115
+ opts.on('--scan-output FILE', 'Save normalized JSON report to FILE. Multi-case runs fan out to FILE.<test_case>.json.') do |f|
116
+ options[:cem_acpt_scan] ||= {}
117
+ options[:cem_acpt_scan][:scan_output] = f
118
+ end
95
119
  end
96
120
 
97
121
  opts.on('-D', '--debug', 'Enable debug logging') do
@@ -52,7 +52,6 @@ module CemAcpt
52
52
  no_destroy_nodes
53
53
  no_ephemeral_ssh_key
54
54
  platform
55
- provisioner
56
55
  puppet
57
56
  quiet
58
57
  secrets
@@ -190,6 +189,16 @@ module CemAcpt
190
189
  end
191
190
  alias ci? ci_mode?
192
191
 
192
+ # Whether this config represents a scan run (cem_acpt_scan).
193
+ # Subclasses that drive a scan workflow override to true.
194
+ def scan_mode?
195
+ false
196
+ end
197
+
198
+ def scan?
199
+ scan_mode?
200
+ end
201
+
193
202
  def debug_mode?
194
203
  get('log_level') == 'debug'
195
204
  end
@@ -262,8 +271,6 @@ module CemAcpt
262
271
  add_config_explanation('user_config.dir', 'static value')
263
272
  config.dset('user_config.file', user_config_file)
264
273
  add_config_explanation('user_config.file', 'static value')
265
- config.dset('provisioner', 'terraform')
266
- add_config_explanation('provisioner', 'static value')
267
274
  config.dset('terraform.dir', terraform_dir)
268
275
  add_config_explanation('terraform.dir', 'static value')
269
276
  set_third_party_env_vars!(config)
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module CemAcpt
6
+ module Config
7
+ # Holds the configuration for cem_acpt_scan.
8
+ #
9
+ # Mirrors the {Config::CemAcpt} pattern: a hardcoded VALID_KEYS list and a
10
+ # {#defaults} hash. The new top-level key {cem_acpt_scan} carries
11
+ # scan-specific settings (scanner choice, threshold, profile mapping,
12
+ # daemon port, CIS-CAT Pro source); the rest of the schema reuses keys
13
+ # already understood by other commands so node provisioning code does not
14
+ # have to special-case scan mode.
15
+ class CemAcptScan < Base
16
+ VALID_KEYS = %i[
17
+ cem_acpt_scan
18
+ actions
19
+ image_name_builder
20
+ module_dir
21
+ node_data
22
+ test_data
23
+ tests
24
+ ].freeze
25
+
26
+ def env_var_prefix
27
+ 'CEM_ACPT_SCAN'
28
+ end
29
+
30
+ def scan_mode?
31
+ true
32
+ end
33
+
34
+ # The default configuration
35
+ def defaults
36
+ {
37
+ actions: {},
38
+ cem_acpt_scan: {
39
+ scanner: nil, # nil = derive from test-case framework (cis -> ciscat, stig -> openscap)
40
+ threshold: 80.0,
41
+ test_thresholds: {},
42
+ scan_output: nil, # nil = stdout only; otherwise a file path
43
+ cis_cat_pro_source: nil, # local path or gs:// URI; required when any test resolves to ciscat
44
+ cis_cat_pro_license: nil, # local path or gs:// URI to license bundle (.tar.gz or .zip); required when any test resolves to ciscat. Rotates independently of cis_cat_pro_source.
45
+ daemon: {
46
+ port: 8084,
47
+ ready_timeout: 60,
48
+ },
49
+ profiles: {
50
+ openscap: {},
51
+ cis_cat: {},
52
+ },
53
+ benchmarks: {
54
+ cis_cat: {},
55
+ },
56
+ },
57
+ ci_mode: false,
58
+ config_file: nil,
59
+ image_name_builder: {
60
+ character_substitutions: [['_', '-']],
61
+ parts: ['cem-acpt', '$image_fam', '$collection', '$firewall'],
62
+ join_with: '-',
63
+ },
64
+ log_level: 'info',
65
+ log_file: nil,
66
+ log_format: 'text',
67
+ module_dir: Dir.pwd,
68
+ node_data: {},
69
+ no_destroy_nodes: false,
70
+ no_ephemeral_ssh_key: false,
71
+ platform: {
72
+ name: 'gcp',
73
+ gcp: {
74
+ windows_bucket: 'win_cem_acpt',
75
+ },
76
+ },
77
+ quiet: false,
78
+ test_data: {
79
+ for_each: {
80
+ collection: %w[puppet8],
81
+ },
82
+ vars: {},
83
+ name_pattern_vars: %r{^(?<framework>[a-z]+)_(?<image_fam>[a-z0-9-]+)_(?<firewall>[a-z]+)_(?<framework_vars>[-_a-z0-9]+)$},
84
+ vars_post_processing: {
85
+ new_vars: [
86
+ {
87
+ name: 'profile',
88
+ string_split: {
89
+ from: 'framework_vars',
90
+ using: '_',
91
+ part: 0,
92
+ },
93
+ },
94
+ {
95
+ name: 'level',
96
+ string_split: {
97
+ from: 'framework_vars',
98
+ using: '_',
99
+ part: 1,
100
+ },
101
+ },
102
+ ],
103
+ delete_vars: %w[framework_vars],
104
+ },
105
+ },
106
+ tests: [],
107
+ verbose: false,
108
+ }
109
+ end
110
+ end
111
+ end
112
+ end
@@ -5,5 +5,6 @@ module CemAcpt
5
5
  module Config
6
6
  require_relative 'config/cem_acpt'
7
7
  require_relative 'config/cem_acpt_image'
8
+ require_relative 'config/cem_acpt_scan'
8
9
  end
9
10
  end
@@ -37,19 +37,16 @@ module CemAcpt
37
37
  @gcp_username = @config.get('platform.username')
38
38
  return @gcp_username if @gcp_username
39
39
 
40
- stdout, stderr, status = Open3.capture3('gcloud compute os-login describe-profile --format json')
41
- raise "gcloud os-login describe-profile failed (exit #{status.exitstatus}): #{stderr.chomp}" unless status.success?
40
+ stdout, stderr, status = Open3.capture3('gcloud auth list --format json')
41
+ raise "gcloud auth list failed (exit #{status.exitstatus}): #{stderr.chomp}" unless status.success?
42
42
 
43
43
  parsed = JSON.parse(stdout)
44
- accounts = parsed['posixAccounts']
45
- raise "gcloud os-login profile returned no posixAccounts. stderr: #{stderr.chomp}" if accounts.nil? || accounts.empty?
44
+ active_account = parsed.find { |account| account['status'] == 'ACTIVE' }
45
+ raise 'No active account found in gcloud auth list output' unless active_account
46
46
 
47
- username = accounts.first['username']
48
- raise "gcloud os-login posixAccounts entry has no username. stdout: #{stdout.chomp}" unless username
49
-
50
- @gcp_username = username
47
+ @gcp_username = active_account['account'].split('@').first
51
48
  rescue JSON::ParserError => e
52
- raise "Failed to parse gcloud os-login output: #{e.message}. stdout: #{stdout.chomp}"
49
+ raise "Failed to parse gcloud auth output: #{e.message}. stdout: #{stdout.chomp}"
53
50
  end
54
51
 
55
52
  def gcp_credentials_file
@@ -47,31 +47,77 @@ module CemAcpt
47
47
  commands << apply_command
48
48
  end
49
49
 
50
+ # Commands to run on a scan-mode node. Skips Goss installation and the
51
+ # Goss systemd units (they are not used in scan mode), installs the
52
+ # scan daemon and its scanners (including a Java JRE because CIS-CAT
53
+ # Pro's Assessor-CLI is a Java app), then applies the puppet manifest.
54
+ # The CIS-CAT Pro tarball is uploaded *and* extracted by the
55
+ # cis_cat_pro_upload null_resource in main.tf, so this method does not
56
+ # touch /opt/cis-cat-pro/.
57
+ # @return [Array<String>] commands for `provisioner "remote-exec"`.
58
+ def scan_provision_commands
59
+ commands = [
60
+ "sudo /opt/puppetlabs/puppet/bin/puppet module install #{destination_provision_directory}/#{remote_module_package_name}",
61
+ 'sudo /opt/puppetlabs/puppet/bin/gem install webrick',
62
+ 'sudo chmod +x /opt/cem_acpt/log_service/log_service.rb',
63
+ 'sudo /opt/cem_acpt/log_service/log_service.rb',
64
+ install_scanner_packages_command,
65
+ install_java_command,
66
+ 'sudo chmod +x /opt/cem_acpt/scan/scan_service.rb',
67
+ 'sudo cp /opt/cem_acpt/scan/scan_service.service /etc/systemd/system/scan_service.service',
68
+ 'sudo systemctl daemon-reload',
69
+ 'sudo systemctl start scan_service.service && sudo systemctl enable scan_service.service',
70
+ ]
71
+ commands << apply_command
72
+ commands
73
+ end
74
+
50
75
  # A wrapper around provision_commands that allows for extra commands to be added for a specific OS version(i.e EL 8)
51
76
  # @param image_name [String] The name of the OS image being provisioned.
77
+ # @param scan_mode [Boolean] When true, returns the scan-mode command list
78
+ # (Goss-free, scan-daemon-installed) wrapped with the same OS-specific prefixes.
52
79
  # @return [Array<String>] An array of shell commands to be run on the provisioned nodes to set up the necessary
53
80
  # environment for running the Puppet manifest, including any additional commands needed for specific OS
54
81
  # versions.
55
- def provision_commands_wrapper(image_name)
82
+ def provision_commands_wrapper(image_name, scan_mode: false)
83
+ base = scan_mode ? scan_provision_commands : provision_commands
56
84
  if ['rhel-8', 'oel-8', 'alma-8', 'rocky-8'].any? { |el8| image_name.include?(el8) }
57
85
  commands = [
58
86
  'sudo dnf upgrade --refresh -y rpm glibc',
59
87
  'sudo rm /var/lib/rpm/.rpm.lock',
60
88
  'sudo dnf upgrade -y dnf',
61
89
  ]
62
- commands = (commands << provision_commands).flatten
63
- commands
90
+ (commands << base).flatten
64
91
  elsif image_name.include?('ubuntu')
65
92
  commands = ['sudo apt purge -y unattended-upgrades', 'sudo apt-get update -y']
66
- commands = (commands << provision_commands).flatten
67
- commands
93
+ (commands << base).flatten
68
94
  else
69
- provision_commands
95
+ base
70
96
  end
71
97
  end
72
98
 
73
99
  private
74
100
 
101
+ # Returns the OS-package install command for OpenSCAP. Both EL and Ubuntu
102
+ # ship `oscap`; the SCAP content package differs by distro family. The
103
+ # command is intentionally OR-chained so it succeeds on whichever package
104
+ # manager is present without needing fact-based dispatch.
105
+ # @return [String] shell command to install the OpenSCAP scanner.
106
+ def install_scanner_packages_command
107
+ # Try dnf first (RHEL/Alma/Rocky/Oracle), fall back to apt (Ubuntu).
108
+ 'sudo dnf install -y openscap-scanner scap-security-guide || sudo apt-get install -y libopenscap8 ssg-base ssg-debderived'
109
+ end
110
+
111
+ # Returns the OS-package install command for a headless Java runtime.
112
+ # CIS-CAT Pro's Assessor-CLI.sh is a Java application, so the JRE must
113
+ # be present on every scan-mode node regardless of which scanner the
114
+ # test case resolves to (installing unconditionally is cheap, and
115
+ # threading scanner-resolution into the provisioner is not worth it).
116
+ # @return [String] shell command to install a headless JRE.
117
+ def install_java_command
118
+ 'sudo dnf install -y java-11-openjdk-headless || sudo apt-get install -y default-jre-headless'
119
+ end
120
+
75
121
  # @return [String] The command to apply the Puppet manifest on the provisioned nodes, including any necessary
76
122
  # options for logging and debugging. This command is constructed based on the configuration settings for the
77
123
  # provisioner, and may include additional options for debug and verbose logging if those settings are enabled.