inspec-core 4.1.4.preview → 4.2.0.preview
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/etc/deprecations.json +30 -30
- data/lib/inspec/cli.rb +5 -1
- data/lib/inspec/control_eval_context.rb +32 -6
- data/lib/inspec/dependencies/requirement.rb +1 -0
- data/lib/inspec/dependencies/resolver.rb +2 -0
- data/lib/inspec/dsl.rb +1 -1
- data/lib/inspec/impact.rb +1 -1
- data/lib/inspec/input_registry.rb +187 -46
- data/lib/inspec/objects/input.rb +276 -65
- data/lib/inspec/profile.rb +23 -16
- data/lib/inspec/profile_context.rb +9 -13
- data/lib/inspec/rspec_extensions.rb +5 -1
- data/lib/inspec/runner.rb +12 -52
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/mssql_session.rb +1 -1
- data/lib/resources/port.rb +4 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3cbd02d922c9f1c6cbe5d81b74598ac88ca10676
|
4
|
+
data.tar.gz: 31b971b6c1c65154a2952d4eb27ab6b1edb765ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91dc0c86668e00121588268d2828521a73e315efa0480919b09d3ee7e82e2dd4a9d93e88204a47e960c96e31ed425ff32e3131a60f94590053c417c3da0a2344
|
7
|
+
data.tar.gz: 45628e2783b3ba28495fa7efe504956c21a9462355a77ef87aa164e9738858b186f187c6c27f18629083b66a5c02cf77835e9fde5cb7c200d8dc6aa8e997152f
|
data/etc/deprecations.json
CHANGED
@@ -3,12 +3,12 @@
|
|
3
3
|
"unknown_group_action": "ignore",
|
4
4
|
"groups": {
|
5
5
|
"attrs_value_replaces_default": {
|
6
|
-
"action": "
|
6
|
+
"action": "warn",
|
7
7
|
"prefix": "The 'default' option for attributes is being replaced by 'value' - please use it instead."
|
8
8
|
},
|
9
9
|
"aws_resources_in_resource_pack": {
|
10
10
|
"comment": "See #3822",
|
11
|
-
"action": "
|
11
|
+
"action": "warn",
|
12
12
|
"prefix": "AWS resources shipped with core InSpec are being to moved to a resource pack for faster iteration. Please update your profiles to depend on git@github.com:inspec/inspec-aws.git ."
|
13
13
|
},
|
14
14
|
"cli_option_json_config": {
|
@@ -17,11 +17,11 @@
|
|
17
17
|
"comment": "See #3661"
|
18
18
|
},
|
19
19
|
"file_resource_be_mounted_matchers": {
|
20
|
-
"action": "
|
20
|
+
"action": "fail_control",
|
21
21
|
"suffix": "This will not be supported in InSpec 4.0."
|
22
22
|
},
|
23
23
|
"host_resource_proto_usage": {
|
24
|
-
"action": "
|
24
|
+
"action": "fail_control",
|
25
25
|
"suffix": "This will not be supported in InSpec 4.0."
|
26
26
|
},
|
27
27
|
"inspec_ui_methods": {
|
@@ -30,67 +30,67 @@
|
|
30
30
|
"comment": "See #3715"
|
31
31
|
},
|
32
32
|
"mssql_session_pass_option": {
|
33
|
-
"action": "
|
33
|
+
"action": "exit",
|
34
34
|
"suffix": "This will not be supported in InSpec 4.0."
|
35
35
|
},
|
36
36
|
"oracledb_session_pass_option": {
|
37
|
-
"action": "
|
38
|
-
"suffix": "This
|
37
|
+
"action": "exit",
|
38
|
+
"suffix": "This is not supported in InSpec 4.0."
|
39
39
|
},
|
40
40
|
"property_filesystem_size": {
|
41
|
-
"action": "
|
41
|
+
"action": "warn",
|
42
42
|
"comment": "See #3778"
|
43
43
|
},
|
44
44
|
"property_processes_list": {
|
45
|
-
"action": "
|
46
|
-
"suffix": "This property
|
45
|
+
"action": "fail_control",
|
46
|
+
"suffix": "This property was removed in InSpec 4.0."
|
47
47
|
},
|
48
48
|
"properties_aws_iam_user": {
|
49
|
-
"action": "
|
50
|
-
"suffix": "This property
|
49
|
+
"action": "fail_control",
|
50
|
+
"suffix": "This property was removed in InSpec 4.0."
|
51
51
|
},
|
52
52
|
"properties_shadow": {
|
53
|
-
"action": "
|
54
|
-
"suffix": "This property
|
53
|
+
"action": "fail_control",
|
54
|
+
"suffix": "This property was removed in InSpec 4.0."
|
55
55
|
},
|
56
56
|
"rename_attributes_to_inputs": {
|
57
|
-
"action": "
|
57
|
+
"action": "warn",
|
58
58
|
"prefix": "InSpec Attributes are being renamed to InSpec Inputs to avoid confusion with Chef Attributes.",
|
59
59
|
"comment": "See #3802"
|
60
60
|
},
|
61
61
|
"resource_apache": {
|
62
|
-
"action": "
|
63
|
-
"suffix": "This resource
|
62
|
+
"action": "exit",
|
63
|
+
"suffix": "This resource was removed in InSpec 4.0."
|
64
64
|
},
|
65
65
|
"resource_azure_generic_resource": {
|
66
66
|
"action": "warn",
|
67
67
|
"prefix": "The azure_generic_resource is deprecated. Please use a specific resource. See: 'https://github.com/inspec/inspec/issues/3131'"
|
68
68
|
},
|
69
69
|
"resource_iis_website": {
|
70
|
-
"action": "
|
71
|
-
"suffix": "This resource
|
70
|
+
"action": "exit",
|
71
|
+
"suffix": "This resource was removed in InSpec 4.0.",
|
72
72
|
"comment": "Needed for ServerSpec compatibility"
|
73
73
|
},
|
74
74
|
"resource_linux_kernel_parameter": {
|
75
|
-
"action": "
|
76
|
-
"suffix": "This resource
|
75
|
+
"action": "exit",
|
76
|
+
"suffix": "This resource was removed in InSpec 4.0.",
|
77
77
|
"comment": "Needed for ServerSpec compatibility"
|
78
78
|
},
|
79
79
|
"resource_ppa": {
|
80
|
-
"action": "
|
81
|
-
"suffix": "This resource
|
80
|
+
"action": "exit",
|
81
|
+
"suffix": "This resource was removed in InSpec 4.0.",
|
82
82
|
"comment": "Needed for ServerSpec compatibility"
|
83
83
|
},
|
84
84
|
"resource_script": {
|
85
|
-
"action": "
|
85
|
+
"action": "exit",
|
86
86
|
"suffix": "This resource will be removed in InSpec 4.0"
|
87
87
|
},
|
88
88
|
"resource_user_serverspec_compat": {
|
89
|
-
"action": "
|
89
|
+
"action": "fail_control"
|
90
90
|
},
|
91
91
|
"resource_windows_registry_key": {
|
92
|
-
"action": "
|
93
|
-
"suffix": "This resource
|
92
|
+
"action": "exit",
|
93
|
+
"suffix": "This resource was removed in InSpec 4.0.",
|
94
94
|
"comment": "Needed for ServerSpec compatibility"
|
95
95
|
},
|
96
96
|
"serverspec_compatibility": {
|
@@ -101,11 +101,11 @@
|
|
101
101
|
"action": "warn"
|
102
102
|
},
|
103
103
|
"mount_parser_serverspec_compat": {
|
104
|
-
"action": "
|
104
|
+
"action": "fail_control"
|
105
105
|
},
|
106
106
|
"wmi_non_hash_usage": {
|
107
|
-
"action": "
|
108
|
-
"suffix": "This property
|
107
|
+
"action": "fail_control",
|
108
|
+
"suffix": "This property was removed in InSpec 4.0."
|
109
109
|
}
|
110
110
|
}
|
111
111
|
}
|
data/lib/inspec/cli.rb
CHANGED
@@ -392,7 +392,11 @@ require 'license_acceptance/acceptor'
|
|
392
392
|
begin
|
393
393
|
if (commands_exempt_from_license_check & ARGV.map(&:downcase)).empty? && # Did they use a non-exempt command?
|
394
394
|
!ARGV.empty? # Did they supply at least one command?
|
395
|
-
LicenseAcceptance::Acceptor.check_and_persist(
|
395
|
+
LicenseAcceptance::Acceptor.check_and_persist(
|
396
|
+
'inspec',
|
397
|
+
Inspec::VERSION,
|
398
|
+
logger: Inspec::Log,
|
399
|
+
)
|
396
400
|
end
|
397
401
|
rescue LicenseAcceptance::LicenseNotAcceptedError
|
398
402
|
Inspec::Log.error 'InSpec cannot execute without accepting the license'
|
@@ -26,8 +26,23 @@ module Inspec
|
|
26
26
|
with_resource_dsl resources_dsl
|
27
27
|
|
28
28
|
# allow attributes to be accessed within control blocks
|
29
|
-
|
30
|
-
|
29
|
+
# TODO: deprecate name, use input()
|
30
|
+
define_method :attribute do |input_name, options = {}|
|
31
|
+
if options.empty?
|
32
|
+
# Simply an access, no event here
|
33
|
+
Inspec::InputRegistry.find_or_register_input(input_name, profile_id).value
|
34
|
+
else
|
35
|
+
options[:priority] = 20
|
36
|
+
options[:provider] = :inline_control_code
|
37
|
+
evt = Inspec::Input.infer_event(options)
|
38
|
+
Inspec::InputRegistry.find_or_register_input(input_name, profile_name, event: evt).value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Find the Input object, but don't collapse to a value.
|
43
|
+
# Will return nil on a miss.
|
44
|
+
define_method :input_object do |input_name|
|
45
|
+
Inspec::InputRegistry.find_or_register_input(input_name, profile_id)
|
31
46
|
end
|
32
47
|
|
33
48
|
# Support for Control DSL plugins.
|
@@ -168,14 +183,25 @@ module Inspec
|
|
168
183
|
end
|
169
184
|
|
170
185
|
# method for inputs; import input handling
|
171
|
-
|
172
|
-
|
173
|
-
|
186
|
+
# TODO: deprecate name, use input()
|
187
|
+
define_method :attribute do |input_name, options = {}|
|
188
|
+
if options.empty?
|
189
|
+
# Simply an access, no event here
|
190
|
+
Inspec::InputRegistry.find_or_register_input(input_name, profile_id).value
|
174
191
|
else
|
175
|
-
|
192
|
+
options[:priority] = 20
|
193
|
+
options[:provider] = :inline_control_code
|
194
|
+
evt = Inspec::Input.infer_event(options)
|
195
|
+
Inspec::InputRegistry.find_or_register_input(input_name, profile_name, event: evt).value
|
176
196
|
end
|
177
197
|
end
|
178
198
|
|
199
|
+
# Find the Input object, but don't collapse to a value.
|
200
|
+
# Will return nil on a miss.
|
201
|
+
define_method :input_object do |input_name|
|
202
|
+
Inspec::InputRegistry.find_or_register_input(input_name, profile_id)
|
203
|
+
end
|
204
|
+
|
179
205
|
define_method :skip_control do |id|
|
180
206
|
profile_context_owner.unregister_rule(id)
|
181
207
|
end
|
@@ -118,6 +118,7 @@ module Inspec
|
|
118
118
|
return @profile unless @profile.nil?
|
119
119
|
opts = @opts.dup
|
120
120
|
opts[:backend] = @backend
|
121
|
+
opts[:runner_conf] = Inspec::Config.cached
|
121
122
|
if !@dependencies.nil? && !@dependencies.empty?
|
122
123
|
opts[:dependencies] = Inspec::DependencySet.from_array(@dependencies, @cwd, @cache, @backend)
|
123
124
|
end
|
@@ -23,6 +23,7 @@ module Inspec
|
|
23
23
|
# implementation of the fetcher being used.
|
24
24
|
#
|
25
25
|
class Resolver
|
26
|
+
# Here deps is an Array of Hashes
|
26
27
|
def self.resolve(dependencies, cache, working_dir, backend)
|
27
28
|
reqs = dependencies.map do |dep|
|
28
29
|
req = Inspec::Requirement.from_metadata(dep, cache, cwd: working_dir, backend: backend)
|
@@ -47,6 +48,7 @@ module Inspec
|
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
51
|
+
# Here deps is an Array of Inspec::Requirement
|
50
52
|
def resolve(deps, top_level = true, seen_items = {}, path_string = '') # rubocop:disable Metrics/AbcSize
|
51
53
|
graph = {}
|
52
54
|
if top_level
|
data/lib/inspec/dsl.rb
CHANGED
@@ -79,7 +79,7 @@ module Inspec::DSL
|
|
79
79
|
|
80
80
|
def self.filter_included_controls(context, profile, &block)
|
81
81
|
mock = Inspec::Backend.create(Inspec::Config.mock)
|
82
|
-
include_ctx = Inspec::ProfileContext.for_profile(profile, mock
|
82
|
+
include_ctx = Inspec::ProfileContext.for_profile(profile, mock)
|
83
83
|
include_ctx.load(block) if block_given?
|
84
84
|
# remove all rules that were not registered
|
85
85
|
context.all_rules.each do |r|
|
data/lib/inspec/impact.rb
CHANGED
@@ -1,83 +1,224 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
require 'singleton'
|
3
3
|
require 'inspec/objects/input'
|
4
|
+
require 'inspec/secrets'
|
5
|
+
require 'inspec/exceptions'
|
4
6
|
|
5
7
|
module Inspec
|
8
|
+
# The InputRegistry's responsibilities include:
|
9
|
+
# - maintaining a list of Input objects that are bound to profiles
|
10
|
+
# - assisting in the lookup and creation of Inputs
|
6
11
|
class InputRegistry
|
7
12
|
include Singleton
|
8
13
|
extend Forwardable
|
9
14
|
|
10
|
-
attr_reader :
|
11
|
-
def_delegator :
|
12
|
-
def_delegator :
|
13
|
-
def_delegator :
|
14
|
-
def_delegator :
|
15
|
+
attr_reader :inputs_by_profile, :profile_aliases
|
16
|
+
def_delegator :inputs_by_profile, :each
|
17
|
+
def_delegator :inputs_by_profile, :[]
|
18
|
+
def_delegator :inputs_by_profile, :key?, :profile_known?
|
19
|
+
def_delegator :inputs_by_profile, :select
|
20
|
+
def_delegator :profile_aliases, :key?, :profile_alias?
|
15
21
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
instance.find_input(name, profile)
|
20
|
-
end
|
22
|
+
def initialize
|
23
|
+
# Keyed on String profile_name => Hash of String input_name => Input object
|
24
|
+
@inputs_by_profile = {}
|
21
25
|
|
22
|
-
|
23
|
-
|
26
|
+
# this is a list of optional profile name overrides set in the inspec.yml
|
27
|
+
@profile_aliases = {}
|
24
28
|
end
|
25
29
|
|
26
|
-
|
27
|
-
|
30
|
+
#-------------------------------------------------------------#
|
31
|
+
# Support for Profiles
|
32
|
+
#-------------------------------------------------------------#
|
33
|
+
|
34
|
+
def register_profile_alias(name, alias_name)
|
35
|
+
@profile_aliases[name] = alias_name
|
28
36
|
end
|
29
37
|
|
30
|
-
def
|
31
|
-
|
38
|
+
def list_inputs_for_profile(profile)
|
39
|
+
inputs_by_profile[profile] = {} unless profile_known?(profile)
|
40
|
+
inputs_by_profile[profile]
|
32
41
|
end
|
33
42
|
|
34
|
-
|
35
|
-
|
36
|
-
|
43
|
+
#-------------------------------------------------------------#
|
44
|
+
# Support for Individual Inputs
|
45
|
+
#-------------------------------------------------------------#
|
37
46
|
|
38
|
-
|
39
|
-
|
47
|
+
def find_or_register_input(input_name, profile_name, options = {})
|
48
|
+
if profile_alias?(profile_name)
|
49
|
+
alias_name = profile_name
|
50
|
+
profile_name = profile_aliases[profile_name]
|
51
|
+
handle_late_arriving_alias(alias_name, profile_name) if profile_known?(alias_name)
|
52
|
+
end
|
53
|
+
|
54
|
+
inputs_by_profile[profile_name] ||= {}
|
55
|
+
if inputs_by_profile[profile_name].key?(input_name)
|
56
|
+
inputs_by_profile[profile_name][input_name].update(options)
|
57
|
+
else
|
58
|
+
inputs_by_profile[profile_name][input_name] = Inspec::Input.new(input_name, options)
|
59
|
+
end
|
60
|
+
|
61
|
+
inputs_by_profile[profile_name][input_name]
|
40
62
|
end
|
41
63
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
64
|
+
# It is possible for a wrapper profile to create an input in metadata,
|
65
|
+
# referring to the child profile by an alias that has not yet been registered.
|
66
|
+
# The registry will then store the inputs under the alias, as if the alias
|
67
|
+
# were a true profile.
|
68
|
+
# If that happens and the child profile also mentions the input, we will
|
69
|
+
# need to move some things - all inputs should be stored under the true
|
70
|
+
# profile name, and no inputs should be stored under the alias.
|
71
|
+
def handle_late_arriving_alias(alias_name, profile_name)
|
72
|
+
inputs_by_profile[profile_name] ||= {}
|
73
|
+
inputs_by_profile[alias_name].each do |input_name, input_from_alias|
|
74
|
+
# Move the inpuut, or if it exists, merge events
|
75
|
+
existing = inputs_by_profile[profile_name][input_name]
|
76
|
+
if existing
|
77
|
+
existing.events.concat(input_from_alias.events)
|
78
|
+
else
|
79
|
+
inputs_by_profile[profile_name][input_name] = input_from_alias
|
80
|
+
end
|
48
81
|
end
|
82
|
+
# Finally, delete the (now copied-out) entry for the alias
|
83
|
+
inputs_by_profile.delete(alias_name)
|
84
|
+
end
|
85
|
+
#-------------------------------------------------------------#
|
86
|
+
# Support for Binding Inputs
|
87
|
+
#-------------------------------------------------------------#
|
88
|
+
|
89
|
+
# This method is called by the Profile as soon as it has
|
90
|
+
# enough context to allow binding inputs to it.
|
91
|
+
def bind_profile_inputs(profile_name, sources = {})
|
92
|
+
inputs_by_profile[profile_name] ||= {}
|
93
|
+
|
94
|
+
# In a more perfect world, we could let the core plugins choose
|
95
|
+
# self-determine what to do; but as-is, the APIs that call this
|
96
|
+
# are a bit over-constrained.
|
97
|
+
bind_inputs_from_metadata(profile_name, sources[:profile_metadata])
|
98
|
+
bind_inputs_from_input_files(profile_name, sources[:cli_input_files])
|
99
|
+
bind_inputs_from_runner_api(profile_name, sources[:runner_api])
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
49
103
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
104
|
+
def bind_inputs_from_runner_api(profile_name, input_hash)
|
105
|
+
# TODO: move this into a core plugin
|
106
|
+
|
107
|
+
return if input_hash.nil?
|
108
|
+
return if input_hash.empty?
|
109
|
+
|
110
|
+
# These arrive as a bare hash - values are raw values, not options
|
111
|
+
input_hash.each do |input_name, input_value|
|
112
|
+
loc = Inspec::Input::Event.probe_stack # TODO: likely modify this to look for a kitchen.yml, if that is realistic
|
113
|
+
evt = Inspec::Input::Event.new(
|
114
|
+
value: input_value,
|
115
|
+
provider: :runner_api, # TODO: suss out if audit cookbook or kitchen-inspec or something unknown
|
116
|
+
priority: 40,
|
117
|
+
file: loc.path,
|
118
|
+
line: loc.lineno,
|
119
|
+
)
|
120
|
+
find_or_register_input(input_name, profile_name, event: evt)
|
55
121
|
end
|
56
|
-
list[profile][name]
|
57
122
|
end
|
58
123
|
|
59
|
-
def
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
124
|
+
def bind_inputs_from_input_files(profile_name, file_list)
|
125
|
+
# TODO: move this into a core plugin
|
126
|
+
|
127
|
+
return if file_list.nil?
|
128
|
+
return if file_list.empty?
|
129
|
+
|
130
|
+
file_list.each do |path|
|
131
|
+
validate_inputs_file_readability!(path)
|
132
|
+
|
133
|
+
# TODO: drop this SecretsBackend stuff, will be handled by plugin system
|
134
|
+
data = Inspec::SecretsBackend.resolve(path)
|
135
|
+
if data.nil?
|
136
|
+
raise Inspec::Exceptions::SecretsBackendNotFound,
|
137
|
+
"Cannot find parser for inputs file '#{path}'. " \
|
138
|
+
'Check to make sure file has the appropriate extension.'
|
139
|
+
end
|
140
|
+
|
141
|
+
next if data.inputs.nil?
|
142
|
+
data.inputs.each do |input_name, input_value|
|
143
|
+
evt = Inspec::Input::Event.new(
|
144
|
+
value: input_value,
|
145
|
+
provider: :cli_files,
|
146
|
+
priority: 40,
|
147
|
+
file: path,
|
148
|
+
# TODO: any way we could get a line number?
|
149
|
+
)
|
150
|
+
find_or_register_input(input_name, profile_name, event: evt)
|
151
|
+
end
|
66
152
|
end
|
67
153
|
end
|
68
154
|
|
69
|
-
def
|
70
|
-
|
155
|
+
def validate_inputs_file_readability!(path)
|
156
|
+
unless File.exist?(path)
|
157
|
+
raise Inspec::Exceptions::InputsFileDoesNotExist,
|
158
|
+
"Cannot find input file '#{path}'. " \
|
159
|
+
'Check to make sure file exists.'
|
160
|
+
end
|
161
|
+
|
162
|
+
unless File.readable?(path)
|
163
|
+
raise Inspec::Exceptions::InputsFileNotReadable,
|
164
|
+
"Cannot read input file '#{path}'. " \
|
165
|
+
'Check to make sure file is readable.'
|
166
|
+
end
|
167
|
+
|
168
|
+
true
|
71
169
|
end
|
72
170
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
171
|
+
def bind_inputs_from_metadata(profile_name, profile_metadata_obj)
|
172
|
+
# TODO: move this into a core plugin
|
173
|
+
# TODO: add deprecation stuff
|
174
|
+
return if profile_metadata_obj.nil? # Metadata files are technically optional
|
175
|
+
|
176
|
+
if profile_metadata_obj.params.key?(:attributes) && profile_metadata_obj.params[:attributes].is_a?(Array)
|
177
|
+
profile_metadata_obj.params[:attributes].each do |input_orig|
|
178
|
+
input_options = input_orig.dup
|
179
|
+
input_name = input_options.delete(:name)
|
180
|
+
input_options.merge!({ priority: 30, provider: :profile_metadata, file: File.join(profile_name, 'inspec.yml') })
|
181
|
+
evt = Inspec::Input.infer_event(input_options)
|
182
|
+
|
183
|
+
# Profile metadata may set inputs in other profiles by naming them.
|
184
|
+
if input_options[:profile]
|
185
|
+
profile_name = input_options[:profile] || profile_name
|
186
|
+
# Override priority to force this to win. Allow user to set their own priority.
|
187
|
+
evt.priority = input_orig[:priority] || 35
|
188
|
+
end
|
189
|
+
find_or_register_input(input_name,
|
190
|
+
profile_name,
|
191
|
+
type: input_options[:type],
|
192
|
+
required: input_options[:required],
|
193
|
+
event: evt)
|
194
|
+
end
|
195
|
+
elsif profile_metadata_obj.params.key?(:attributes)
|
196
|
+
Inspec::Log.warn 'Inputs must be defined as an Array. Skipping current definition.'
|
197
|
+
end
|
76
198
|
end
|
77
199
|
|
200
|
+
#-------------------------------------------------------------#
|
201
|
+
# Other Support
|
202
|
+
#-------------------------------------------------------------#
|
203
|
+
public
|
204
|
+
|
205
|
+
# Used in testing
|
78
206
|
def __reset
|
79
|
-
@
|
207
|
+
@inputs_by_profile = {}
|
80
208
|
@profile_aliases = {}
|
81
209
|
end
|
210
|
+
|
211
|
+
# These class methods are convenience methods so you don't always
|
212
|
+
# have to call #instance when calling the registry
|
213
|
+
[
|
214
|
+
:find_or_register_input,
|
215
|
+
:register_profile_alias,
|
216
|
+
:list_inputs_for_profile,
|
217
|
+
:bind_profile_inputs,
|
218
|
+
].each do |meth|
|
219
|
+
define_singleton_method(meth) do |*args|
|
220
|
+
instance.send(meth, *args)
|
221
|
+
end
|
222
|
+
end
|
82
223
|
end
|
83
224
|
end
|
data/lib/inspec/objects/input.rb
CHANGED
@@ -14,6 +14,73 @@ end
|
|
14
14
|
|
15
15
|
module Inspec
|
16
16
|
class Input
|
17
|
+
#===========================================================================#
|
18
|
+
# Class Input::Event
|
19
|
+
#===========================================================================#
|
20
|
+
|
21
|
+
# Information about how the input obtained its value.
|
22
|
+
# Each time it changes, an Input::Event is added to the #events array.
|
23
|
+
class Event
|
24
|
+
EVENT_PROPERTIES = [
|
25
|
+
:action, # :create, :set, :fetch
|
26
|
+
:provider, # Name of the plugin
|
27
|
+
:priority, # Priority of this plugin for resolving conflicts. 1-100, higher numbers win.
|
28
|
+
:value, # New value, if provided.
|
29
|
+
:file, # File containing the input-changing action, if known
|
30
|
+
:line, # Line in file containing the input-changing action, if known
|
31
|
+
:hit, # if action is :fetch, true if the remote source had the input
|
32
|
+
].freeze
|
33
|
+
|
34
|
+
# Value has a special handler
|
35
|
+
EVENT_PROPERTIES.reject { |p| p == :value }.each do |prop|
|
36
|
+
attr_accessor prop
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :value
|
40
|
+
|
41
|
+
def initialize(properties = {})
|
42
|
+
@value_has_been_set = false
|
43
|
+
|
44
|
+
properties.each do |prop_name, prop_value|
|
45
|
+
if EVENT_PROPERTIES.include? prop_name
|
46
|
+
# OK, save the property
|
47
|
+
send((prop_name.to_s + '=').to_sym, prop_value)
|
48
|
+
else
|
49
|
+
raise "Unrecognized property to Input::Event: #{prop_name}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def value=(the_val)
|
55
|
+
# Even if set to nil or false, it has indeed been set; note that fact.
|
56
|
+
@value_has_been_set = true
|
57
|
+
@value = the_val
|
58
|
+
end
|
59
|
+
|
60
|
+
def value_has_been_set?
|
61
|
+
@value_has_been_set
|
62
|
+
end
|
63
|
+
|
64
|
+
def diagnostic_string
|
65
|
+
to_h.reject { |_, val| val.nil? }.to_a.map { |pair| "#{pair[0]}: '#{pair[1]}'" }.join(', ')
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_h
|
69
|
+
EVENT_PROPERTIES.each_with_object({}) do |prop, hash|
|
70
|
+
hash[prop] = send(prop)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.probe_stack
|
75
|
+
frames = caller_locations(2, 40)
|
76
|
+
frames.reject! { |f| f.path && f.path.include?('/lib/inspec/') }
|
77
|
+
frames.first
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
#===========================================================================#
|
82
|
+
# Class NO_VALUE_SET
|
83
|
+
#===========================================================================#
|
17
84
|
# This special class is used to represent the value when an input has
|
18
85
|
# not been assigned a value. This allows a user to explicitly assign nil
|
19
86
|
# to an input.
|
@@ -62,8 +129,11 @@ module Inspec
|
|
62
129
|
end
|
63
130
|
|
64
131
|
class Input
|
65
|
-
|
132
|
+
#===========================================================================#
|
133
|
+
# Class Inspec::Input
|
134
|
+
#===========================================================================#
|
66
135
|
|
136
|
+
# Validation types for input values
|
67
137
|
VALID_TYPES = %w{
|
68
138
|
String
|
69
139
|
Numeric
|
@@ -74,6 +144,21 @@ module Inspec
|
|
74
144
|
Any
|
75
145
|
}.freeze
|
76
146
|
|
147
|
+
# If you call `input` in a control file, the input will receive this priority.
|
148
|
+
# You can override that with a :priority option.
|
149
|
+
DEFAULT_PRIORITY_FOR_DSL_ATTRIBUTES = 20
|
150
|
+
|
151
|
+
# If you somehow manage to initialize an Input outside of the DSL,
|
152
|
+
# AND you don't provide an Input::Event, this is the priority you get.
|
153
|
+
DEFAULT_PRIORITY_FOR_UNKNOWN_CALLER = 10
|
154
|
+
|
155
|
+
# If you directly call value=, this is the priority assigned.
|
156
|
+
# This is the highest priority within InSpec core; though plugins
|
157
|
+
# are free to go higher.
|
158
|
+
DEFAULT_PRIORITY_FOR_VALUE_SET = 60
|
159
|
+
|
160
|
+
attr_reader :description, :events, :identifier, :name, :required, :title, :type
|
161
|
+
|
77
162
|
def initialize(name, options = {})
|
78
163
|
@name = name
|
79
164
|
@opts = options
|
@@ -82,49 +167,164 @@ module Inspec
|
|
82
167
|
if @opts.key?(:value)
|
83
168
|
Inspec::Log.warn "Input #{@name} created using both :default and :value options - ignoring :default"
|
84
169
|
@opts.delete(:default)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Array of Input::Event objects. These compete with one another to determine
|
174
|
+
# the value of the input when value() is called, as well as providing a
|
175
|
+
# debugging record of when and how the value changed.
|
176
|
+
@events = []
|
177
|
+
events.push make_creation_event(options)
|
178
|
+
|
179
|
+
update(options)
|
180
|
+
end
|
181
|
+
|
182
|
+
def set_events
|
183
|
+
events.select { |e| e.action == :set }
|
184
|
+
end
|
185
|
+
|
186
|
+
def diagnostic_string
|
187
|
+
"Input #{name}, with history:\n" +
|
188
|
+
events.map(&:diagnostic_string).map { |line| " #{line}" }.join("\n")
|
189
|
+
end
|
190
|
+
|
191
|
+
#--------------------------------------------------------------------------#
|
192
|
+
# Managing Value
|
193
|
+
#--------------------------------------------------------------------------#
|
194
|
+
|
195
|
+
def update(options)
|
196
|
+
_update_set_metadata(options)
|
197
|
+
normalize_type_restriction!
|
198
|
+
|
199
|
+
# Values are set by passing events in; but we can also infer an event.
|
200
|
+
if options.key?(:value) || options.key?(:default)
|
201
|
+
if options.key?(:event)
|
202
|
+
if options.key?(:value) || options.key?(:default)
|
203
|
+
Inspec::Log.warn "Do not provide both an Event and a value as an option to attribute('#{name}') - using value from event"
|
204
|
+
end
|
85
205
|
else
|
86
|
-
|
206
|
+
self.class.infer_event(options) # Sets options[:event]
|
87
207
|
end
|
88
208
|
end
|
89
|
-
|
90
|
-
|
209
|
+
events << options[:event] if options.key? :event
|
210
|
+
|
211
|
+
enforce_type_restriction!
|
91
212
|
end
|
92
213
|
|
93
|
-
|
94
|
-
|
95
|
-
|
214
|
+
# We can determine a value:
|
215
|
+
# 1. By event.value (preferred)
|
216
|
+
# 2. By options[:value]
|
217
|
+
# 3. By options[:default] (deprecated)
|
218
|
+
def self.infer_event(options)
|
219
|
+
# Don't rely on this working; you really should be passing a proper Input::Event
|
220
|
+
# with the context information you have.
|
221
|
+
location = Input::Event.probe_stack
|
222
|
+
event = Input::Event.new(
|
223
|
+
action: :set,
|
224
|
+
provider: options[:provider] || :unknown,
|
225
|
+
priority: options[:priority] || Inspec::Input::DEFAULT_PRIORITY_FOR_UNKNOWN_CALLER,
|
226
|
+
file: location.path,
|
227
|
+
line: location.lineno,
|
228
|
+
)
|
229
|
+
|
230
|
+
if options.key?(:default)
|
231
|
+
Inspec.deprecate(:attrs_value_replaces_default, "attribute name: '#{name}'")
|
232
|
+
if options.key?(:value)
|
233
|
+
Inspec::Log.warn "Input #{@name} created using both :default and :value options - ignoring :default"
|
234
|
+
options.delete(:default)
|
235
|
+
else
|
236
|
+
options[:value] = options.delete(:default)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
event.value = options[:value] if options.key?(:value)
|
240
|
+
options[:event] = event
|
96
241
|
end
|
97
242
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
243
|
+
private
|
244
|
+
|
245
|
+
def _update_set_metadata(options)
|
246
|
+
# Basic metadata
|
247
|
+
@title = options[:title] if options.key?(:title)
|
248
|
+
@description = options[:description] if options.key?(:description)
|
249
|
+
@required = options[:required] if options.key?(:required)
|
250
|
+
@identifier = options[:identifier] if options.key?(:identifier) # TODO: determine if this is ever used
|
251
|
+
@type = options[:type] if options.key?(:type)
|
252
|
+
end
|
253
|
+
|
254
|
+
def make_creation_event(options)
|
255
|
+
loc = options[:location] || Event.probe_stack
|
256
|
+
Input::Event.new(
|
257
|
+
action: :create,
|
258
|
+
provider: options[:provider],
|
259
|
+
file: loc.path,
|
260
|
+
line: loc.lineno,
|
261
|
+
)
|
262
|
+
end
|
263
|
+
|
264
|
+
# Determine the current winning value, but don't validate it
|
265
|
+
def current_value
|
266
|
+
# Examine the events to determine highest-priority value. Tie-break
|
267
|
+
# by using the last one set.
|
268
|
+
events_that_set_a_value = events.select(&:value_has_been_set?)
|
269
|
+
winning_priority = events_that_set_a_value.map(&:priority).max
|
270
|
+
winning_events = events_that_set_a_value.select { |e| e.priority == winning_priority }
|
271
|
+
winning_event = winning_events.last # Last for tie-break
|
272
|
+
|
273
|
+
if winning_event.nil?
|
274
|
+
# No value has been set - return special no value object
|
275
|
+
NO_VALUE_SET.new(name)
|
102
276
|
else
|
103
|
-
|
277
|
+
winning_event.value # May still be nil
|
104
278
|
end
|
105
279
|
end
|
106
280
|
|
107
|
-
|
108
|
-
|
281
|
+
public
|
282
|
+
|
283
|
+
def value=(new_value, priority = DEFAULT_PRIORITY_FOR_VALUE_SET)
|
284
|
+
# Inject a new Event with the new value.
|
285
|
+
location = Event.probe_stack
|
286
|
+
events << Event.new(
|
287
|
+
action: :set,
|
288
|
+
provider: :value_setter,
|
289
|
+
priority: priority,
|
290
|
+
value: new_value,
|
291
|
+
file: location.path,
|
292
|
+
line: location.lineno,
|
293
|
+
)
|
294
|
+
enforce_type_restriction!
|
295
|
+
|
296
|
+
new_value
|
109
297
|
end
|
110
298
|
|
111
|
-
def
|
112
|
-
|
299
|
+
def value
|
300
|
+
enforce_required_validation!
|
301
|
+
current_value
|
113
302
|
end
|
114
303
|
|
115
|
-
def
|
116
|
-
|
304
|
+
def has_value?
|
305
|
+
!current_value.is_a? NO_VALUE_SET
|
117
306
|
end
|
118
307
|
|
308
|
+
#--------------------------------------------------------------------------#
|
309
|
+
# Marshalling
|
310
|
+
#--------------------------------------------------------------------------#
|
311
|
+
|
119
312
|
def to_hash
|
120
|
-
{
|
121
|
-
|
122
|
-
|
123
|
-
|
313
|
+
as_hash = { name: name, options: {} }
|
314
|
+
[:description, :title, :identifier, :type, :required, :value].each do |field|
|
315
|
+
val = send(field)
|
316
|
+
next if val.nil?
|
317
|
+
as_hash[:options][field] = val
|
318
|
+
end
|
319
|
+
as_hash
|
320
|
+
end
|
321
|
+
|
322
|
+
def ruby_var_identifier
|
323
|
+
identifier || 'attr_' + name.downcase.strip.gsub(/\s+/, '-').gsub(/[^\w-]/, '')
|
124
324
|
end
|
125
325
|
|
126
326
|
def to_ruby
|
127
|
-
res = ["#{ruby_var_identifier} = attribute('#{
|
327
|
+
res = ["#{ruby_var_identifier} = attribute('#{name}',{"]
|
128
328
|
res.push " title: '#{title}'," unless title.to_s.empty?
|
129
329
|
res.push " value: #{value.inspect}," unless value.to_s.empty?
|
130
330
|
# to_ruby may generate code that is to be used by older versions of inspec.
|
@@ -136,37 +336,78 @@ module Inspec
|
|
136
336
|
res.join("\n")
|
137
337
|
end
|
138
338
|
|
339
|
+
#--------------------------------------------------------------------------#
|
340
|
+
# Value Type Coercion
|
341
|
+
#--------------------------------------------------------------------------#
|
342
|
+
|
139
343
|
def to_s
|
140
|
-
"Input #{
|
344
|
+
"Input #{name} with #{current_value}"
|
141
345
|
end
|
142
346
|
|
347
|
+
#--------------------------------------------------------------------------#
|
348
|
+
# Validation
|
349
|
+
#--------------------------------------------------------------------------#
|
350
|
+
|
143
351
|
private
|
144
352
|
|
145
|
-
def
|
353
|
+
def enforce_required_validation!
|
354
|
+
return unless required
|
146
355
|
# skip if we are not doing an exec call (archive/vendor/check)
|
147
356
|
return unless Inspec::BaseCLI.inspec_cli_command == :exec
|
148
357
|
|
149
|
-
|
150
|
-
if
|
358
|
+
proposed_value = current_value
|
359
|
+
if proposed_value.nil? || proposed_value.is_a?(NO_VALUE_SET)
|
151
360
|
error = Inspec::Input::RequiredError.new
|
152
|
-
error.input_name =
|
361
|
+
error.input_name = name
|
153
362
|
raise error, "Input '#{error.input_name}' is required and does not have a value."
|
154
363
|
end
|
155
364
|
end
|
156
365
|
|
157
|
-
def
|
158
|
-
|
366
|
+
def enforce_type_restriction! # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
367
|
+
return unless type
|
368
|
+
return unless has_value?
|
369
|
+
|
370
|
+
type_req = type
|
371
|
+
return if type_req == 'Any'
|
372
|
+
|
373
|
+
proposed_value = current_value
|
374
|
+
|
375
|
+
invalid_type = false
|
376
|
+
if type_req == 'Regexp'
|
377
|
+
invalid_type = true if !valid_regexp?(proposed_value)
|
378
|
+
elsif type_req == 'Numeric'
|
379
|
+
invalid_type = true if !valid_numeric?(proposed_value)
|
380
|
+
elsif type_req == 'Boolean'
|
381
|
+
invalid_type = true if ![true, false].include?(proposed_value)
|
382
|
+
elsif proposed_value.is_a?(Module.const_get(type_req)) == false
|
383
|
+
# TODO: why is this case here?
|
384
|
+
invalid_type = true
|
385
|
+
end
|
386
|
+
|
387
|
+
if invalid_type == true
|
388
|
+
error = Inspec::Input::ValidationError.new
|
389
|
+
error.input_name = @name
|
390
|
+
error.input_value = proposed_value
|
391
|
+
error.input_type = type_req
|
392
|
+
raise error, "Input '#{error.input_name}' with value '#{error.input_value}' does not validate to type '#{error.input_type}'."
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def normalize_type_restriction!
|
397
|
+
return unless type
|
398
|
+
|
399
|
+
type_req = type.capitalize
|
159
400
|
abbreviations = {
|
160
401
|
'Num' => 'Numeric',
|
161
402
|
'Regex' => 'Regexp',
|
162
403
|
}
|
163
|
-
|
164
|
-
if !VALID_TYPES.include?(
|
404
|
+
type_req = abbreviations[type_req] if abbreviations.key?(type_req)
|
405
|
+
if !VALID_TYPES.include?(type_req)
|
165
406
|
error = Inspec::Input::TypeError.new
|
166
|
-
error.input_type =
|
407
|
+
error.input_type = type_req
|
167
408
|
raise error, "Type '#{error.input_type}' is not a valid input type."
|
168
409
|
end
|
169
|
-
type
|
410
|
+
@type = type_req
|
170
411
|
end
|
171
412
|
|
172
413
|
def valid_numeric?(value)
|
@@ -177,41 +418,11 @@ module Inspec
|
|
177
418
|
end
|
178
419
|
|
179
420
|
def valid_regexp?(value)
|
180
|
-
# check for invalid regex
|
421
|
+
# check for invalid regex syntax
|
181
422
|
Regexp.new(value)
|
182
423
|
true
|
183
424
|
rescue
|
184
425
|
false
|
185
426
|
end
|
186
|
-
|
187
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
188
|
-
def validate_value_type(value)
|
189
|
-
type = validate_type(@opts[:type])
|
190
|
-
return if type == 'Any'
|
191
|
-
|
192
|
-
invalid_type = false
|
193
|
-
if type == 'Regexp'
|
194
|
-
invalid_type = true if !value.is_a?(String) || !valid_regexp?(value)
|
195
|
-
elsif type == 'Numeric'
|
196
|
-
invalid_type = true if !valid_numeric?(value)
|
197
|
-
elsif type == 'Boolean'
|
198
|
-
invalid_type = true if ![true, false].include?(value)
|
199
|
-
elsif value.is_a?(Module.const_get(type)) == false
|
200
|
-
invalid_type = true
|
201
|
-
end
|
202
|
-
|
203
|
-
if invalid_type == true
|
204
|
-
error = Inspec::Input::ValidationError.new
|
205
|
-
error.input_name = @name
|
206
|
-
error.input_value = value
|
207
|
-
error.input_type = type
|
208
|
-
raise error, "Input '#{error.input_name}' with value '#{error.input_value}' does not validate to type '#{error.input_type}'."
|
209
|
-
end
|
210
|
-
end
|
211
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
212
|
-
|
213
|
-
def value_or_dummy
|
214
|
-
@opts.key?(:value) ? @opts[:value] : Inspec::Input::NO_VALUE_SET.new(@name)
|
215
|
-
end
|
216
427
|
end
|
217
428
|
end
|
data/lib/inspec/profile.rb
CHANGED
@@ -81,7 +81,7 @@ module Inspec
|
|
81
81
|
end
|
82
82
|
|
83
83
|
attr_reader :source_reader, :backend, :runner_context, :check_mode
|
84
|
-
attr_accessor :parent_profile, :profile_name
|
84
|
+
attr_accessor :parent_profile, :profile_id, :profile_name
|
85
85
|
def_delegator :@source_reader, :tests
|
86
86
|
def_delegator :@source_reader, :libraries
|
87
87
|
def_delegator :@source_reader, :metadata
|
@@ -118,25 +118,32 @@ module Inspec
|
|
118
118
|
@runtime_profile = RuntimeProfile.new(self)
|
119
119
|
@backend.profile = @runtime_profile
|
120
120
|
|
121
|
+
# The AttributeRegistry is in charge of keeping track of inputs;
|
122
|
+
# it is the single source of truth. Now that we have a profile object,
|
123
|
+
# we can create any inputs that were provided by various mechanisms.
|
124
|
+
options[:runner_conf] ||= Inspec::Config.cached
|
125
|
+
|
126
|
+
if options[:runner_conf].key?(:attrs)
|
127
|
+
Inspec.deprecate(:rename_attributes_to_inputs, 'Use --input-file on the command line instead of --attrs.')
|
128
|
+
options[:runner_conf][:input_file] = options[:runner_conf].delete(:attrs)
|
129
|
+
end
|
130
|
+
|
131
|
+
Inspec::InputRegistry.bind_profile_inputs(
|
132
|
+
# Every input only exists in the context of a profile
|
133
|
+
metadata.params[:name], # TODO: test this with profile aliasing
|
134
|
+
# Remaining args are possible sources of inputs
|
135
|
+
cli_input_files: options[:runner_conf][:input_file], # From CLI --input-file
|
136
|
+
profile_metadata: metadata,
|
137
|
+
# TODO: deprecation checks here
|
138
|
+
runner_api: options[:runner_conf][:attributes], # This is the route the audit_cookbook and kitchen-inspec take
|
139
|
+
)
|
140
|
+
|
121
141
|
@runner_context =
|
122
142
|
options[:profile_context] ||
|
123
|
-
Inspec::ProfileContext.for_profile(self, @backend
|
143
|
+
Inspec::ProfileContext.for_profile(self, @backend)
|
124
144
|
|
125
145
|
@supports_platform = metadata.supports_platform?(@backend)
|
126
146
|
@supports_runtime = metadata.supports_runtime?
|
127
|
-
register_metadata_inputs
|
128
|
-
end
|
129
|
-
|
130
|
-
def register_metadata_inputs # TODO: deprecate
|
131
|
-
if metadata.params.key?(:attributes) && metadata.params[:attributes].is_a?(Array)
|
132
|
-
metadata.params[:attributes].each do |attribute|
|
133
|
-
attr_dup = attribute.dup
|
134
|
-
name = attr_dup.delete(:name)
|
135
|
-
@runner_context.register_input(name, attr_dup)
|
136
|
-
end
|
137
|
-
elsif metadata.params.key?(:attributes)
|
138
|
-
Inspec::Log.warn 'Inputs must be defined as an Array. Skipping current definition.'
|
139
|
-
end
|
140
147
|
end
|
141
148
|
|
142
149
|
def name
|
@@ -595,7 +602,7 @@ module Inspec
|
|
595
602
|
f = load_rule_filepath(prefix, rule)
|
596
603
|
load_rule(rule, f, controls, groups)
|
597
604
|
end
|
598
|
-
params[:inputs] = @
|
605
|
+
params[:inputs] = Inspec::InputRegistry.list_inputs_for_profile(@profile_id)
|
599
606
|
params
|
600
607
|
end
|
601
608
|
|
@@ -12,13 +12,11 @@ require 'inspec/objects/input'
|
|
12
12
|
|
13
13
|
module Inspec
|
14
14
|
class ProfileContext
|
15
|
-
def self.for_profile(profile, backend
|
16
|
-
new(profile.name, backend, { 'profile' => profile,
|
17
|
-
'inputs' => inputs,
|
18
|
-
'check_mode' => profile.check_mode })
|
15
|
+
def self.for_profile(profile, backend)
|
16
|
+
new(profile.name, backend, { 'profile' => profile, 'check_mode' => profile.check_mode })
|
19
17
|
end
|
20
18
|
|
21
|
-
attr_reader :
|
19
|
+
attr_reader :backend, :profile_name, :profile_id, :resource_registry
|
22
20
|
attr_accessor :rules
|
23
21
|
def initialize(profile_id, backend, conf)
|
24
22
|
if backend.nil?
|
@@ -35,7 +33,8 @@ module Inspec
|
|
35
33
|
@lib_subcontexts = []
|
36
34
|
@require_loader = ::Inspec::RequireLoader.new
|
37
35
|
Inspec::InputRegistry.register_profile_alias(@profile_id, @profile_name) if @profile_id != @profile_name
|
38
|
-
|
36
|
+
# TODO: consider polling input source plugins; this is a bulk fetch opportunity
|
37
|
+
|
39
38
|
# A local resource registry that only contains resources defined
|
40
39
|
# in the transitive dependency tree of the loaded profile.
|
41
40
|
@resource_registry = Inspec::Resource.new_registry
|
@@ -43,6 +42,10 @@ module Inspec
|
|
43
42
|
@current_load = nil
|
44
43
|
end
|
45
44
|
|
45
|
+
def attributes
|
46
|
+
Inspec::AttributeRegistry.list_attributes_for_profile(@profile_id)
|
47
|
+
end
|
48
|
+
|
46
49
|
def dependencies
|
47
50
|
if @conf['profile'].nil?
|
48
51
|
{}
|
@@ -187,13 +190,6 @@ module Inspec
|
|
187
190
|
end
|
188
191
|
end
|
189
192
|
|
190
|
-
def register_input(name, options = {})
|
191
|
-
# we need to return an input object, to allow dermination of values
|
192
|
-
input = Inspec::InputRegistry.register_input(name, @profile_id, options)
|
193
|
-
input.value = @conf['inputs'][name] unless @conf['inputs'].nil? || @conf['inputs'][name].nil?
|
194
|
-
input.value
|
195
|
-
end
|
196
|
-
|
197
193
|
def set_header(field, val)
|
198
194
|
@current_load[field] = val
|
199
195
|
end
|
@@ -66,9 +66,13 @@ end
|
|
66
66
|
class RSpec::Core::ExampleGroup
|
67
67
|
# This DSL method allows us to access the values of inputs within InSpec tests
|
68
68
|
def attribute(name)
|
69
|
-
Inspec::InputRegistry.
|
69
|
+
Inspec::InputRegistry.find_or_register_input(name, self.class.metadata[:profile_id]).value
|
70
70
|
end
|
71
71
|
define_example_method :attribute
|
72
|
+
def input_obj(name)
|
73
|
+
Inspec::InputRegistry.find_or_register_input(name, self.class.metadata[:profile_id])
|
74
|
+
end
|
75
|
+
define_example_method :input_obj
|
72
76
|
|
73
77
|
# Here, we have to ensure our method_missing gets called prior
|
74
78
|
# to RSpec::Core::ExampleGroup.method_missing (the class method).
|
data/lib/inspec/runner.rb
CHANGED
@@ -9,7 +9,6 @@ require 'inspec/backend'
|
|
9
9
|
require 'inspec/profile_context'
|
10
10
|
require 'inspec/profile'
|
11
11
|
require 'inspec/metadata'
|
12
|
-
require 'inspec/secrets'
|
13
12
|
require 'inspec/config'
|
14
13
|
require 'inspec/dependencies/cache'
|
15
14
|
# spec requirements
|
@@ -32,7 +31,7 @@ module Inspec
|
|
32
31
|
class Runner
|
33
32
|
extend Forwardable
|
34
33
|
|
35
|
-
attr_reader :backend, :rules
|
34
|
+
attr_reader :backend, :rules
|
36
35
|
|
37
36
|
def attributes
|
38
37
|
Inspec.deprecate(:rename_attributes_to_inputs, "Don't call runner.attributes, call runner.inputs")
|
@@ -57,10 +56,17 @@ module Inspec
|
|
57
56
|
RunnerRspec.new(@conf)
|
58
57
|
end
|
59
58
|
|
60
|
-
#
|
61
|
-
@
|
59
|
+
# About reading inputs:
|
60
|
+
# @conf gets passed around a lot, eventually to
|
61
|
+
# Inspec::InputRegistry.register_external_inputs.
|
62
|
+
#
|
63
|
+
# @conf may contain the key :attributes or :inputs, which is to be a Hash
|
64
|
+
# of values passed in from the Runner API.
|
65
|
+
# This is how kitchen-inspec and the audit_cookbook pass in inputs.
|
66
|
+
#
|
67
|
+
# @conf may contain the key :attrs or :input_file, which is to be an Array
|
68
|
+
# of file paths, each a YAML file. This how --input-file works.
|
62
69
|
|
63
|
-
load_inputs(@conf)
|
64
70
|
configure_transport
|
65
71
|
end
|
66
72
|
|
@@ -101,7 +107,6 @@ module Inspec
|
|
101
107
|
@test_collector.add_profile(requirement.profile)
|
102
108
|
end
|
103
109
|
|
104
|
-
@inputs = profile.runner_context.inputs if @inputs.empty?
|
105
110
|
tests = profile.collect_tests
|
106
111
|
all_controls += tests unless tests.nil?
|
107
112
|
end
|
@@ -149,35 +154,6 @@ module Inspec
|
|
149
154
|
@test_collector.exit_code
|
150
155
|
end
|
151
156
|
|
152
|
-
# determine all inputs before the execution, fetch data from secrets backend
|
153
|
-
def load_inputs(options)
|
154
|
-
# TODO: - rename :attributes - it is user-visible
|
155
|
-
options[:attributes] ||= {}
|
156
|
-
|
157
|
-
if options.key?(:attrs)
|
158
|
-
Inspec.deprecate(:rename_attributes_to_inputs, 'Use --input-file on the command line instead of --attrs.')
|
159
|
-
options[:input_file] = options.delete(:attrs)
|
160
|
-
end
|
161
|
-
secrets_targets = options[:input_file]
|
162
|
-
return options[:attributes] if secrets_targets.nil?
|
163
|
-
|
164
|
-
secrets_targets.each do |target|
|
165
|
-
validate_inputs_file_readability!(target)
|
166
|
-
|
167
|
-
secrets = Inspec::SecretsBackend.resolve(target)
|
168
|
-
if secrets.nil?
|
169
|
-
raise Inspec::Exceptions::SecretsBackendNotFound,
|
170
|
-
"Cannot find parser for inputs file '#{target}'. " \
|
171
|
-
'Check to make sure file has the appropriate extension.'
|
172
|
-
end
|
173
|
-
|
174
|
-
next if secrets.inputs.nil?
|
175
|
-
options[:attributes].merge!(secrets.inputs)
|
176
|
-
end
|
177
|
-
|
178
|
-
options[:attributes]
|
179
|
-
end
|
180
|
-
|
181
157
|
#
|
182
158
|
# add_target allows the user to add a target whose tests will be
|
183
159
|
# run when the user calls the run method.
|
@@ -209,7 +185,7 @@ module Inspec
|
|
209
185
|
vendor_cache: @cache,
|
210
186
|
backend: @backend,
|
211
187
|
controls: @controls,
|
212
|
-
|
188
|
+
runner_conf: @conf)
|
213
189
|
raise "Could not resolve #{target} to valid input." if profile.nil?
|
214
190
|
@target_profiles << profile if supports_profile?(profile)
|
215
191
|
end
|
@@ -300,22 +276,6 @@ module Inspec
|
|
300
276
|
examples.each { |e| @test_collector.add_test(e, rule) }
|
301
277
|
end
|
302
278
|
|
303
|
-
def validate_inputs_file_readability!(target)
|
304
|
-
unless File.exist?(target)
|
305
|
-
raise Inspec::Exceptions::InputsFileDoesNotExist,
|
306
|
-
"Cannot find input file '#{target}'. " \
|
307
|
-
'Check to make sure file exists.'
|
308
|
-
end
|
309
|
-
|
310
|
-
unless File.readable?(target)
|
311
|
-
raise Inspec::Exceptions::InputsFileNotReadable,
|
312
|
-
"Cannot read input file '#{target}'. " \
|
313
|
-
'Check to make sure file is readable.'
|
314
|
-
end
|
315
|
-
|
316
|
-
true
|
317
|
-
end
|
318
|
-
|
319
279
|
def rspec_skipped_block(arg, opts, message)
|
320
280
|
@test_collector.example_group(*arg, opts) do
|
321
281
|
# Send custom `it` block to RSpec
|
data/lib/inspec/version.rb
CHANGED
data/lib/resources/port.rb
CHANGED
@@ -569,6 +569,10 @@ module Inspec::Resources
|
|
569
569
|
# example: ::ffff:10.0.2.15:9200
|
570
570
|
host.delete!('::ffff:') if host.start_with?('::ffff:')
|
571
571
|
|
572
|
+
# To remove brackets that might surround the IPv6 address
|
573
|
+
# example: [::] and [fe80::dc11:b9b6:514b:134]%eth0:123
|
574
|
+
host = host.tr('[]', '')
|
575
|
+
|
572
576
|
# if there's an interface name in the local address, which is common for
|
573
577
|
# IPv6 listeners, strip that out too.
|
574
578
|
# example: fe80::a00:27ff:fe32:ed09%enp0s3
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: inspec-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.2.0.preview
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dominik Richter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-04-
|
11
|
+
date: 2019-04-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: train-core
|