compliance_engine 0.3.0 → 0.5.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/CHANGELOG.md +16 -0
- data/compliance_engine.gemspec +2 -2
- data/lib/compliance_engine/ce.rb +1 -1
- data/lib/compliance_engine/ces.rb +1 -1
- data/lib/compliance_engine/check.rb +1 -1
- data/lib/compliance_engine/checks.rb +1 -1
- data/lib/compliance_engine/cli.rb +1 -1
- data/lib/compliance_engine/collection.rb +16 -2
- data/lib/compliance_engine/component.rb +3 -3
- data/lib/compliance_engine/control.rb +1 -1
- data/lib/compliance_engine/controls.rb +1 -1
- data/lib/compliance_engine/data.rb +49 -25
- data/lib/compliance_engine/data_loader/file.rb +2 -2
- data/lib/compliance_engine/data_loader/json.rb +2 -2
- data/lib/compliance_engine/data_loader/yaml.rb +2 -2
- data/lib/compliance_engine/data_loader.rb +1 -1
- data/lib/compliance_engine/data_version.rb +21 -0
- data/lib/compliance_engine/environment_loader/zip.rb +17 -12
- data/lib/compliance_engine/environment_loader.rb +6 -4
- data/lib/compliance_engine/module_loader.rb +44 -26
- data/lib/compliance_engine/profile.rb +1 -1
- data/lib/compliance_engine/profiles.rb +1 -1
- data/lib/compliance_engine/puppet_logger.rb +49 -0
- 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 +55 -4
- 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: 739c21f5b10c86e4ec22f8fdbe5e86175a69f1bfd15af9d064a2fec3c6b11d7b
|
|
4
|
+
data.tar.gz: e142e74b1b28997ead592c6c358cc160cd094d307f162adc5f0edf77b3e22f68
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f99f46c33175af5a2bc879ab5254fe4cc91334c11edf334c4809063063fd82279458de34e296d0af4dcc619cd9caa5342f3412f40aded8a73e629137716ea090
|
|
7
|
+
data.tar.gz: 0ff070e539cb02ef974de2b2c7de752a79b7e0e0e89f3306bd26780dabe91c836a347f12e8c6e1fe32c4fe0cd2b97c0eefd267ada51a6b22aff64cc5c8151f0f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
### 0.5.0 / 2026-04-29
|
|
2
|
+
* Add JSON Schema for SCE data, exposed via `ComplianceEngine.schema` (#49)
|
|
3
|
+
* Add `ComplianceEngine::Tolerance` constants for enforcement tolerance levels (#107)
|
|
4
|
+
* Drop stale file data on environment rescan (#108)
|
|
5
|
+
* Handle malformed compliance data gracefully instead of raising (#111)
|
|
6
|
+
* Make dotfile loading opt-in (defaults to off) (#112)
|
|
7
|
+
* Accept `::Zip::File` objects directly in `EnvironmentLoader::Zip` (#114)
|
|
8
|
+
* Support `compliance_markup`-style parameter knockout prefixes
|
|
9
|
+
* Split `version.rb`: separate gem `VERSION` from data format validator (#104)
|
|
10
|
+
|
|
11
|
+
### 0.4.0 / 2026-04-20
|
|
12
|
+
* Route Puppet Hiera backend log messages through Puppet's logging system (#96)
|
|
13
|
+
* 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
|
|
14
|
+
* Replace PDK with puppet-modulebuilder for Puppet Forge builds
|
|
15
|
+
* Add Beaker acceptance test suite covering puppet apply, server/agent, cache leakage, and environment leakage scenarios
|
|
16
|
+
|
|
1
17
|
### 0.3.0 / 2026-03-19
|
|
2
18
|
* Hash-like Collection methods return Collection objects (#37)
|
|
3
19
|
|
data/compliance_engine.gemspec
CHANGED
|
@@ -19,13 +19,13 @@ 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']
|
|
26
26
|
|
|
27
27
|
spec.add_dependency 'deep_merge', '~> 1.2'
|
|
28
|
-
spec.add_dependency 'irb', '~> 1.14'
|
|
28
|
+
spec.add_dependency 'irb', '~> 1.14' unless RUBY_PLATFORM == 'java'
|
|
29
29
|
spec.add_dependency 'logger', '~> 1.4'
|
|
30
30
|
spec.add_dependency 'observer', '~> 0.1'
|
|
31
31
|
spec.add_dependency 'rubyzip', '>= 2.3', '< 4'
|
data/lib/compliance_engine/ce.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative '../compliance_engine'
|
|
4
4
|
|
|
5
5
|
# A generic compliance engine data collection
|
|
6
6
|
class ComplianceEngine::Collection
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative '../compliance_engine'
|
|
4
4
|
require 'deep_merge'
|
|
5
5
|
|
|
6
6
|
# A generic compliance engine data component
|
|
@@ -216,14 +216,14 @@ class ComplianceEngine::Component
|
|
|
216
216
|
|
|
217
217
|
if fragment['remediation'].key?('disabled')
|
|
218
218
|
message = "Remediation disabled for #{fragment}"
|
|
219
|
-
reason = fragment['remediation']['disabled']&.map { |value| value['reason'] }&.
|
|
219
|
+
reason = fragment['remediation']['disabled']&.map { |value| value['reason'] }&.compact&.join("\n")
|
|
220
220
|
message += "\n#{reason}" unless reason.nil?
|
|
221
221
|
ComplianceEngine.log.info message
|
|
222
222
|
return true
|
|
223
223
|
end
|
|
224
224
|
|
|
225
225
|
if fragment['remediation'].key?('risk')
|
|
226
|
-
risk_level = fragment['remediation']['risk']&.map { |value| value['level'] }&.
|
|
226
|
+
risk_level = fragment['remediation']['risk']&.map { |value| value['level'] }&.grep(Integer)&.max
|
|
227
227
|
if risk_level.is_a?(Integer) && risk_level >= enforcement_tolerance
|
|
228
228
|
ComplianceEngine.log.info "Remediation risk #{risk_level} exceeds enforcement enforcement_tolerance #{enforcement_tolerance} for #{fragment}"
|
|
229
229
|
return true
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
3
|
+
require 'set'
|
|
4
|
+
require_relative '../compliance_engine'
|
|
5
|
+
require_relative 'data_version'
|
|
6
|
+
require_relative 'component'
|
|
7
|
+
require_relative 'ce'
|
|
8
|
+
require_relative 'check'
|
|
9
|
+
require_relative 'control'
|
|
10
|
+
require_relative 'profile'
|
|
11
|
+
require_relative 'collection'
|
|
12
|
+
require_relative 'ces'
|
|
13
|
+
require_relative 'checks'
|
|
14
|
+
require_relative 'controls'
|
|
15
|
+
require_relative 'profiles'
|
|
16
|
+
|
|
17
|
+
require_relative 'data_loader'
|
|
18
|
+
require_relative 'data_loader/json'
|
|
19
|
+
require_relative 'data_loader/yaml'
|
|
20
|
+
require_relative 'module_loader'
|
|
21
|
+
require_relative 'environment_loader'
|
|
21
22
|
|
|
22
23
|
require 'deep_merge'
|
|
23
24
|
require 'json'
|
|
@@ -161,9 +162,17 @@ class ComplianceEngine::Data
|
|
|
161
162
|
|
|
162
163
|
if path.is_a?(ComplianceEngine::ModuleLoader)
|
|
163
164
|
modules[path.name] = path.version unless path.name.nil?
|
|
164
|
-
path.files.
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
new_keys = path.files.to_set(&:key)
|
|
166
|
+
module_root = if path.zipfile_path
|
|
167
|
+
::File.join(path.zipfile_path, '.', path.path.sub(%r{^/+}, ''))
|
|
168
|
+
else
|
|
169
|
+
path.path
|
|
170
|
+
end
|
|
171
|
+
module_prefix = ::File.join(module_root, '')
|
|
172
|
+
stale_keys = data.keys.select { |k| (k == module_root || k.start_with?(module_prefix)) && !new_keys.include?(k) }
|
|
173
|
+
stale_keys.each { |k| data.delete(k) }
|
|
174
|
+
path.files.each { |file_loader| update(file_loader) }
|
|
175
|
+
reset_collection if path.files.empty? && !stale_keys.empty?
|
|
167
176
|
next
|
|
168
177
|
end
|
|
169
178
|
|
|
@@ -221,7 +230,7 @@ class ComplianceEngine::Data
|
|
|
221
230
|
loader.add_observer(self, :update)
|
|
222
231
|
data[key] = {
|
|
223
232
|
loader: loader,
|
|
224
|
-
version: ComplianceEngine::
|
|
233
|
+
version: ComplianceEngine::DataVersion.new(loader.data['version']),
|
|
225
234
|
content: loader.data,
|
|
226
235
|
}
|
|
227
236
|
else
|
|
@@ -237,7 +246,7 @@ class ComplianceEngine::Data
|
|
|
237
246
|
data[filename.key][:loader] = filename
|
|
238
247
|
data[filename.key][:loader].add_observer(self, :update)
|
|
239
248
|
end
|
|
240
|
-
data[filename.key][:version] = ComplianceEngine::
|
|
249
|
+
data[filename.key][:version] = ComplianceEngine::DataVersion.new(filename.data['version'])
|
|
241
250
|
data[filename.key][:content] = filename.data
|
|
242
251
|
end
|
|
243
252
|
|
|
@@ -306,7 +315,8 @@ class ComplianceEngine::Data
|
|
|
306
315
|
v.to_a.each do |component|
|
|
307
316
|
next unless component.key?('confine')
|
|
308
317
|
|
|
309
|
-
|
|
318
|
+
confine = component['confine'].transform_values { |val| val.is_a?(Array) ? val.dup : Array(val) }
|
|
319
|
+
@confines = DeepMerge.deep_merge!(confine, @confines, knockout_prefix: '--')
|
|
310
320
|
end
|
|
311
321
|
end
|
|
312
322
|
end
|
|
@@ -348,10 +358,24 @@ class ComplianceEngine::Data
|
|
|
348
358
|
|
|
349
359
|
valid_profiles.reverse_each do |profile|
|
|
350
360
|
check_mapping(profile).each_value do |check|
|
|
351
|
-
|
|
361
|
+
hiera_data = check.hiera
|
|
362
|
+
next if hiera_data.nil?
|
|
363
|
+
|
|
364
|
+
parameters = DeepMerge.deep_merge!(Marshal.load(Marshal.dump(hiera_data)), parameters)
|
|
352
365
|
end
|
|
353
366
|
end
|
|
354
367
|
|
|
368
|
+
# deep_merge does not support hash-key knockout via knockout_prefix.
|
|
369
|
+
# Handle parameter-name knockout explicitly: any key starting with '--'
|
|
370
|
+
# signals that the matching key without the prefix should be suppressed
|
|
371
|
+
# (mirrors compliance_markup behavior).
|
|
372
|
+
parameters.each_key do |key|
|
|
373
|
+
next unless key.start_with?('--')
|
|
374
|
+
|
|
375
|
+
parameters.delete(key.delete_prefix('--'))
|
|
376
|
+
parameters.delete(key)
|
|
377
|
+
end
|
|
378
|
+
|
|
355
379
|
@hiera[cache_key] = parameters
|
|
356
380
|
end
|
|
357
381
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
require_relative '../../compliance_engine'
|
|
4
|
+
require_relative '../data_loader'
|
|
5
5
|
|
|
6
6
|
# Load compliance engine data from a file
|
|
7
7
|
class ComplianceEngine::DataLoader::File < ComplianceEngine::DataLoader
|
|
@@ -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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
require_relative '../../compliance_engine'
|
|
4
|
+
require_relative '../environment_loader'
|
|
5
5
|
require 'zip/filesystem'
|
|
6
6
|
|
|
7
7
|
# Load compliance engine data from a zip file containing a Puppet environment
|
|
@@ -9,16 +9,21 @@ class ComplianceEngine::EnvironmentLoader::Zip < ComplianceEngine::EnvironmentLo
|
|
|
9
9
|
# Initialize a ComplianceEngine::EnvironmentLoader::Zip object from a zip
|
|
10
10
|
# file and an optional root directory.
|
|
11
11
|
#
|
|
12
|
-
# @param
|
|
12
|
+
# @param input [String, ::Zip::File] either a filesystem path to a zip file,
|
|
13
|
+
# or an already-opened ::Zip::File (e.g. from Zip::File.open_buffer); when
|
|
14
|
+
# a ::Zip::File is passed, the caller owns its lifecycle
|
|
13
15
|
# @param root [String] a directory within the zip file to use as the root of the environment
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
# @param load_dotfiles [Boolean] whether to load dotfiles; defaults to true to
|
|
17
|
+
# preserve the historical zip-loader behaviour of including all files
|
|
18
|
+
# @param name [String, nil] identifier used for modulepath and downstream
|
|
19
|
+
# cache keys; defaults to the zip's #name (the path on disk, or "-" for
|
|
20
|
+
# buffer-opened zips). Pass an explicit value when loading a buffer-opened
|
|
21
|
+
# zip to keep cache keys unique and logs informative.
|
|
22
|
+
def initialize(input, root: '/'.dup, load_dotfiles: true, name: nil)
|
|
23
|
+
zipfile = input.is_a?(::Zip::File) ? input : ::Zip::File.open(input)
|
|
24
|
+
@modulepath = name || zipfile.name
|
|
25
|
+
super(root, fileclass: zipfile.file, dirclass: zipfile.dir, zipfile_path: @modulepath, load_dotfiles: load_dotfiles)
|
|
26
|
+
ensure
|
|
27
|
+
zipfile.close if zipfile && !input.is_a?(::Zip::File)
|
|
23
28
|
end
|
|
24
29
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
require_relative '../compliance_engine'
|
|
4
|
+
require_relative 'module_loader'
|
|
5
5
|
|
|
6
6
|
# Load compliance engine data from a Puppet environment
|
|
7
7
|
class ComplianceEngine::EnvironmentLoader
|
|
@@ -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
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
require_relative '../compliance_engine'
|
|
4
|
+
require_relative 'data_loader/json'
|
|
5
|
+
require_relative 'data_loader/yaml'
|
|
6
6
|
|
|
7
7
|
# Load compliance engine data from a Puppet module
|
|
8
8
|
class ComplianceEngine::ModuleLoader
|
|
@@ -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,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ComplianceEngine
|
|
4
|
+
# Routes ComplianceEngine log messages through Puppet's logging system.
|
|
5
|
+
# Used as a drop-in replacement for the default Logger when running inside Puppet.
|
|
6
|
+
class PuppetLogger
|
|
7
|
+
def initialize(*_args, **_kwargs)
|
|
8
|
+
ensure_puppet_available!
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def debug(msg)
|
|
12
|
+
ensure_puppet_available!
|
|
13
|
+
::Puppet.debug(msg)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def info(msg)
|
|
17
|
+
ensure_puppet_available!
|
|
18
|
+
::Puppet.info(msg)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def warn(msg)
|
|
22
|
+
ensure_puppet_available!
|
|
23
|
+
::Puppet.warning(msg)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def error(msg)
|
|
27
|
+
ensure_puppet_available!
|
|
28
|
+
::Puppet.err(msg)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def fatal(msg)
|
|
32
|
+
ensure_puppet_available!
|
|
33
|
+
::Puppet.crit(msg)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def level; end
|
|
37
|
+
|
|
38
|
+
def level=(_val); end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def ensure_puppet_available!
|
|
43
|
+
return if defined?(::Puppet)
|
|
44
|
+
|
|
45
|
+
raise ComplianceEngine::Error,
|
|
46
|
+
'ComplianceEngine::PuppetLogger requires Puppet to be loaded, but ::Puppet is not defined'
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
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.",
|
|
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.",
|
|
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.",
|
|
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.5.0'
|
|
25
5
|
end
|
data/lib/compliance_engine.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
require_relative 'compliance_engine/version'
|
|
4
|
+
require_relative 'compliance_engine/tolerance'
|
|
5
|
+
require_relative 'compliance_engine/data'
|
|
5
6
|
require 'logger'
|
|
6
7
|
|
|
7
8
|
# Work with compliance data
|
|
@@ -26,7 +27,7 @@ module ComplianceEngine
|
|
|
26
27
|
|
|
27
28
|
# Get the logger
|
|
28
29
|
#
|
|
29
|
-
# @return [Logger]
|
|
30
|
+
# @return [Logger, ComplianceEngine::PuppetLogger]
|
|
30
31
|
def self.log
|
|
31
32
|
return @log unless @log.nil?
|
|
32
33
|
|
|
@@ -36,8 +37,58 @@ module ComplianceEngine
|
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
# Set the logger
|
|
39
|
-
#
|
|
40
|
+
#
|
|
41
|
+
# @param value [Logger, ComplianceEngine::PuppetLogger] The logger to use
|
|
40
42
|
def self.log=(value)
|
|
41
43
|
@log = value
|
|
42
44
|
end
|
|
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
|
+
|
|
84
|
+
# Install a PuppetLogger unless a logger has already been explicitly configured.
|
|
85
|
+
# Extracted so the behaviour can be unit-tested without reloading enforcement.rb.
|
|
86
|
+
#
|
|
87
|
+
# @return [void]
|
|
88
|
+
def self.install_puppet_logger
|
|
89
|
+
return unless @log.nil?
|
|
90
|
+
|
|
91
|
+
require_relative 'compliance_engine/puppet_logger'
|
|
92
|
+
@log = PuppetLogger.new
|
|
93
|
+
end
|
|
43
94
|
end
|
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.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Steven Pritchard
|
|
@@ -142,11 +142,15 @@ 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
|
|
147
148
|
- lib/compliance_engine/module_loader.rb
|
|
148
149
|
- lib/compliance_engine/profile.rb
|
|
149
150
|
- lib/compliance_engine/profiles.rb
|
|
151
|
+
- lib/compliance_engine/puppet_logger.rb
|
|
152
|
+
- lib/compliance_engine/sce-schema.json
|
|
153
|
+
- lib/compliance_engine/tolerance.rb
|
|
150
154
|
- lib/compliance_engine/version.rb
|
|
151
155
|
homepage: https://simp-project.com/docs/sce/
|
|
152
156
|
licenses:
|
|
@@ -154,7 +158,7 @@ licenses:
|
|
|
154
158
|
metadata:
|
|
155
159
|
homepage_uri: https://simp-project.com/docs/sce/
|
|
156
160
|
source_code_uri: https://github.com/simp/rubygem-simp-compliance_engine
|
|
157
|
-
changelog_uri: https://github.com/simp/rubygem-simp-compliance_engine/releases/tag/0.
|
|
161
|
+
changelog_uri: https://github.com/simp/rubygem-simp-compliance_engine/releases/tag/0.5.0
|
|
158
162
|
bug_tracker_uri: https://github.com/simp/rubygem-simp-compliance_engine/issues
|
|
159
163
|
rdoc_options: []
|
|
160
164
|
require_paths:
|