compliance_engine 0.1.6 → 0.2.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: 9cf000d354045bd026560c6973a78b8939d7e1afbcabcfceea28514c9cad4cb2
4
- data.tar.gz: 4d2bc0af35450a9304c54894935898438a220f4a9dd2ec3cf27fd4464fdc85f1
3
+ metadata.gz: 6243625cabdaf239f089f05704cf4a7584d4d7631f6f81ff0cab3bbf8cb4cf49
4
+ data.tar.gz: 81d9b5d6a80799384f052e0dab725fcdbb34804691b591cf8101ff08411ae6c7
5
5
  SHA512:
6
- metadata.gz: 46feb241ff9d68a5664992212a4b5b30852359e94a9b97b4d9adc26189fa18a8112e3c2ee3ff7b8eeacd717b315a1d38535e7f2a44266c88cc029559529d5d5e
7
- data.tar.gz: 894d7860b229bf92db40bf7ca4583d1119da1b047b04899e99c2a3054e3c195770fddd0eb524312577acd678da3b7b5c4ffe3288c5a342efe83ac3bf536b5bb6
6
+ metadata.gz: b29bfb3cbb236ca9b384b5ee08db766a438dbc64aece96f1427a86fdb9389306b606fc12fe3f5bcbfa174ebfe810d88b1dd27fb55f77e084521dabb0db6083c1
7
+ data.tar.gz: 8d95140ec88142904d3614239bf2b1100876650a320fd4db38028493fe9a974d12342083e5f5b2f73fb48f79b5622fd13f6fb0b96940452621e4c82e769014f1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ### 0.2.0 / 2026-01-15
2
+ * Add Hiera backend (#33)
3
+
1
4
  ### 0.1.6 / 2026-01-08
2
5
  * Add Ruby 4.0 support (#77)
3
6
 
data/README.md CHANGED
@@ -42,6 +42,34 @@ Options:
42
42
 
43
43
  See the [`ComplianceEngine::Data`](https://rubydoc.info/gems/compliance_engine/ComplianceEngine/Data) class for details.
44
44
 
45
+ ## Using as a Puppet Module
46
+
47
+ The Compliance Engine can be used as a Puppet module to provide a Hiera backend for compliance data. This allows you to enforce compliance profiles through Hiera lookups within your Puppet manifests.
48
+
49
+ ### Hiera Backend
50
+
51
+ To use the Compliance Engine Hiera backend, configure it in your `hiera.yaml`:
52
+
53
+ ```yaml
54
+ ---
55
+ version: 5
56
+ hierarchy:
57
+ - name: "Compliance Engine"
58
+ lookup_key: compliance_engine::enforcement
59
+ ```
60
+
61
+ Specify the profile used by setting the `compliance_engine::enforcement` key in your Hiera data.
62
+
63
+ ```yaml
64
+ ---
65
+ compliance_engine::enforcement:
66
+ - your_profile
67
+ ```
68
+
69
+ The `compliance_engine::enforcement` function serves as the Hiera entry point and allows you to look up compliance data based on configured profiles.
70
+
71
+ For detailed information about available functions, parameters, and configuration options, see [REFERENCE.md](REFERENCE.md).
72
+
45
73
  ## Contributing
46
74
 
47
75
  Bug reports and pull requests are welcome on GitHub at https://github.com/simp/rubygem-simp-compliance_engine.
data/REFERENCE.md ADDED
@@ -0,0 +1,42 @@
1
+ # Reference
2
+
3
+ <!-- DO NOT EDIT: This document was generated by Puppet Strings -->
4
+
5
+ ## Table of Contents
6
+
7
+ ### Functions
8
+
9
+ * [`compliance_engine::enforcement`](#compliance_engine--enforcement): Hiera entry point for Compliance Engine
10
+
11
+ ## Functions
12
+
13
+ ### <a name="compliance_engine--enforcement"></a>`compliance_engine::enforcement`
14
+
15
+ Type: Ruby 4.x API
16
+
17
+ Hiera entry point for Compliance Engine
18
+
19
+ #### `compliance_engine::enforcement(String[1] $key, Hash[String[1], Any] $options, Puppet::LookupContext $context)`
20
+
21
+ The compliance_engine::enforcement function.
22
+
23
+ Returns: `String` The value of the key in the Hiera data
24
+
25
+ ##### `key`
26
+
27
+ Data type: `String[1]`
28
+
29
+ String The key to lookup in the Hiera data
30
+
31
+ ##### `options`
32
+
33
+ Data type: `Hash[String[1], Any]`
34
+
35
+
36
+
37
+ ##### `context`
38
+
39
+ Data type: `Puppet::LookupContext`
40
+
41
+
42
+
@@ -19,14 +19,14 @@ 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'])
22
+ spec.files = Dir.glob(['*.gemspec', '*.md', 'LICENSE', 'exe/*', 'lib/**/*.rb']).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
28
  spec.add_dependency 'irb', '~> 1.14'
29
- spec.add_dependency 'logger', '>= 1.4', '< 2.0'
29
+ spec.add_dependency 'logger', '~> 1.4'
30
30
  spec.add_dependency 'observer', '~> 0.1'
31
31
  spec.add_dependency 'rubyzip', '>= 2.3', '< 4'
32
32
  spec.add_dependency 'semantic_puppet', '~> 1.1'
@@ -134,7 +134,7 @@ class ComplianceEngine::Component
134
134
 
135
135
  fact == confine
136
136
  elsif confine.is_a?(Array)
137
- if depth == 0
137
+ if depth.zero?
138
138
  confine.any? { |value| fact_match?(fact, value, depth + 1) }
139
139
  else
140
140
  fact == confine
@@ -155,12 +155,13 @@ class ComplianceEngine::Component
155
155
  if k == 'module_name'
156
156
  unless environment_data.nil?
157
157
  return true unless environment_data.key?(v)
158
+
158
159
  module_version = fragment['confine']['module_version']
159
160
  unless module_version.nil?
160
161
  require 'semantic_puppet'
161
162
  begin
162
163
  return true unless SemanticPuppet::VersionRange.parse(module_version).include?(SemanticPuppet::Version.parse(environment_data[v]))
163
- rescue => e
164
+ rescue StandardError => e
164
165
  ComplianceEngine.log.error "Failed to compare #{v} #{environment_data[v]} with version confinement #{module_version}: #{e.message}"
165
166
  return true
166
167
  end
@@ -172,12 +173,8 @@ class ComplianceEngine::Component
172
173
  # Confinement based on Puppet facts
173
174
  unless facts.nil?
174
175
  fact = facts.dig(*k.split('.'))
175
- if fact.nil?
176
- return true
177
- end
178
- unless fact_match?(fact, v)
179
- return true
180
- end
176
+ return true if fact.nil?
177
+ return true unless fact_match?(fact, v)
181
178
  end
182
179
  end
183
180
  end
@@ -185,6 +182,34 @@ class ComplianceEngine::Component
185
182
  false
186
183
  end
187
184
 
185
+ # Check if a fragment is has remediation risk too high or if remediation is disabled
186
+ #
187
+ # @param fragment [Hash] The fragment to check
188
+ # @return [TrueClass, FalseClass] true if the fragment should be dropped
189
+ def risk_too_high?(fragment)
190
+ return false unless is_a?(ComplianceEngine::Check)
191
+ return false unless fragment.key?('remediation')
192
+ return false unless enforcement_tolerance.is_a?(Integer) && enforcement_tolerance.positive?
193
+
194
+ if fragment['remediation'].key?('disabled')
195
+ message = "Remediation disabled for #{fragment}"
196
+ reason = fragment['remediation']['disabled']&.map { |value| value['reason'] }&.reject(&:nil?)&.join("\n")
197
+ message += "\n#{reason}" unless reason.nil?
198
+ ComplianceEngine.log.info message
199
+ return true
200
+ end
201
+
202
+ if fragment['remediation'].key?('risk')
203
+ risk_level = fragment['remediation']['risk']&.map { |value| value['level'] }&.select { |value| value.is_a?(Integer) }&.max
204
+ if risk_level.is_a?(Integer) && risk_level >= enforcement_tolerance
205
+ ComplianceEngine.log.info "Remediation risk #{risk_level} exceeds enforcement enforcement_tolerance #{enforcement_tolerance} for #{fragment}"
206
+ return true
207
+ end
208
+ end
209
+
210
+ false
211
+ end
212
+
188
213
  # Returns the fragments of the component after confinement
189
214
  #
190
215
  # @return [Hash] the fragments of the component
@@ -208,25 +233,7 @@ class ComplianceEngine::Component
208
233
  end
209
234
 
210
235
  next if confine_away?(fragment)
211
-
212
- # Confinement based on remediation risk
213
- if enforcement_tolerance.is_a?(Integer) && is_a?(ComplianceEngine::Check) && fragment.key?('remediation')
214
- if fragment['remediation'].key?('disabled')
215
- message = "Remediation disabled for #{fragment}"
216
- reason = fragment['remediation']['disabled']&.map { |value| value['reason'] }&.reject { |value| value.nil? }&.join("\n")
217
- message += "\n#{reason}" unless reason.nil?
218
- ComplianceEngine.log.info message
219
- next
220
- end
221
-
222
- if fragment['remediation'].key?('risk')
223
- risk_level = fragment['remediation']['risk']&.map { |value| value['level'] }&.select { |value| value.is_a?(Integer) }&.max
224
- if risk_level.is_a?(Integer) && risk_level >= enforcement_tolerance
225
- ComplianceEngine.log.info "Remediation risk #{risk_level} exceeds enforcement tolerance #{enforcement_tolerance} for #{fragment}"
226
- next
227
- end
228
- end
229
- end
236
+ next if risk_too_high?(fragment)
230
237
 
231
238
  @fragments[filename] = fragment
232
239
  end
@@ -28,7 +28,7 @@ class ComplianceEngine::Data
28
28
  # @param facts [Hash] The facts to use while evaluating the data
29
29
  # @param enforcement_tolerance [Integer] The tolerance to use while evaluating the data
30
30
  def initialize(*paths, facts: nil, enforcement_tolerance: nil)
31
- @data ||= {}
31
+ @data = {}
32
32
  @facts = facts
33
33
  @enforcement_tolerance = enforcement_tolerance
34
34
  open(*paths) unless paths.nil? || paths.empty?
@@ -201,7 +201,7 @@ class ComplianceEngine::Data
201
201
  end
202
202
 
203
203
  reset_collection
204
- rescue => e
204
+ rescue StandardError => e
205
205
  ComplianceEngine.log.error e.message
206
206
  end
207
207
 
@@ -210,6 +210,7 @@ class ComplianceEngine::Data
210
210
  # @return [Array<String>]
211
211
  def files
212
212
  return @files unless @files.nil?
213
+
213
214
  @files = data.select { |_, file| file.key?(:content) }.keys
214
215
  end
215
216
 
@@ -219,7 +220,7 @@ class ComplianceEngine::Data
219
220
  # @return [Hash]
220
221
  def get(file)
221
222
  data[file][:content]
222
- rescue
223
+ rescue StandardError
223
224
  nil
224
225
  end
225
226
 
@@ -263,6 +264,7 @@ class ComplianceEngine::Data
263
264
  collection.each_value do |v|
264
265
  v.to_a.each do |component|
265
266
  next unless component.key?('confine')
267
+
266
268
  @confines = DeepMerge.deep_merge!(component['confine'], @confines)
267
269
  end
268
270
  end
@@ -404,9 +406,7 @@ class ComplianceEngine::Data
404
406
  # @return [TrueClass, FalseClass]
405
407
  def correlate(a, b)
406
408
  return false if a.nil? || b.nil?
407
- unless a.is_a?(Array) && b.is_a?(Hash)
408
- raise ComplianceEngine::Error, "Expected array and hash, got #{a.class} and #{b.class}"
409
- end
409
+ raise ComplianceEngine::Error, "Expected array and hash, got #{a.class} and #{b.class}" unless a.is_a?(Array) && b.is_a?(Hash)
410
410
  return false if a.empty? || b.empty?
411
411
 
412
412
  a.any? { |item| b[item] }
@@ -25,6 +25,7 @@ class ComplianceEngine::DataLoader
25
25
  # @raise [ComplianceEngine::Error] If the value is not a Hash
26
26
  def data=(value)
27
27
  raise ComplianceEngine::Error, 'Data must be a hash' unless value.is_a?(Hash)
28
+
28
29
  @data = value
29
30
  changed
30
31
  notify_observers(self)
@@ -13,6 +13,7 @@ class ComplianceEngine::EnvironmentLoader
13
13
  # @param zipfile_path [String, nil] the path to the zip file if loading from a zip archive
14
14
  def initialize(*paths, fileclass: File, dirclass: Dir, zipfile_path: nil)
15
15
  raise ArgumentError, 'No paths specified' if paths.empty?
16
+
16
17
  @modulepath ||= paths
17
18
  @zipfile_path = zipfile_path
18
19
  modules = paths.map do |path|
@@ -21,7 +22,7 @@ class ComplianceEngine::EnvironmentLoader
21
22
  .select { |child| fileclass.directory?(File.join(path, child)) }
22
23
  .map { |child| File.join(path, child) }
23
24
  .sort
24
- rescue
25
+ rescue StandardError
25
26
  []
26
27
  end
27
28
  modules.flatten!
@@ -27,7 +27,7 @@ class ComplianceEngine::ModuleLoader
27
27
  metadata = ComplianceEngine::DataLoader::Json.new(metadata_json, fileclass: fileclass)
28
28
  @name = metadata.data['name']
29
29
  @version = metadata.data['version']
30
- rescue => e
30
+ rescue StandardError => e
31
31
  ComplianceEngine.log.warn "Could not parse #{metadata_json}: #{e.message}"
32
32
  end
33
33
  end
@@ -53,7 +53,7 @@ class ComplianceEngine::ModuleLoader
53
53
  ComplianceEngine::DataLoader::Yaml.new(file.to_s, fileclass: fileclass, key: key)
54
54
  end
55
55
  @files << loader
56
- rescue => e
56
+ rescue StandardError => e
57
57
  ComplianceEngine.log.warn "Could not load #{file}: #{e.message}"
58
58
  end
59
59
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ComplianceEngine
4
- VERSION = '0.1.6'
4
+ VERSION = '0.2.0'
5
5
 
6
6
  # Handle supported compliance data versions
7
7
  class Version
@@ -11,6 +11,7 @@ module ComplianceEngine
11
11
  def initialize(version)
12
12
  raise 'Missing version' if version.nil?
13
13
  raise "Unsupported version '#{version}'" unless version == '2.0.0'
14
+
14
15
  @version = version
15
16
  end
16
17
 
@@ -30,7 +30,7 @@ module ComplianceEngine
30
30
  def self.log
31
31
  return @log unless @log.nil?
32
32
 
33
- @log = Logger.new(STDERR)
33
+ @log = Logger.new($stderr)
34
34
  @log.level = Logger::WARN
35
35
  @log
36
36
  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.1.6
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Pritchard
@@ -41,22 +41,16 @@ dependencies:
41
41
  name: logger
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - ">="
44
+ - - "~>"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '1.4'
47
- - - "<"
48
- - !ruby/object:Gem::Version
49
- version: '2.0'
50
47
  type: :runtime
51
48
  prerelease: false
52
49
  version_requirements: !ruby/object:Gem::Requirement
53
50
  requirements:
54
- - - ">="
51
+ - - "~>"
55
52
  - !ruby/object:Gem::Version
56
53
  version: '1.4'
57
- - - "<"
58
- - !ruby/object:Gem::Version
59
- version: '2.0'
60
54
  - !ruby/object:Gem::Dependency
61
55
  name: observer
62
56
  requirement: !ruby/object:Gem::Requirement
@@ -129,6 +123,7 @@ files:
129
123
  - CHANGELOG.md
130
124
  - LICENSE
131
125
  - README.md
126
+ - REFERENCE.md
132
127
  - compliance_engine.gemspec
133
128
  - exe/compliance_engine
134
129
  - lib/compliance_engine.rb
@@ -158,7 +153,7 @@ licenses:
158
153
  metadata:
159
154
  homepage_uri: https://simp-project.com/docs/sce/
160
155
  source_code_uri: https://github.com/simp/rubygem-simp-compliance_engine
161
- changelog_uri: https://github.com/simp/rubygem-simp-compliance_engine/releases/tag/0.1.6
156
+ changelog_uri: https://github.com/simp/rubygem-simp-compliance_engine/releases/tag/0.2.0
162
157
  bug_tracker_uri: https://github.com/simp/rubygem-simp-compliance_engine/issues
163
158
  rdoc_options: []
164
159
  require_paths: