compliance_engine 0.4.0 → 0.6.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/AGENTS.md +2 -0
- data/CHANGELOG.md +15 -0
- data/compliance_engine.gemspec +1 -1
- data/lib/compliance_engine/collection.rb +15 -1
- data/lib/compliance_engine/data.rb +59 -15
- data/lib/compliance_engine/data_version.rb +21 -0
- data/lib/compliance_engine/environment_loader/zip.rb +13 -10
- data/lib/compliance_engine/environment_loader/zip_bytes.rb +27 -0
- data/lib/compliance_engine/environment_loader.rb +4 -2
- data/lib/compliance_engine/module_loader.rb +41 -23
- data/lib/compliance_engine/sce-schema.json +517 -0
- data/lib/compliance_engine/tolerance.rb +28 -0
- data/lib/compliance_engine/version.rb +1 -21
- data/lib/compliance_engine.rb +39 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9f8203b0a0381216d9a1a91c318f0f162bde1d4835bc7731c954156371b6f9b8
|
|
4
|
+
data.tar.gz: 784264cbb1aba5772bbf97dafdd952c3b8823fa404236c372af87f1d6dad885b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1026ff097b4e46f31b59b2a584e9d25b94baa986014564fa6a0196d70224306956fd0499d923fa19b0c1035d248c490a3ffaaa1bbcf8f55bd222e7726fb25157
|
|
7
|
+
data.tar.gz: 588997e4f32f95f7d1edef560a16a09399c59d1b7aba4677ca6cf0bf9d97f38568d9d14c9349a5e489288d16dffe0abc2abc0147cecc9beb5127701270d3594a
|
data/AGENTS.md
CHANGED
|
@@ -91,6 +91,8 @@ A component can have multiple **fragments** (one per source file), which are dee
|
|
|
91
91
|
| CE→Control overlap | Any of `check.ces`' CEs has a control that also appears in `profile.controls` |
|
|
92
92
|
| Direct reference | `profile.checks[check_key]` is truthy |
|
|
93
93
|
|
|
94
|
+
An entry on the **profile side** with value `false` is treated as an **explicit exclusion** that overrides positive matches: `profile.checks[X] = false` hard-excludes check `X` from every route, and `profile.ces[X] = false` prevents CE `X` from being used as either a direct CE match or as a CE→Control bridge. On the **check side**, `false` is local-only (e.g. `check.controls[X] = false` simply means the check is not associated with control `X`); `check.ces` is an array, so no `false` handling applies.
|
|
95
|
+
|
|
94
96
|
`check_mapping` can also be called with CE objects (in addition to profiles). Results are cached by `"#{object.class}:#{object.key}"`.
|
|
95
97
|
|
|
96
98
|
### Loading Pipeline
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
### 0.6.0 / 2026-05-05
|
|
2
|
+
* Honor explicit `false` in profile `checks` and `ces` mappings as exclusions that override positive matches (#117)
|
|
3
|
+
* Coerce non-String `zipfile.name` to `-` for buffer-opened zips (#121)
|
|
4
|
+
* Replace `Zip::File` handoff with `open_environment_zip_bytes` (#124)
|
|
5
|
+
|
|
6
|
+
### 0.5.0 / 2026-04-29
|
|
7
|
+
* Add JSON Schema for SCE data, exposed via `ComplianceEngine.schema` (#49)
|
|
8
|
+
* Add `ComplianceEngine::Tolerance` constants for enforcement tolerance levels (#107)
|
|
9
|
+
* Drop stale file data on environment rescan (#108)
|
|
10
|
+
* Handle malformed compliance data gracefully instead of raising (#111)
|
|
11
|
+
* Make dotfile loading opt-in (defaults to off) (#112)
|
|
12
|
+
* Accept `::Zip::File` objects directly in `EnvironmentLoader::Zip` (#114)
|
|
13
|
+
* Support `compliance_markup`-style parameter knockout prefixes
|
|
14
|
+
* Split `version.rb`: separate gem `VERSION` from data format validator (#104)
|
|
15
|
+
|
|
1
16
|
### 0.4.0 / 2026-04-20
|
|
2
17
|
* Route Puppet Hiera backend log messages through Puppet's logging system (#96)
|
|
3
18
|
* Fix JRuby compatibility: convert internal requires to require_relative so the library loads correctly inside Puppet Server / OpenVox Server when installed as a Puppet module rather than a standalone gem
|
data/compliance_engine.gemspec
CHANGED
|
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
|
19
19
|
spec.metadata['bug_tracker_uri'] = 'https://github.com/simp/rubygem-simp-compliance_engine/issues'
|
|
20
20
|
|
|
21
21
|
# Specify which files should be added to the gem when it is released.
|
|
22
|
-
spec.files = Dir.glob(['*.gemspec', '*.md', 'LICENSE', 'exe/*', 'lib/**/*.rb']).reject { |f| f.start_with?('lib/puppet/') }
|
|
22
|
+
spec.files = Dir.glob(['*.gemspec', '*.md', 'LICENSE', 'exe/*', 'lib/**/*.rb', 'lib/**/*.json']).reject { |f| f.start_with?('lib/puppet/') }
|
|
23
23
|
spec.bindir = 'exe'
|
|
24
24
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
25
25
|
spec.require_paths = ['lib']
|
|
@@ -12,7 +12,21 @@ class ComplianceEngine::Collection
|
|
|
12
12
|
@collection ||= {}
|
|
13
13
|
hash_key = key
|
|
14
14
|
data.files.each do |file|
|
|
15
|
-
data.get(file)
|
|
15
|
+
file_data = data.get(file)
|
|
16
|
+
unless file_data.is_a?(Hash)
|
|
17
|
+
ComplianceEngine.log.debug "Skipping #{file}: expected Hash content, got #{file_data.class}"
|
|
18
|
+
next
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
entries = file_data[hash_key]
|
|
22
|
+
next if entries.nil?
|
|
23
|
+
|
|
24
|
+
unless entries.is_a?(Hash)
|
|
25
|
+
ComplianceEngine.log.error "Expected '#{hash_key}' in #{file} to be a Hash, got #{entries.class}"
|
|
26
|
+
next
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
entries.each do |k, v|
|
|
16
30
|
@collection[k] ||= collected.new(k, data: self)
|
|
17
31
|
@collection[k].add(file, v)
|
|
18
32
|
end
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'set'
|
|
3
4
|
require_relative '../compliance_engine'
|
|
4
|
-
require_relative '
|
|
5
|
+
require_relative 'data_version'
|
|
5
6
|
require_relative 'component'
|
|
6
7
|
require_relative 'ce'
|
|
7
8
|
require_relative 'check'
|
|
@@ -124,13 +125,29 @@ class ComplianceEngine::Data
|
|
|
124
125
|
nil
|
|
125
126
|
end
|
|
126
127
|
|
|
127
|
-
# Scan a Puppet environment from a zip file
|
|
128
|
-
# @param path [String]
|
|
128
|
+
# Scan a Puppet environment from a zip file on disk
|
|
129
|
+
# @param path [String] filesystem path to the zip archive
|
|
130
|
+
# @param name [String, nil] stable string identifier used as the modulepath
|
|
131
|
+
# and cache-key prefix; defaults to the full path string passed as +path+.
|
|
129
132
|
# @return [NilClass]
|
|
130
|
-
def open_environment_zip(path)
|
|
133
|
+
def open_environment_zip(path, name: nil)
|
|
131
134
|
require 'compliance_engine/environment_loader/zip'
|
|
132
135
|
|
|
133
|
-
environment = ComplianceEngine::EnvironmentLoader::Zip.new(path)
|
|
136
|
+
environment = ComplianceEngine::EnvironmentLoader::Zip.new(path, name: name)
|
|
137
|
+
self.modulepath = environment.modulepath
|
|
138
|
+
open(environment)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Scan a Puppet environment from a raw zip byte string
|
|
142
|
+
#
|
|
143
|
+
# @param bytes [String] raw binary zip data (e.g. from File.binread or an HTTP body)
|
|
144
|
+
# @param name [String, nil] stable string identifier used as the modulepath
|
|
145
|
+
# and cache-key prefix; defaults to "-" when no filename is available.
|
|
146
|
+
# @return [NilClass]
|
|
147
|
+
def open_environment_zip_bytes(bytes, name: nil)
|
|
148
|
+
require 'compliance_engine/environment_loader/zip_bytes'
|
|
149
|
+
|
|
150
|
+
environment = ComplianceEngine::EnvironmentLoader::ZipBytes.new(bytes, name: name)
|
|
134
151
|
self.modulepath = environment.modulepath
|
|
135
152
|
open(environment)
|
|
136
153
|
end
|
|
@@ -161,9 +178,17 @@ class ComplianceEngine::Data
|
|
|
161
178
|
|
|
162
179
|
if path.is_a?(ComplianceEngine::ModuleLoader)
|
|
163
180
|
modules[path.name] = path.version unless path.name.nil?
|
|
164
|
-
path.files.
|
|
165
|
-
|
|
166
|
-
|
|
181
|
+
new_keys = path.files.to_set(&:key)
|
|
182
|
+
module_root = if path.zipfile_path
|
|
183
|
+
::File.join(path.zipfile_path, '.', path.path.sub(%r{^/+}, ''))
|
|
184
|
+
else
|
|
185
|
+
path.path
|
|
186
|
+
end
|
|
187
|
+
module_prefix = ::File.join(module_root, '')
|
|
188
|
+
stale_keys = data.keys.select { |k| (k == module_root || k.start_with?(module_prefix)) && !new_keys.include?(k) }
|
|
189
|
+
stale_keys.each { |k| data.delete(k) }
|
|
190
|
+
path.files.each { |file_loader| update(file_loader) }
|
|
191
|
+
reset_collection if path.files.empty? && !stale_keys.empty?
|
|
167
192
|
next
|
|
168
193
|
end
|
|
169
194
|
|
|
@@ -221,7 +246,7 @@ class ComplianceEngine::Data
|
|
|
221
246
|
loader.add_observer(self, :update)
|
|
222
247
|
data[key] = {
|
|
223
248
|
loader: loader,
|
|
224
|
-
version: ComplianceEngine::
|
|
249
|
+
version: ComplianceEngine::DataVersion.new(loader.data['version']),
|
|
225
250
|
content: loader.data,
|
|
226
251
|
}
|
|
227
252
|
else
|
|
@@ -237,7 +262,7 @@ class ComplianceEngine::Data
|
|
|
237
262
|
data[filename.key][:loader] = filename
|
|
238
263
|
data[filename.key][:loader].add_observer(self, :update)
|
|
239
264
|
end
|
|
240
|
-
data[filename.key][:version] = ComplianceEngine::
|
|
265
|
+
data[filename.key][:version] = ComplianceEngine::DataVersion.new(filename.data['version'])
|
|
241
266
|
data[filename.key][:content] = filename.data
|
|
242
267
|
end
|
|
243
268
|
|
|
@@ -306,7 +331,8 @@ class ComplianceEngine::Data
|
|
|
306
331
|
v.to_a.each do |component|
|
|
307
332
|
next unless component.key?('confine')
|
|
308
333
|
|
|
309
|
-
|
|
334
|
+
confine = component['confine'].transform_values { |val| val.is_a?(Array) ? val.dup : Array(val) }
|
|
335
|
+
@confines = DeepMerge.deep_merge!(confine, @confines, knockout_prefix: '--')
|
|
310
336
|
end
|
|
311
337
|
end
|
|
312
338
|
end
|
|
@@ -348,10 +374,24 @@ class ComplianceEngine::Data
|
|
|
348
374
|
|
|
349
375
|
valid_profiles.reverse_each do |profile|
|
|
350
376
|
check_mapping(profile).each_value do |check|
|
|
351
|
-
|
|
377
|
+
hiera_data = check.hiera
|
|
378
|
+
next if hiera_data.nil?
|
|
379
|
+
|
|
380
|
+
parameters = DeepMerge.deep_merge!(Marshal.load(Marshal.dump(hiera_data)), parameters)
|
|
352
381
|
end
|
|
353
382
|
end
|
|
354
383
|
|
|
384
|
+
# deep_merge does not support hash-key knockout via knockout_prefix.
|
|
385
|
+
# Handle parameter-name knockout explicitly: any key starting with '--'
|
|
386
|
+
# signals that the matching key without the prefix should be suppressed
|
|
387
|
+
# (mirrors compliance_markup behavior).
|
|
388
|
+
parameters.each_key do |key|
|
|
389
|
+
next unless key.start_with?('--')
|
|
390
|
+
|
|
391
|
+
parameters.delete(key.delete_prefix('--'))
|
|
392
|
+
parameters.delete(key)
|
|
393
|
+
end
|
|
394
|
+
|
|
355
395
|
@hiera[cache_key] = parameters
|
|
356
396
|
end
|
|
357
397
|
|
|
@@ -415,6 +455,9 @@ class ComplianceEngine::Data
|
|
|
415
455
|
cache_key = [check.key, "#{profile_or_ce.class}:#{profile_or_ce.key}"].to_s
|
|
416
456
|
return @mapping[cache_key] if @mapping.key?(cache_key)
|
|
417
457
|
|
|
458
|
+
# An explicit `false` on the profile side hard-excludes the check, regardless of any other route.
|
|
459
|
+
return @mapping[cache_key] = false if profile_or_ce.is_a?(ComplianceEngine::Profile) && profile_or_ce.checks&.dig(check.key) == false
|
|
460
|
+
|
|
418
461
|
# Correlate based on controls
|
|
419
462
|
controls = check.controls&.select { |_, v| v }&.map { |k, _| k }
|
|
420
463
|
|
|
@@ -433,9 +476,10 @@ class ComplianceEngine::Data
|
|
|
433
476
|
# Correlate based on CEs
|
|
434
477
|
return @mapping[cache_key] = true if correlate(check.ces, profile_or_ce.ces)
|
|
435
478
|
|
|
436
|
-
# Correlate based on CEs and controls
|
|
437
|
-
|
|
438
|
-
return @mapping[cache_key] = true if
|
|
479
|
+
# Correlate based on CEs and controls. A CE explicitly set to `false` in the profile
|
|
480
|
+
# cannot be used as a bridge; absence (nil) still allows the bridge.
|
|
481
|
+
return @mapping[cache_key] = true if profile_or_ce.ces&.any? { |k, v| v && correlate(controls, ces[k]&.controls) }
|
|
482
|
+
return @mapping[cache_key] = true if check.ces&.any? { |ce| profile_or_ce.ces&.dig(ce) != false && ces[ce]&.controls&.any? { |k, v| v && profile_or_ce.controls&.dig(k) } }
|
|
439
483
|
|
|
440
484
|
@mapping[cache_key] = false
|
|
441
485
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../compliance_engine'
|
|
4
|
+
|
|
5
|
+
# Validates the version field found in compliance data files.
|
|
6
|
+
# Currently only version 2.0.0 of the data format is supported.
|
|
7
|
+
class ComplianceEngine::DataVersion
|
|
8
|
+
# @param version [String] the version string from a compliance data file
|
|
9
|
+
# @raise [ComplianceEngine::Error] if version is nil or not '2.0.0'
|
|
10
|
+
def initialize(version)
|
|
11
|
+
raise ComplianceEngine::Error, 'Missing version' if version.nil?
|
|
12
|
+
raise ComplianceEngine::Error, "Unsupported version '#{version}'" unless version == '2.0.0'
|
|
13
|
+
|
|
14
|
+
@version = version
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [String]
|
|
18
|
+
def to_s
|
|
19
|
+
@version
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -7,18 +7,21 @@ require 'zip/filesystem'
|
|
|
7
7
|
# Load compliance engine data from a zip file containing a Puppet environment
|
|
8
8
|
class ComplianceEngine::EnvironmentLoader::Zip < ComplianceEngine::EnvironmentLoader
|
|
9
9
|
# Initialize a ComplianceEngine::EnvironmentLoader::Zip object from a zip
|
|
10
|
-
# file and an optional root directory.
|
|
10
|
+
# file path and an optional root directory.
|
|
11
11
|
#
|
|
12
|
-
# @param
|
|
12
|
+
# @param input [String] filesystem path to a zip file
|
|
13
13
|
# @param root [String] a directory within the zip file to use as the root of the environment
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
# @param load_dotfiles [Boolean] whether to load dotfiles; defaults to true to
|
|
15
|
+
# preserve the historical zip-loader behaviour of including all files
|
|
16
|
+
# @param name [String, nil] identifier used for modulepath and downstream
|
|
17
|
+
# cache keys; defaults to the full path string passed as +input+.
|
|
18
|
+
def initialize(input, root: '/'.dup, load_dotfiles: true, name: nil)
|
|
19
|
+
raise ArgumentError, "input must be a String path, got #{input.class}" unless input.is_a?(String)
|
|
16
20
|
|
|
17
|
-
::Zip::File.open(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
end
|
|
21
|
+
zipfile = ::Zip::File.open(input)
|
|
22
|
+
@modulepath = name || input.to_s
|
|
23
|
+
super(root, fileclass: zipfile.file, dirclass: zipfile.dir, zipfile_path: @modulepath, load_dotfiles: load_dotfiles)
|
|
24
|
+
ensure
|
|
25
|
+
zipfile&.close
|
|
23
26
|
end
|
|
24
27
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../compliance_engine'
|
|
4
|
+
require_relative '../environment_loader'
|
|
5
|
+
# zip/filesystem must be required before any Zip::File instance is opened so
|
|
6
|
+
# that the per-instance dir/file accessors are wired up at open time.
|
|
7
|
+
require 'zip/filesystem'
|
|
8
|
+
require 'zip'
|
|
9
|
+
|
|
10
|
+
# Load compliance engine data from a raw zip byte string containing a Puppet environment
|
|
11
|
+
class ComplianceEngine::EnvironmentLoader::ZipBytes < ComplianceEngine::EnvironmentLoader
|
|
12
|
+
# @param bytes [String] raw binary zip data (e.g. from File.binread or an HTTP body)
|
|
13
|
+
# @param root [String] a directory within the zip file to use as the root of the environment
|
|
14
|
+
# @param load_dotfiles [Boolean] whether to load dotfiles; defaults to true to
|
|
15
|
+
# preserve the historical zip-loader behaviour of including all files
|
|
16
|
+
# @param name [String, nil] identifier used for modulepath and downstream
|
|
17
|
+
# cache keys; defaults to "-" when no filename is available.
|
|
18
|
+
def initialize(bytes, root: '/'.dup, load_dotfiles: true, name: nil)
|
|
19
|
+
raise ArgumentError, "bytes must be a String, got #{bytes.class}" unless bytes.is_a?(String)
|
|
20
|
+
|
|
21
|
+
zipfile = ::Zip::File.open_buffer(bytes)
|
|
22
|
+
@modulepath = name || '-'
|
|
23
|
+
super(root, fileclass: zipfile.file, dirclass: zipfile.dir, zipfile_path: @modulepath, load_dotfiles: load_dotfiles)
|
|
24
|
+
ensure
|
|
25
|
+
zipfile&.close
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -11,7 +11,9 @@ class ComplianceEngine::EnvironmentLoader
|
|
|
11
11
|
# @param fileclass [File] the class to use for file operations (default: `File`)
|
|
12
12
|
# @param dirclass [Dir] the class to use for directory operations (default: `Dir`)
|
|
13
13
|
# @param zipfile_path [String, nil] the path to the zip file if loading from a zip archive
|
|
14
|
-
|
|
14
|
+
# @param load_dotfiles [Boolean] whether to load dotfiles; passed through to
|
|
15
|
+
# each ModuleLoader (default: false)
|
|
16
|
+
def initialize(*paths, fileclass: File, dirclass: Dir, zipfile_path: nil, load_dotfiles: false)
|
|
15
17
|
raise ArgumentError, 'No paths specified' if paths.empty?
|
|
16
18
|
|
|
17
19
|
@modulepath ||= paths
|
|
@@ -26,7 +28,7 @@ class ComplianceEngine::EnvironmentLoader
|
|
|
26
28
|
[]
|
|
27
29
|
end
|
|
28
30
|
modules.flatten!
|
|
29
|
-
@modules = modules.map { |path| ComplianceEngine::ModuleLoader.new(path, fileclass: fileclass, dirclass: dirclass, zipfile_path: @zipfile_path) }
|
|
31
|
+
@modules = modules.map { |path| ComplianceEngine::ModuleLoader.new(path, fileclass: fileclass, dirclass: dirclass, zipfile_path: @zipfile_path, load_dotfiles: load_dotfiles) }
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
attr_reader :modulepath, :modules, :zipfile_path
|
|
@@ -12,12 +12,18 @@ class ComplianceEngine::ModuleLoader
|
|
|
12
12
|
# @param fileclass [File] the class to use for file operations (default: `File`)
|
|
13
13
|
# @param dirclass [Dir] the class to use for directory operations (default: `Dir`)
|
|
14
14
|
# @param zipfile_path [String, nil] the path to the zip file if loading from a zip archive
|
|
15
|
-
|
|
15
|
+
# @param load_dotfiles [Boolean] whether to load files whose relative path contains
|
|
16
|
+
# a component (directory or filename) beginning with '.'. Defaults to false so that
|
|
17
|
+
# dotfiles are skipped during normal module scanning, matching the behavior of
|
|
18
|
+
# Ruby's Dir.glob on real filesystems. Set to true only when the caller explicitly
|
|
19
|
+
# needs dotfile support (e.g. zip-based environment loading).
|
|
20
|
+
def initialize(path, fileclass: File, dirclass: Dir, zipfile_path: nil, load_dotfiles: false)
|
|
16
21
|
raise ComplianceEngine::Error, "#{path} is not a directory" unless fileclass.directory?(path)
|
|
17
22
|
|
|
18
23
|
@name = nil
|
|
19
24
|
@version = nil
|
|
20
25
|
@files = []
|
|
26
|
+
@path = path.to_s
|
|
21
27
|
@zipfile_path = zipfile_path
|
|
22
28
|
|
|
23
29
|
# Read the Puppet module's metadata.json
|
|
@@ -34,30 +40,42 @@ class ComplianceEngine::ModuleLoader
|
|
|
34
40
|
|
|
35
41
|
# In this directory, we want to look for all yaml and json files
|
|
36
42
|
# under SIMP/compliance_profiles and simp/compliance_profiles.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
43
|
+
# The loops are structured this way (rather than building a flat globs
|
|
44
|
+
# array first) so that each glob result can be checked against its
|
|
45
|
+
# base directory for dotfile filtering.
|
|
46
|
+
['SIMP/compliance_profiles', 'simp/compliance_profiles'].each do |dir|
|
|
47
|
+
base = File.join(path, dir)
|
|
48
|
+
next unless fileclass.directory?(base)
|
|
49
|
+
|
|
50
|
+
# Using .each here to make mocking with rspec easier.
|
|
51
|
+
['yaml', 'json'].each do |type|
|
|
52
|
+
dirclass.glob(File.join(base, '**', "*.#{type}")).sort.each do |file|
|
|
53
|
+
unless load_dotfiles
|
|
54
|
+
# Skip any file whose path (relative to the compliance_profiles
|
|
55
|
+
# base) contains a component beginning with '.', e.g. hidden
|
|
56
|
+
# files (.profile.yaml) or files inside hidden directories
|
|
57
|
+
# (.hidden/profile.yaml).
|
|
58
|
+
relative = file.to_s.delete_prefix("#{base}/")
|
|
59
|
+
next if relative.split('/').any? { |part| part.start_with?('.') }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
key = if @zipfile_path
|
|
63
|
+
File.join(@zipfile_path, '.', file.to_s)
|
|
64
|
+
else
|
|
65
|
+
file.to_s
|
|
66
|
+
end
|
|
67
|
+
loader = if File.extname(file.to_s) == '.json'
|
|
68
|
+
ComplianceEngine::DataLoader::Json.new(file.to_s, fileclass: fileclass, key: key)
|
|
69
|
+
else
|
|
70
|
+
ComplianceEngine::DataLoader::Yaml.new(file.to_s, fileclass: fileclass, key: key)
|
|
71
|
+
end
|
|
72
|
+
@files << loader
|
|
73
|
+
rescue StandardError => e
|
|
74
|
+
ComplianceEngine.log.warn "Could not load #{file}: #{e.message}"
|
|
75
|
+
end
|
|
58
76
|
end
|
|
59
77
|
end
|
|
60
78
|
end
|
|
61
79
|
|
|
62
|
-
attr_reader :name, :version, :files, :zipfile_path
|
|
80
|
+
attr_reader :name, :version, :files, :path, :zipfile_path
|
|
63
81
|
end
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://simp-project.com/docs/sce/schema.json",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"default": {},
|
|
6
|
+
"title": "Compliance Engine data",
|
|
7
|
+
"description": "Data for the Sicura Compliance Engine",
|
|
8
|
+
"required": [
|
|
9
|
+
"version"
|
|
10
|
+
],
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"$defs": {
|
|
13
|
+
"confine": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"title": "Conditions that confine this entry to specific environments",
|
|
16
|
+
"description": "Keys are dot-notation Puppet fact paths (e.g. os.release.major) or the special keys module_name and module_version. Values may be a single scalar (exact match; strings may be prefixed with ! to negate) or an array of scalars (any match).",
|
|
17
|
+
"patternProperties": {
|
|
18
|
+
"^\\w+(?:[.\\-]\\w+)*$": {
|
|
19
|
+
"oneOf": [
|
|
20
|
+
{
|
|
21
|
+
"type": "string"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"type": "boolean"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"type": "integer"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"type": "array",
|
|
31
|
+
"items": {
|
|
32
|
+
"type": ["string", "boolean", "integer"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"examples": [
|
|
39
|
+
{
|
|
40
|
+
"os.release.major": ["8"],
|
|
41
|
+
"os.name": "RedHat"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"ipv6_enabled": true
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"module_name": "simp-compliance_engine",
|
|
48
|
+
"module_version": ">=1.0.0 <2.0.0"
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"controlsMap": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"title": "References to top-level controls entries. Enforced when value is true; explicitly excluded when value is false.",
|
|
55
|
+
"patternProperties": {
|
|
56
|
+
"^\\w(?:[\\w.-]*[\\w-])?(?::\\w(?:[\\w.-]*[\\w-])?)*$": {
|
|
57
|
+
"type": "boolean",
|
|
58
|
+
"default": false
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"additionalProperties": false,
|
|
62
|
+
"examples": [
|
|
63
|
+
{
|
|
64
|
+
"nist_800_53:rev4:AU-2": true
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"identifiers": {
|
|
69
|
+
"title": "External framework identifiers",
|
|
70
|
+
"description": "Preferred form: an object whose keys are framework names (e.g. nist_800_53:rev4, disa_stig) and whose values are arrays of identifier strings or numbers within that framework. A plain array of identifier strings is also accepted.",
|
|
71
|
+
"oneOf": [
|
|
72
|
+
{
|
|
73
|
+
"type": "object",
|
|
74
|
+
"patternProperties": {
|
|
75
|
+
"^\\w(?:[\\w.-]*[\\w-])?(?::\\w(?:[\\w.-]*[\\w-])?)*$": {
|
|
76
|
+
"type": "array",
|
|
77
|
+
"items": {
|
|
78
|
+
"type": ["string", "number"]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"examples": [
|
|
83
|
+
{
|
|
84
|
+
"nist_800_53:rev4": ["AU-2", "AU-3"],
|
|
85
|
+
"disa_stig": ["RHEL-07-021820", "CCI-000293"]
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"type": "array",
|
|
91
|
+
"items": {
|
|
92
|
+
"type": ["string", "number"]
|
|
93
|
+
},
|
|
94
|
+
"examples": [
|
|
95
|
+
["ID1.1", "ID1.2"]
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"properties": {
|
|
102
|
+
"version": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"const": "2.0.0",
|
|
105
|
+
"title": "The SCE data format version"
|
|
106
|
+
},
|
|
107
|
+
"profiles": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"default": {},
|
|
110
|
+
"title": "Collection of compliance profiles",
|
|
111
|
+
"description": "Each profile is a named checklist of Checks, CEs, and Controls to enforce.",
|
|
112
|
+
"patternProperties": {
|
|
113
|
+
"^\\w(?:[\\w.-]*[\\w-])?(?::\\w(?:[\\w.-]*[\\w-])?)*$": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"default": {},
|
|
116
|
+
"title": "A compliance profile",
|
|
117
|
+
"properties": {
|
|
118
|
+
"title": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"title": "Short description of the profile",
|
|
121
|
+
"examples": [
|
|
122
|
+
"Level 1 - Server"
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
"description": {
|
|
126
|
+
"type": "string",
|
|
127
|
+
"title": "Longer description of the profile",
|
|
128
|
+
"examples": [
|
|
129
|
+
"Items in this profile intend to: be practical and prudent; provide a clear security benefit; and not inhibit the utility of the technology beyond acceptable means. This profile is intended for servers."
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
"ces": {
|
|
133
|
+
"type": "object",
|
|
134
|
+
"default": {},
|
|
135
|
+
"title": "CEs to include in this profile. Enforced when value is true; explicitly excluded when value is false (overrides any positive mapping route).",
|
|
136
|
+
"patternProperties": {
|
|
137
|
+
"^\\w(?:[\\w.-]*[\\w-])?(?::\\w(?:[\\w.-]*[\\w-])?)*$": {
|
|
138
|
+
"type": "boolean",
|
|
139
|
+
"default": false
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
"examples": [
|
|
143
|
+
{
|
|
144
|
+
"oval:simp.cis.el8.1.6.2_Ensure_ASLR_is_enabled:def:1": true
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
"controls": {
|
|
149
|
+
"$ref": "#/$defs/controlsMap"
|
|
150
|
+
},
|
|
151
|
+
"checks": {
|
|
152
|
+
"type": "object",
|
|
153
|
+
"default": {},
|
|
154
|
+
"title": "Checks to include in this profile. Enforced when value is true; hard-excluded when value is false (overrides any positive mapping route).",
|
|
155
|
+
"patternProperties": {
|
|
156
|
+
"^\\w(?:[\\w.-]*[\\w-])?(?::\\w(?:[\\w.-]*[\\w-])?)*$": {
|
|
157
|
+
"type": "boolean",
|
|
158
|
+
"default": false
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"examples": [
|
|
162
|
+
{
|
|
163
|
+
"widget_spinner_audit_logging": true
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
"confine": {
|
|
168
|
+
"$ref": "#/$defs/confine"
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"examples": [
|
|
172
|
+
{
|
|
173
|
+
"title": "Level 1 - Server",
|
|
174
|
+
"description": "Items in this profile intend to: be practical and prudent; provide a clear security benefit; and not inhibit the utility of the technology beyond acceptable means. This profile is intended for servers.",
|
|
175
|
+
"controls": {
|
|
176
|
+
"cis:el8:l1": true
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
"examples": [
|
|
183
|
+
{
|
|
184
|
+
"cis:el8:l1:server": {
|
|
185
|
+
"title": "CIS Level 1 - Server",
|
|
186
|
+
"controls": {
|
|
187
|
+
"cis:el8:l1": true
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
},
|
|
193
|
+
"ce": {
|
|
194
|
+
"type": "object",
|
|
195
|
+
"default": {},
|
|
196
|
+
"title": "Collection of Compliance Elements (CEs)",
|
|
197
|
+
"description": "Each CE names a single compliance capability and bridges profiles to checks via a shared vocabulary.",
|
|
198
|
+
"patternProperties": {
|
|
199
|
+
"^\\w(?:[\\w.-]*[\\w-])?(?::\\w(?:[\\w.-]*[\\w-])?)*$": {
|
|
200
|
+
"type": "object",
|
|
201
|
+
"default": {},
|
|
202
|
+
"title": "A Compliance Element",
|
|
203
|
+
"properties": {
|
|
204
|
+
"title": {
|
|
205
|
+
"type": "string",
|
|
206
|
+
"title": "Short title of the compliance element",
|
|
207
|
+
"examples": [
|
|
208
|
+
"Ensure address space layout randomization (ASLR) is enabled"
|
|
209
|
+
]
|
|
210
|
+
},
|
|
211
|
+
"description": {
|
|
212
|
+
"type": "string",
|
|
213
|
+
"title": "Detailed description of the compliance element",
|
|
214
|
+
"examples": [
|
|
215
|
+
"Address space layout randomization (ASLR) is an exploit mitigation technique which randomly arranges the address space of key data areas of a process."
|
|
216
|
+
]
|
|
217
|
+
},
|
|
218
|
+
"controls": {
|
|
219
|
+
"$ref": "#/$defs/controlsMap"
|
|
220
|
+
},
|
|
221
|
+
"identifiers": {
|
|
222
|
+
"$ref": "#/$defs/identifiers"
|
|
223
|
+
},
|
|
224
|
+
"oval-ids": {
|
|
225
|
+
"type": "array",
|
|
226
|
+
"default": [],
|
|
227
|
+
"title": "OVAL identifiers associated with this CE",
|
|
228
|
+
"items": {
|
|
229
|
+
"type": "string"
|
|
230
|
+
},
|
|
231
|
+
"examples": [
|
|
232
|
+
[
|
|
233
|
+
"xccdf_org.cisecurity.benchmarks_rule_1.6.2_Ensure_ASLR_is_enabled"
|
|
234
|
+
]
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
"imported_data": {
|
|
238
|
+
"type": "object",
|
|
239
|
+
"default": {},
|
|
240
|
+
"title": "Data imported from external benchmark sources",
|
|
241
|
+
"properties": {
|
|
242
|
+
"fixtext": {
|
|
243
|
+
"type": "string",
|
|
244
|
+
"title": "Remediation instructions from the source benchmark"
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
"additionalProperties": true
|
|
248
|
+
},
|
|
249
|
+
"confine": {
|
|
250
|
+
"$ref": "#/$defs/confine"
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
"examples": [
|
|
254
|
+
{
|
|
255
|
+
"controls": {
|
|
256
|
+
"cis:el8:v1.0.0.1": true
|
|
257
|
+
},
|
|
258
|
+
"identifiers": {
|
|
259
|
+
"cis": ["1.6.2"],
|
|
260
|
+
"nist_800_53:rev4": ["SC-39"]
|
|
261
|
+
},
|
|
262
|
+
"title": "Ensure address space layout randomization (ASLR) is enabled",
|
|
263
|
+
"description": "Address space layout randomization (ASLR) is an exploit mitigation technique which randomly arranges the address space of key data areas of a process.",
|
|
264
|
+
"confine": {
|
|
265
|
+
"os.release.major": ["8"],
|
|
266
|
+
"os.name": ["RedHat"]
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
"checks": {
|
|
274
|
+
"type": "object",
|
|
275
|
+
"default": {},
|
|
276
|
+
"title": "Collection of compliance checks",
|
|
277
|
+
"description": "Each check is a verifiable assertion about a system setting. Currently only type puppet-class-parameter is supported; it carries a parameter and value that become Hiera data.",
|
|
278
|
+
"patternProperties": {
|
|
279
|
+
"^\\w(?:[\\w.-]*[\\w-])?(?::\\w(?:[\\w.-]*[\\w-])?)*$": {
|
|
280
|
+
"type": "object",
|
|
281
|
+
"default": {},
|
|
282
|
+
"title": "A compliance check",
|
|
283
|
+
"properties": {
|
|
284
|
+
"type": {
|
|
285
|
+
"type": "string",
|
|
286
|
+
"const": "puppet-class-parameter",
|
|
287
|
+
"title": "The type of check"
|
|
288
|
+
},
|
|
289
|
+
"settings": {
|
|
290
|
+
"type": "object",
|
|
291
|
+
"title": "Settings for the check",
|
|
292
|
+
"required": ["parameter", "value"],
|
|
293
|
+
"properties": {
|
|
294
|
+
"parameter": {
|
|
295
|
+
"type": "string",
|
|
296
|
+
"title": "The fully-qualified Puppet class parameter name. Prefix with -- to knock out an existing parameter.",
|
|
297
|
+
"examples": [
|
|
298
|
+
"simp::sysctl::kernel__randomize_va_space",
|
|
299
|
+
"--simp::sysctl::kernel__randomize_va_space"
|
|
300
|
+
]
|
|
301
|
+
},
|
|
302
|
+
"value": {
|
|
303
|
+
"title": "The value to enforce for the parameter",
|
|
304
|
+
"type": ["boolean", "integer", "number", "string", "array", "object", "null"],
|
|
305
|
+
"examples": [
|
|
306
|
+
2,
|
|
307
|
+
true,
|
|
308
|
+
"weekly",
|
|
309
|
+
["item1", "item2"]
|
|
310
|
+
]
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
"examples": [
|
|
314
|
+
{
|
|
315
|
+
"parameter": "simp::sysctl::kernel__randomize_va_space",
|
|
316
|
+
"value": 2
|
|
317
|
+
}
|
|
318
|
+
]
|
|
319
|
+
},
|
|
320
|
+
"ces": {
|
|
321
|
+
"type": "array",
|
|
322
|
+
"default": [],
|
|
323
|
+
"title": "CEs that this check satisfies",
|
|
324
|
+
"items": {
|
|
325
|
+
"type": "string"
|
|
326
|
+
},
|
|
327
|
+
"examples": [
|
|
328
|
+
[
|
|
329
|
+
"oval:simp.cis.el8.1.6.2_Ensure_ASLR_is_enabled:def:1"
|
|
330
|
+
]
|
|
331
|
+
]
|
|
332
|
+
},
|
|
333
|
+
"controls": {
|
|
334
|
+
"$ref": "#/$defs/controlsMap"
|
|
335
|
+
},
|
|
336
|
+
"identifiers": {
|
|
337
|
+
"$ref": "#/$defs/identifiers"
|
|
338
|
+
},
|
|
339
|
+
"confine": {
|
|
340
|
+
"$ref": "#/$defs/confine"
|
|
341
|
+
},
|
|
342
|
+
"remediation": {
|
|
343
|
+
"type": "object",
|
|
344
|
+
"title": "Remediation metadata used to filter checks by enforcement tolerance",
|
|
345
|
+
"properties": {
|
|
346
|
+
"risk": {
|
|
347
|
+
"type": "array",
|
|
348
|
+
"title": "Risk entries describing the potential impact of enforcing this check",
|
|
349
|
+
"items": {
|
|
350
|
+
"type": "object",
|
|
351
|
+
"properties": {
|
|
352
|
+
"level": {
|
|
353
|
+
"type": "integer",
|
|
354
|
+
"minimum": 1,
|
|
355
|
+
"maximum": 100,
|
|
356
|
+
"title": "Numeric risk level (1-100). Checks are skipped when this value is >= the enforcement_tolerance setting."
|
|
357
|
+
},
|
|
358
|
+
"reason": {
|
|
359
|
+
"type": "string",
|
|
360
|
+
"title": "Human-readable explanation of the risk"
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
"examples": [
|
|
365
|
+
[
|
|
366
|
+
{
|
|
367
|
+
"level": 41,
|
|
368
|
+
"reason": "Depending on the system being scanned, aide could cause system sluggishness during the period it performs its scan."
|
|
369
|
+
}
|
|
370
|
+
]
|
|
371
|
+
]
|
|
372
|
+
},
|
|
373
|
+
"disabled": {
|
|
374
|
+
"type": "array",
|
|
375
|
+
"title": "Entries indicating this check is disabled. Disabled checks are always skipped when enforcement_tolerance is set.",
|
|
376
|
+
"items": {
|
|
377
|
+
"type": "object",
|
|
378
|
+
"properties": {
|
|
379
|
+
"reason": {
|
|
380
|
+
"type": "string",
|
|
381
|
+
"title": "Human-readable explanation of why this check is disabled"
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
"examples": [
|
|
386
|
+
[
|
|
387
|
+
{
|
|
388
|
+
"reason": "This check is not applicable to containerized environments."
|
|
389
|
+
}
|
|
390
|
+
]
|
|
391
|
+
]
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
"if": {
|
|
397
|
+
"required": ["type"]
|
|
398
|
+
},
|
|
399
|
+
"then": {
|
|
400
|
+
"required": ["settings"]
|
|
401
|
+
},
|
|
402
|
+
"examples": [
|
|
403
|
+
{
|
|
404
|
+
"type": "puppet-class-parameter",
|
|
405
|
+
"settings": {
|
|
406
|
+
"parameter": "simp::sysctl::kernel__randomize_va_space",
|
|
407
|
+
"value": 2
|
|
408
|
+
},
|
|
409
|
+
"controls": {
|
|
410
|
+
"nist_800_53:rev4:SC-39": true
|
|
411
|
+
},
|
|
412
|
+
"confine": {
|
|
413
|
+
"os.family": "RedHat",
|
|
414
|
+
"os.release.major": ["7", "8", "9"]
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
]
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
"controls": {
|
|
422
|
+
"type": "object",
|
|
423
|
+
"default": {},
|
|
424
|
+
"title": "Collection of compliance controls",
|
|
425
|
+
"description": "Controls are cross-reference labels from external frameworks (e.g. nist_800_53:rev4:AU-2, disa_stig). Profiles and checks annotate themselves with controls to express alignment. Control entries carry optional metadata; an empty object {} is valid.",
|
|
426
|
+
"patternProperties": {
|
|
427
|
+
"^\\w(?:[\\w.-]*[\\w-])?(?::\\w(?:[\\w.-]*[\\w-])?)*$": {
|
|
428
|
+
"type": "object",
|
|
429
|
+
"default": {},
|
|
430
|
+
"title": "A compliance control",
|
|
431
|
+
"properties": {
|
|
432
|
+
"title": {
|
|
433
|
+
"type": "string",
|
|
434
|
+
"title": "Short title of the control"
|
|
435
|
+
},
|
|
436
|
+
"description": {
|
|
437
|
+
"type": "string",
|
|
438
|
+
"title": "Description of the control"
|
|
439
|
+
},
|
|
440
|
+
"identifiers": {
|
|
441
|
+
"$ref": "#/$defs/identifiers"
|
|
442
|
+
},
|
|
443
|
+
"confine": {
|
|
444
|
+
"$ref": "#/$defs/confine"
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
"examples": [
|
|
448
|
+
{},
|
|
449
|
+
{
|
|
450
|
+
"title": "Audit Events",
|
|
451
|
+
"identifiers": {
|
|
452
|
+
"nist_800_53:rev4": ["AU-2"]
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
]
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
"examples": [
|
|
459
|
+
{
|
|
460
|
+
"nist_800_53:rev4": {},
|
|
461
|
+
"disa_stig": {},
|
|
462
|
+
"nist_800_53:rev4:AU-2": {
|
|
463
|
+
"title": "Audit Events"
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
]
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
"examples": [
|
|
470
|
+
{
|
|
471
|
+
"version": "2.0.0",
|
|
472
|
+
"profiles": {
|
|
473
|
+
"cis:el8:l1:server": {
|
|
474
|
+
"title": "CIS Level 1 - Server",
|
|
475
|
+
"controls": {
|
|
476
|
+
"cis:el8:l1": true
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
"ce": {
|
|
481
|
+
"oval:simp.cis.el8.1.6.2_Ensure_ASLR_is_enabled:def:1": {
|
|
482
|
+
"controls": {
|
|
483
|
+
"cis:el8:l1": true
|
|
484
|
+
},
|
|
485
|
+
"identifiers": {
|
|
486
|
+
"cis": ["1.6.2"],
|
|
487
|
+
"nist_800_53:rev4": ["SC-39"]
|
|
488
|
+
},
|
|
489
|
+
"title": "Ensure address space layout randomization (ASLR) is enabled",
|
|
490
|
+
"description": "ASLR is an exploit mitigation technique which randomly arranges the address space of key data areas of a process.",
|
|
491
|
+
"confine": {
|
|
492
|
+
"os.release.major": ["8"],
|
|
493
|
+
"os.name": ["RedHat"]
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
"checks": {
|
|
498
|
+
"oval:com.puppet.forge.simp.cis.simp.sysctl.kernel__randomize_va_space": {
|
|
499
|
+
"type": "puppet-class-parameter",
|
|
500
|
+
"settings": {
|
|
501
|
+
"parameter": "simp::sysctl::kernel__randomize_va_space",
|
|
502
|
+
"value": 2
|
|
503
|
+
},
|
|
504
|
+
"controls": {
|
|
505
|
+
"cis:el8:l1": true
|
|
506
|
+
},
|
|
507
|
+
"confine": {
|
|
508
|
+
"os.family": "RedHat"
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
"controls": {
|
|
513
|
+
"cis:el8:l1": {}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
]
|
|
517
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ComplianceEngine
|
|
4
|
+
# Named constants for enforcement tolerance levels.
|
|
5
|
+
#
|
|
6
|
+
# The enforcement tolerance controls which remediation risk levels are
|
|
7
|
+
# enforced. A check whose risk level is greater than or equal to the
|
|
8
|
+
# tolerance value is filtered out. For example, setting
|
|
9
|
+
# +enforcement_tolerance+ to +ComplianceEngine::Tolerance::MODERATE+
|
|
10
|
+
# enforces checks with risk levels below 60 while skipping anything
|
|
11
|
+
# rated MODERATE (60) or higher.
|
|
12
|
+
module Tolerance
|
|
13
|
+
# Enforce only checks with no meaningful risk (risk < 20).
|
|
14
|
+
NONE = 20
|
|
15
|
+
|
|
16
|
+
# Enforce checks below low-risk remediations; skip SAFE (40) and above (risk < 40).
|
|
17
|
+
SAFE = 40
|
|
18
|
+
|
|
19
|
+
# Enforce checks below moderate-risk remediations; skip MODERATE (60) and above (risk < 60).
|
|
20
|
+
MODERATE = 60
|
|
21
|
+
|
|
22
|
+
# Enforce checks below remediations that affect access; skip ACCESS (80) and above (risk < 80).
|
|
23
|
+
ACCESS = 80
|
|
24
|
+
|
|
25
|
+
# Enforce all checks, including those that may cause breaking changes (risk < 100).
|
|
26
|
+
BREAKING = 100
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -1,25 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ComplianceEngine
|
|
4
|
-
VERSION = '0.
|
|
5
|
-
|
|
6
|
-
# Handle supported compliance data versions
|
|
7
|
-
class Version
|
|
8
|
-
# Verify that the version is supported
|
|
9
|
-
#
|
|
10
|
-
# @param version [String] The version to verify
|
|
11
|
-
def initialize(version)
|
|
12
|
-
raise 'Missing version' if version.nil?
|
|
13
|
-
raise "Unsupported version '#{version}'" unless version == '2.0.0'
|
|
14
|
-
|
|
15
|
-
@version = version
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Convert the version to a string
|
|
19
|
-
#
|
|
20
|
-
# @return [String]
|
|
21
|
-
def to_s
|
|
22
|
-
@version
|
|
23
|
-
end
|
|
24
|
-
end
|
|
4
|
+
VERSION = '0.6.0'
|
|
25
5
|
end
|
data/lib/compliance_engine.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'compliance_engine/version'
|
|
4
|
+
require_relative 'compliance_engine/tolerance'
|
|
4
5
|
require_relative 'compliance_engine/data'
|
|
5
6
|
require 'logger'
|
|
6
7
|
|
|
@@ -42,6 +43,44 @@ module ComplianceEngine
|
|
|
42
43
|
@log = value
|
|
43
44
|
end
|
|
44
45
|
|
|
46
|
+
# Return the path to the bundled JSON schema for SCE data files
|
|
47
|
+
#
|
|
48
|
+
# @return [String] absolute path to sce-schema.json
|
|
49
|
+
def self.schema_path
|
|
50
|
+
File.expand_path(File.join(__dir__, 'compliance_engine', 'sce-schema.json'))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Return the parsed JSON schema for SCE data files
|
|
54
|
+
#
|
|
55
|
+
# @return [Hash] the parsed JSON schema
|
|
56
|
+
def self.schema
|
|
57
|
+
require 'json'
|
|
58
|
+
@schema ||= begin
|
|
59
|
+
path = schema_path
|
|
60
|
+
deep_freeze(JSON.parse(File.read(path, encoding: 'UTF-8')))
|
|
61
|
+
rescue Errno::ENOENT, JSON::ParserError => e
|
|
62
|
+
raise Error, "Failed to load schema from #{path}: #{e.class}: #{e.message}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Recursively freeze a Hash, Array, and all nested objects.
|
|
67
|
+
#
|
|
68
|
+
# @param obj [Object] the object to freeze
|
|
69
|
+
# @return [Object] the frozen object
|
|
70
|
+
def self.deep_freeze(obj)
|
|
71
|
+
case obj
|
|
72
|
+
when Hash
|
|
73
|
+
obj.each do |k, v|
|
|
74
|
+
deep_freeze(k)
|
|
75
|
+
deep_freeze(v)
|
|
76
|
+
end
|
|
77
|
+
when Array
|
|
78
|
+
obj.each { |v| deep_freeze(v) }
|
|
79
|
+
end
|
|
80
|
+
obj.freeze
|
|
81
|
+
end
|
|
82
|
+
private_class_method :deep_freeze
|
|
83
|
+
|
|
45
84
|
# Install a PuppetLogger unless a logger has already been explicitly configured.
|
|
46
85
|
# Extracted so the behaviour can be unit-tested without reloading enforcement.rb.
|
|
47
86
|
#
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: compliance_engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Steven Pritchard
|
|
@@ -142,12 +142,16 @@ files:
|
|
|
142
142
|
- lib/compliance_engine/data_loader/file.rb
|
|
143
143
|
- lib/compliance_engine/data_loader/json.rb
|
|
144
144
|
- lib/compliance_engine/data_loader/yaml.rb
|
|
145
|
+
- lib/compliance_engine/data_version.rb
|
|
145
146
|
- lib/compliance_engine/environment_loader.rb
|
|
146
147
|
- lib/compliance_engine/environment_loader/zip.rb
|
|
148
|
+
- lib/compliance_engine/environment_loader/zip_bytes.rb
|
|
147
149
|
- lib/compliance_engine/module_loader.rb
|
|
148
150
|
- lib/compliance_engine/profile.rb
|
|
149
151
|
- lib/compliance_engine/profiles.rb
|
|
150
152
|
- lib/compliance_engine/puppet_logger.rb
|
|
153
|
+
- lib/compliance_engine/sce-schema.json
|
|
154
|
+
- lib/compliance_engine/tolerance.rb
|
|
151
155
|
- lib/compliance_engine/version.rb
|
|
152
156
|
homepage: https://simp-project.com/docs/sce/
|
|
153
157
|
licenses:
|
|
@@ -155,7 +159,7 @@ licenses:
|
|
|
155
159
|
metadata:
|
|
156
160
|
homepage_uri: https://simp-project.com/docs/sce/
|
|
157
161
|
source_code_uri: https://github.com/simp/rubygem-simp-compliance_engine
|
|
158
|
-
changelog_uri: https://github.com/simp/rubygem-simp-compliance_engine/releases/tag/0.
|
|
162
|
+
changelog_uri: https://github.com/simp/rubygem-simp-compliance_engine/releases/tag/0.6.0
|
|
159
163
|
bug_tracker_uri: https://github.com/simp/rubygem-simp-compliance_engine/issues
|
|
160
164
|
rdoc_options: []
|
|
161
165
|
require_paths:
|