inspec-core 4.1.4.preview → 4.2.0.preview
Sign up to get free protection for your applications and to get access to all the features.
- 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
|