inspec-core 4.41.20 → 4.52.9

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -0
  3. data/etc/deprecations.json +1 -1
  4. data/lib/bundles/inspec-supermarket/README.md +21 -2
  5. data/lib/bundles/inspec-supermarket/cli.rb +20 -3
  6. data/lib/bundles/inspec-supermarket/target.rb +3 -2
  7. data/lib/inspec/base_cli.rb +12 -0
  8. data/lib/inspec/cli.rb +21 -4
  9. data/lib/inspec/control_eval_context.rb +40 -39
  10. data/lib/inspec/dsl.rb +18 -3
  11. data/lib/inspec/globals.rb +5 -0
  12. data/lib/inspec/plugin/v1/registry.rb +1 -1
  13. data/lib/inspec/profile.rb +115 -2
  14. data/lib/inspec/resources/auditd.rb +5 -4
  15. data/lib/inspec/resources/cassandra.rb +64 -0
  16. data/lib/inspec/resources/cassandradb_conf.rb +47 -0
  17. data/lib/inspec/resources/cassandradb_session.rb +68 -0
  18. data/lib/inspec/resources/chrony_conf.rb +55 -0
  19. data/lib/inspec/resources/csv.rb +26 -3
  20. data/lib/inspec/resources/groups.rb +22 -3
  21. data/lib/inspec/resources/http.rb +135 -54
  22. data/lib/inspec/resources/ibmdb2_conf.rb +57 -0
  23. data/lib/inspec/resources/ibmdb2_session.rb +69 -0
  24. data/lib/inspec/resources/mssql_sys_conf.rb +48 -0
  25. data/lib/inspec/resources/opa.rb +4 -1
  26. data/lib/inspec/resources/oracle.rb +66 -0
  27. data/lib/inspec/resources/oracledb_conf.rb +40 -0
  28. data/lib/inspec/resources/oracledb_listener_conf.rb +123 -0
  29. data/lib/inspec/resources/oracledb_session.rb +25 -6
  30. data/lib/inspec/resources/packages.rb +21 -0
  31. data/lib/inspec/resources/postgres_session.rb +15 -4
  32. data/lib/inspec/resources/service.rb +59 -10
  33. data/lib/inspec/resources/ssl.rb +7 -0
  34. data/lib/inspec/resources/sybase_conf.rb +37 -0
  35. data/lib/inspec/resources/sybase_session.rb +111 -0
  36. data/lib/inspec/resources/users.rb +16 -2
  37. data/lib/inspec/resources/windows_firewall.rb +1 -1
  38. data/lib/inspec/resources.rb +9 -0
  39. data/lib/inspec/run_data/profile.rb +0 -2
  40. data/lib/inspec/version.rb +1 -1
  41. metadata +14 -2
@@ -0,0 +1,68 @@
1
+ module Inspec::Resources
2
+ class Lines
3
+ attr_reader :output
4
+
5
+ def initialize(raw, desc)
6
+ @output = raw
7
+ @desc = desc
8
+ end
9
+
10
+ def to_s
11
+ @desc
12
+ end
13
+ end
14
+
15
+ class CassandradbSession < Inspec.resource(1)
16
+ name "cassandradb_session"
17
+ supports platform: "unix"
18
+ supports platform: "windows"
19
+ desc "Use the cassandradb_session InSpec resource to test commands against an Cassandra database"
20
+ example <<~EXAMPLE
21
+ cql = cassandradb_session(user: 'my_user', password: 'password', host: 'host', port: 'port')
22
+ describe cql.query("SELECT cluster_name FROM system.local") do
23
+ its('output') { should match /Test Cluster/ }
24
+ end
25
+ EXAMPLE
26
+
27
+ attr_reader :user, :password, :host, :port
28
+
29
+ def initialize(opts = {})
30
+ @user = opts[:user] || "cassandra"
31
+ @password = opts[:password] || "cassandra"
32
+ @host = opts[:host]
33
+ @port = opts[:port]
34
+ end
35
+
36
+ def query(q)
37
+ cassandra_cmd = create_cassandra_cmd(q)
38
+ cmd = inspec.command(cassandra_cmd)
39
+ out = cmd.stdout + "\n" + cmd.stderr
40
+ if cmd.exit_status != 0 || out =~ /Unable to connect to any servers/ || out.downcase =~ /^error:.*/
41
+ raise Inspec::Exceptions::ResourceFailed, "Cassandra query with errors: #{out}"
42
+ else
43
+ Lines.new(cmd.stdout.strip, "Cassandra query: #{q}")
44
+ end
45
+ end
46
+
47
+ def to_s
48
+ "Cassandra DB Session"
49
+ end
50
+
51
+ private
52
+
53
+ def create_cassandra_cmd(q)
54
+ # TODO: simple escape, must be handled by a library
55
+ # that does this securely
56
+ escaped_query = q.gsub(/\\/, "\\\\").gsub(/"/, '\\"').gsub(/\$/, '\\$')
57
+
58
+ # construct the query
59
+ command = "cqlsh"
60
+ command += " #{@host}" unless @host.nil?
61
+ command += " #{@port}" unless @port.nil?
62
+ command += " -u #{@user}"
63
+ command += " -p #{@password}"
64
+ command += " --execute '#{escaped_query}'"
65
+ command
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,55 @@
1
+ # chrony_conf
2
+
3
+ require "inspec/utils/simpleconfig"
4
+ require "inspec/utils/file_reader"
5
+
6
+ module Inspec::Resources
7
+ class ChronyConf < Inspec.resource(1)
8
+ name "chrony_conf"
9
+ supports platform: "unix"
10
+ desc "Use the chrony_conf InSpec audit resource to test the synchronization settings defined in the chrony.conf file. This file is typically located at /etc/chrony.conf."
11
+ example <<~EXAMPLE
12
+ describe chrony_conf do
13
+ its('server') { should_not cmp nil }
14
+ its('restrict') { should include '-4 default kod notrap nomodify nopeer noquery' }
15
+ its('pool') { should include 'pool.ntp.org iburst' }
16
+ its('driftfile') { should cmp '/var/lib/ntp/drift' }
17
+ its('allow') { should cmp nil }
18
+ its('keyfile') { should cmp '/etc/chrony.keys' }
19
+ end
20
+ EXAMPLE
21
+
22
+ include FileReader
23
+
24
+ def initialize(path = nil)
25
+ @conf_path = path || "/etc/chrony.conf"
26
+ @content = read_file_content(@conf_path)
27
+ end
28
+
29
+ def method_missing(name)
30
+ param = read_params[name.to_s]
31
+ # extract first value if we have only one value in array
32
+ return param[0] if param.is_a?(Array) && (param.length == 1)
33
+
34
+ param
35
+ end
36
+
37
+ def to_s
38
+ "chrony.conf"
39
+ end
40
+
41
+ private
42
+
43
+ def read_params
44
+ return @params if defined?(@params)
45
+
46
+ # parse the file
47
+ conf = SimpleConfig.new(
48
+ @content,
49
+ assignment_regex: /^\s*(\S+)\s+(.*)\s*$/,
50
+ multiple_values: true
51
+ )
52
+ @params = conf.params
53
+ end
54
+ end
55
+ end
@@ -11,14 +11,28 @@ module Inspec::Resources
11
11
  describe csv('example.csv') do
12
12
  its('name') { should eq(['John', 'Alice']) }
13
13
  end
14
+
15
+ describe csv('example.csv', false).params do
16
+ its[[0]] { should eq (['name', 'col1', 'col2']) }
17
+ emd
14
18
  EXAMPLE
15
19
 
20
+ def initialize(path, headers = true)
21
+ @headers = headers
22
+ super(path)
23
+ end
24
+
16
25
  # override the parse method from JsonConfig
17
26
  # Assuming a header row of name,col1,col2, it will output an array of hashes like so:
18
27
  # [
19
28
  # { 'name' => 'row1', 'col1' => 'value1', 'col2' => 'value2' },
20
29
  # { 'name' => 'row2', 'col1' => 'value3', 'col2' => 'value4' }
21
30
  # ]
31
+ # When headers is set to false it will return data as array of array
32
+ # [
33
+ # ['name', col1', 'col2'],
34
+ # ['row2', 'value3', 'value4']
35
+ # ]
22
36
  def parse(content)
23
37
  require "csv" unless defined?(CSV)
24
38
 
@@ -28,10 +42,14 @@ module Inspec::Resources
28
42
  end
29
43
 
30
44
  # implicit conversion of values
31
- csv = CSV.new(content, headers: true, converters: %i{all blank_to_nil})
45
+ csv = CSV.new(content, headers: @headers, converters: %i{all blank_to_nil})
32
46
 
33
47
  # convert to hash
34
- csv.to_a.map(&:to_hash)
48
+ if @headers
49
+ csv.to_a.map(&:to_hash)
50
+ else
51
+ csv.to_a
52
+ end
35
53
  rescue => e
36
54
  raise Inspec::Exceptions::ResourceFailed, "Unable to parse CSV: #{e.message}"
37
55
  end
@@ -42,7 +60,12 @@ module Inspec::Resources
42
60
  # #value method from JsonConfig (which uses ObjectTraverser.extract_value)
43
61
  # doesn't make sense here.
44
62
  def value(key)
45
- @params.map { |x| x[key.first.to_s] }.compact
63
+ if @headers
64
+ @params.map { |x| x[key.first.to_s] }.compact
65
+ else
66
+ # when headers is set to false send the array as it is.
67
+ @params
68
+ end
46
69
  end
47
70
 
48
71
  private
@@ -22,6 +22,18 @@ module Inspec::Resources
22
22
  end
23
23
  end
24
24
 
25
+ # Class defined to check for members without case-sensitivity
26
+ class Members < Array
27
+ def initialize(group_members)
28
+ @group_members = group_members
29
+ super
30
+ end
31
+
32
+ def include?(user)
33
+ !(@group_members.select { |group_member| group_member.casecmp?(user) }.empty?)
34
+ end
35
+ end
36
+
25
37
  class Groups < Inspec.resource(1)
26
38
  include GroupManagementSelector
27
39
 
@@ -82,6 +94,7 @@ module Inspec::Resources
82
94
  # its('gid') { should eq 0 }
83
95
  # end
84
96
  #
97
+
85
98
  class Group < Inspec.resource(1)
86
99
  include GroupManagementSelector
87
100
 
@@ -118,11 +131,13 @@ module Inspec::Resources
118
131
  end
119
132
 
120
133
  def members
121
- flatten_entry(group_info, "members") || empty_value_for_members
134
+ members_list = flatten_entry(group_info, "members") || empty_value_for_members
135
+ inspec.os.windows? ? Members.new(members_list) : members_list
122
136
  end
123
137
 
124
138
  def members_array
125
- flatten_entry(group_info, "members_array") || []
139
+ members_list = flatten_entry(group_info, "members_array") || []
140
+ inspec.os.windows? ? Members.new(members_list) : members_list
126
141
  end
127
142
 
128
143
  def local
@@ -150,7 +165,11 @@ module Inspec::Resources
150
165
  def group_info
151
166
  # we need a local copy for the block
152
167
  group = @group.dup
153
- @groups_cache ||= inspec.groups.where { name == group }
168
+ if inspec.os.windows?
169
+ @groups_cache ||= inspec.groups.where { name.casecmp?(group) }
170
+ else
171
+ @groups_cache ||= inspec.groups.where { name == group }
172
+ end
154
173
  end
155
174
 
156
175
  def empty_value_for_members
@@ -11,6 +11,7 @@ module Inspec::Resources
11
11
  class Http < Inspec.resource(1)
12
12
  name "http"
13
13
  supports platform: "unix"
14
+ supports platform: "windows"
14
15
  desc "Use the http InSpec audit resource to test http call."
15
16
  example <<~EXAMPLE
16
17
  describe http('http://localhost:8080/ping', auth: {user: 'user', pass: 'test'}, params: {format: 'html'}) do
@@ -104,6 +105,7 @@ module Inspec::Resources
104
105
  opts[:data]
105
106
  end
106
107
 
108
+ # not supported on Windows
107
109
  def open_timeout
108
110
  opts.fetch(:open_timeout, 60)
109
111
  end
@@ -117,7 +119,11 @@ module Inspec::Resources
117
119
  end
118
120
 
119
121
  def max_redirects
120
- opts.fetch(:max_redirects, 0)
122
+ opts.fetch(:max_redirects, nil)
123
+ end
124
+
125
+ def proxy
126
+ opts.fetch(:proxy, nil)
121
127
  end
122
128
  end
123
129
 
@@ -139,12 +145,18 @@ module Inspec::Resources
139
145
  def response
140
146
  return @response if @response
141
147
 
148
+ Faraday.ignore_env_proxy = true if proxy == "disable"
149
+
142
150
  conn = Faraday.new(url: url, headers: request_headers, params: params, ssl: { verify: ssl_verify? }) do |builder|
143
151
  builder.request :url_encoded
144
- builder.use FaradayMiddleware::FollowRedirects, limit: max_redirects if max_redirects > 0
152
+ builder.use FaradayMiddleware::FollowRedirects, limit: max_redirects unless max_redirects.nil?
145
153
  builder.adapter Faraday.default_adapter
146
154
  end
147
155
 
156
+ unless proxy == "disable" || proxy.nil?
157
+ conn.proxy = proxy
158
+ end
159
+
148
160
  # set basic authentication
149
161
  conn.basic_auth username, password unless username.nil? || password.nil?
150
162
 
@@ -162,98 +174,167 @@ module Inspec::Resources
162
174
  attr_reader :inspec
163
175
 
164
176
  def initialize(inspec, http_method, url, opts)
165
- unless inspec.command("curl").exist?
177
+ http_cmd = inspec.os.windows? ? "Invoke-WebRequest" : "curl"
178
+ unless inspec.command(http_cmd).exist?
166
179
  raise Inspec::Exceptions::ResourceSkipped,
167
- "curl is not available on the target machine"
180
+ "#{http_cmd} is not available on the target machine"
168
181
  end
169
-
170
- @ran_curl = false
182
+ @ran_http = false
171
183
  @inspec = inspec
172
184
  super(http_method, url, opts)
173
185
  end
174
186
 
175
187
  def status
176
- run_curl
188
+ run_http
177
189
  @status
178
190
  end
179
191
 
180
192
  def body
181
- run_curl
193
+ run_http
182
194
  @body&.strip
183
195
  end
184
196
 
185
197
  def response_headers
186
- run_curl
198
+ run_http
187
199
  @response_headers
188
200
  end
189
201
 
190
202
  private
191
203
 
192
- def run_curl
193
- return if @ran_curl
204
+ def run_http
205
+ return if @ran_http
194
206
 
195
- cmd_result = inspec.command(curl_command)
207
+ cmd_result = inspec.command(http_command)
196
208
  response = cmd_result.stdout
197
- @ran_curl = true
209
+ @ran_http = true
198
210
  return if response.nil? || cmd_result.exit_status != 0
199
211
 
200
- # strip any carriage returns to normalize output
201
- response.delete!("\r")
212
+ if inspec.os.windows?
213
+ response = JSON.parse(response)
202
214
 
203
- # split the prelude (status line and headers) and the body
204
- prelude, remainder = response.split("\n\n", 2)
205
- loop do
206
- break unless remainder =~ %r{^HTTP/}
215
+ @status = response["StatusCode"]
216
+ @body = response["Content"]
207
217
 
208
- prelude, remainder = remainder.split("\n\n", 2)
209
- end
210
- @body = remainder
211
- prelude = prelude.lines
212
-
213
- # grab the status off of the first line of the prelude
214
- status_line = prelude.shift
215
- @status = status_line.split(" ", 3)[1].to_i
216
-
217
- # parse the rest of the prelude which will be all the HTTP headers
218
- @response_headers = {}
219
- prelude.each do |line|
220
- line.strip!
221
- key, value = line.split(":", 2)
222
- @response_headers[key] = value.strip
218
+ @response_headers = {}
219
+ response["Headers"].each do |name, value|
220
+ @response_headers["#{name}"] = value
221
+ end
222
+ else
223
+ # strip any carriage returns to normalize output
224
+ response.delete!("\r")
225
+
226
+ # split the prelude (status line and headers) and the body
227
+ prelude, remainder = response.split("\n\n", 2)
228
+ loop do
229
+ break unless remainder =~ %r{^HTTP/}
230
+
231
+ prelude, remainder = remainder.split("\n\n", 2)
232
+ end
233
+ @body = remainder
234
+ prelude = prelude.lines
235
+
236
+ # grab the status off of the first line of the prelude
237
+ status_line = prelude.shift
238
+ @status = status_line.split(" ", 3)[1].to_i
239
+
240
+ # parse the rest of the prelude which will be all the HTTP headers
241
+ @response_headers = {}
242
+ prelude.each do |line|
243
+ line.strip!
244
+ key, value = line.split(":", 2)
245
+ @response_headers[key] = value.strip
246
+ end
223
247
  end
224
248
  end
225
249
 
226
- def curl_command # rubocop:disable Metrics/AbcSize
227
- cmd = ["curl -i"]
228
-
229
- # Use curl's --head option when the method requested is HEAD. Otherwise,
230
- # the user may experience a timeout when curl does not properly close
231
- # the connection after the response is received.
232
- if http_method.casecmp("HEAD") == 0
233
- cmd << "--head"
250
+ def http_command # rubocop:disable Metrics/AbcSize
251
+ if inspec.os.windows?
252
+ load_powershell_command
234
253
  else
235
- cmd << "-X #{http_method}"
254
+ cmd = ["curl -i"]
255
+
256
+ # Use curl's --head option when the method requested is HEAD. Otherwise,
257
+ # the user may experience a timeout when curl does not properly close
258
+ # the connection after the response is received.
259
+ if http_method.casecmp("HEAD") == 0
260
+ cmd << "--head"
261
+ else
262
+ cmd << "-X #{http_method}"
263
+ end
264
+
265
+ cmd << "--noproxy '*'" if proxy == "disable"
266
+ unless proxy == "disable" || proxy.nil?
267
+ if proxy.is_a?(Hash)
268
+ cmd << "--proxy #{proxy[:uri]} --proxy-user #{proxy[:user]}:#{proxy[:password]}"
269
+ else
270
+ cmd << "--proxy #{proxy}"
271
+ end
272
+ end
273
+ cmd << "--connect-timeout #{open_timeout}"
274
+ cmd << "--max-time #{open_timeout + read_timeout}"
275
+ cmd << "--user \'#{username}:#{password}\'" unless username.nil? || password.nil?
276
+ cmd << "--insecure" unless ssl_verify?
277
+ cmd << "--data #{Shellwords.shellescape(request_body)}" unless request_body.nil?
278
+ cmd << "--location" unless max_redirects.nil?
279
+ cmd << "--max-redirs #{max_redirects}" unless max_redirects.nil?
280
+
281
+ request_headers.each do |k, v|
282
+ cmd << "-H '#{k}: #{v}'"
283
+ end
284
+
285
+ if params.nil?
286
+ cmd << "'#{url}'"
287
+ else
288
+ cmd << "'#{url}?#{params.map { |e| e.join("=") }.join("&")}'"
289
+ end
290
+
291
+ cmd.join(" ")
236
292
  end
293
+ end
237
294
 
238
- cmd << "--connect-timeout #{open_timeout}"
239
- cmd << "--max-time #{open_timeout + read_timeout}"
240
- cmd << "--user \'#{username}:#{password}\'" unless username.nil? || password.nil?
241
- cmd << "--insecure" unless ssl_verify?
242
- cmd << "--data #{Shellwords.shellescape(request_body)}" unless request_body.nil?
243
- cmd << "--location" if max_redirects > 0
244
- cmd << "--max-redirs #{max_redirects}" if max_redirects > 0
245
-
295
+ def load_powershell_command
296
+ cmd = ["Invoke-WebRequest"]
297
+ cmd << "-Method #{http_method}"
298
+ # Missing connect-timeout
299
+ cmd << "-TimeoutSec #{open_timeout + read_timeout}"
300
+ # Insecure not supported simply https://stackoverflow.com/questions/11696944/powershell-v3-invoke-webrequest-https-error
301
+ cmd << "-MaximumRedirection #{max_redirects}" unless max_redirects.nil?
302
+ request_headers["Authorization"] = """ '\"Basic ' + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(\"#{username}:#{password}\")) +'\"' """ unless username.nil? || password.nil?
303
+ request_header_string = nil
246
304
  request_headers.each do |k, v|
247
- cmd << "-H '#{k}: #{v}'"
305
+ request_header_string << " #{k} = #{v}"
248
306
  end
249
-
307
+ cmd << "-Headers @{#{request_header_string.join(";")}}" unless request_header_string.nil?
250
308
  if params.nil?
251
309
  cmd << "'#{url}'"
252
310
  else
253
311
  cmd << "'#{url}?#{params.map { |e| e.join("=") }.join("&")}'"
254
312
  end
255
313
 
256
- cmd.join(" ")
314
+ proxy_script = ""
315
+ unless proxy == "disable" || proxy.nil?
316
+ cmd << "-Proxy #{proxy[:uri]}"
317
+ cmd << "-ProxyCredential $proxyCreds"
318
+ proxy_script = <<-EOH
319
+ $secPasswd = ConvertTo-SecureString "#{proxy[:password]}" -AsPlainText -Force
320
+ $proxyCreds = New-Object System.Management.Automation.PSCredential -ArgumentList "#{proxy[:user]}",$secPasswd
321
+ EOH
322
+ end
323
+
324
+ command = cmd.join(" ")
325
+ body = "\'#{request_body}\'"
326
+ script = <<-EOH
327
+ $body = #{body.strip unless request_body.nil?}
328
+ $Body = $body | ConvertFrom-Json
329
+ #convert to hashtable
330
+ $HashTable = @{}
331
+ foreach ($property in $Body.PSObject.Properties) {
332
+ $HashTable[$property.Name] = $property.Value
333
+ }
334
+ $response = #{command} -Body $HashTable -UseBasicParsing
335
+ $response | Select-Object -Property * | ConvertTo-json # We use `Select-Object -Property * ` to get around an odd PowerShell error
336
+ EOH
337
+ proxy_script.strip + "\n" + script.strip
257
338
  end
258
339
  end
259
340
  end
@@ -0,0 +1,57 @@
1
+ module Inspec::Resources
2
+ class Ibmdb2Conf < Inspec.resource(1)
3
+ name "ibmdb2_conf"
4
+
5
+ supports platform: "unix"
6
+ supports platform: "windows"
7
+
8
+ desc "Use the ibmdb2_conf InSpec audit resource to test the configuration values of IBM Db2 database."
9
+ example <<~EXAMPLE
10
+ describe ibmdb2_conf(db2_executable_file_path: "path_to_db2_binary", db_instance: "db2inst1") do
11
+ its("output") { should_not be_empty }
12
+ its("output") { should include("Audit buffer size (4KB) (AUDIT_BUF_SZ) = 0")}
13
+ end
14
+ EXAMPLE
15
+
16
+ attr_reader :output
17
+
18
+ def initialize(opts = {})
19
+ if inspec.os.platform?("unix")
20
+ @db2_executable_file_path = opts[:db2_executable_file_path]
21
+ @db_instance = opts[:db_instance]
22
+ raise Inspec::Exceptions::ResourceFailed, "Can't connect to IBM DB2 without db2_executable_file_path, db_instance options provided." if @db2_executable_file_path.nil? || @db_instance.nil?
23
+ end
24
+ @output = run_command
25
+ end
26
+
27
+ def to_s
28
+ "IBM Db2 Conf"
29
+ end
30
+
31
+ private
32
+
33
+ def run_command
34
+ # attach to the db2 instance and get the configuration
35
+ if inspec.os.platform?("unix")
36
+ cmd = inspec.command("#{@db2_executable_file_path} attach to #{@db_instance}\; #{@db2_executable_file_path} get database manager configuration")
37
+ out = cmd.stdout + "\n" + cmd.stderr
38
+
39
+ # check if following specific error is there. Sourcing the db2profile to resolve the error.
40
+ if cmd.exit_status != 0 && out =~ /SQL10007N Message "-1390" could not be retrieved. Reason code: "3"/
41
+ cmd = inspec.command(". ~/sqllib/db2profile\; #{@db2_executable_file_path} attach to #{@db_instance}\; #{@db2_executable_file_path} get database manager configuration")
42
+ out = cmd.stdout + "\n" + cmd.stderr
43
+ end
44
+ elsif inspec.os.platform?("windows")
45
+ # set-item command set the powershell to run the db2 commands.
46
+ cmd = inspec.command("set-item -path env:DB2CLP -value \"**$$**\"\; db2 get database manager configuration")
47
+ out = cmd.stdout + "\n" + cmd.stderr
48
+ end
49
+
50
+ if cmd.exit_status != 0 || out =~ /Can't connect to IBM Db2 server/ || out.downcase =~ /^error:.*/
51
+ raise Inspec::Exceptions::ResourceFailed, "IBM Db2 query with error: #{out}"
52
+ else
53
+ cmd.stdout.gsub(/\n|\r/, ",").split(",").reject { |n| n.nil? || n.empty? }.map { |n| n.strip.gsub!(/\s+/, " ") }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,69 @@
1
+ module Inspec::Resources
2
+ class Lines
3
+ attr_reader :output
4
+
5
+ def initialize(raw, desc)
6
+ @output = raw
7
+ @desc = desc
8
+ end
9
+
10
+ def to_s
11
+ @desc
12
+ end
13
+ end
14
+
15
+ class Ibmdb2Session < Inspec.resource(1)
16
+ name "ibmdb2_session"
17
+
18
+ supports platform: "unix"
19
+ supports platform: "windows"
20
+
21
+ desc "Use the ibmdb2_session InSpec audit resource to test SQL commands run against a IBM Db2 database."
22
+ example <<~EXAMPLE
23
+ describe ibmdb2_session(db2_executable_file_path: "path_to_db2_binary", db_instance: "db2inst1", db_name: "sample").query('list database directory') do
24
+ its('output') { should_not match(/sample/) }
25
+ end
26
+ EXAMPLE
27
+
28
+ def initialize(opts = {})
29
+ @db_name = opts[:db_name]
30
+ if inspec.os.platform?("unix")
31
+ @db2_executable_file_path = opts[:db2_executable_file_path]
32
+ @db_instance = opts[:db_instance]
33
+ raise Inspec::Exceptions::ResourceFailed, "Can't run IBM DB2 queries without db2_executable_file_path, db_instance, db_name options provided." if @db2_executable_file_path.nil? || @db_instance.nil? || @db_name.nil?
34
+ elsif inspec.os.platform?("windows")
35
+ raise Inspec::Exceptions::ResourceFailed, "Can't run IBM DB2 queries without db_name option provided." if @db_name.nil?
36
+ end
37
+ end
38
+
39
+ def query(q)
40
+ raise Inspec::Exceptions::ResourceFailed, "#{resource_exception_message}" if resource_failed?
41
+
42
+ if inspec.os.platform?("unix")
43
+ # connect to the db and query on the database
44
+ cmd = inspec.command("#{@db2_executable_file_path} attach to #{@db_instance}\; #{@db2_executable_file_path} connect to #{@db_name}\; #{@db2_executable_file_path} #{q}\;")
45
+ out = cmd.stdout + "\n" + cmd.stderr
46
+
47
+ # check if following specific error is there. Sourcing the db2profile to resolve the error.
48
+ if cmd.exit_status != 0 && out =~ /SQL10007N Message "-1390" could not be retrieved. Reason code: "3"/
49
+ cmd = inspec.command(". ~/sqllib/db2profile\; #{@db2_executable_file_path} attach to #{@db_instance}\; #{@db2_executable_file_path} connect to #{@db_name}\; #{@db2_executable_file_path} \"#{q}\"\;")
50
+ out = cmd.stdout + "\n" + cmd.stderr
51
+ end
52
+ elsif inspec.os.platform?("windows")
53
+ # set-item command set the powershell to run the db2 commands.
54
+ cmd = inspec.command("set-item -path env:DB2CLP -value \"**$$**\"\; db2 connect to #{@db_name}\; db2 \"#{q}\"\;")
55
+ out = cmd.stdout + "\n" + cmd.stderr
56
+ end
57
+
58
+ if cmd.exit_status != 0 || out =~ /Can't connect to IBM Db2 / || out.downcase =~ /^error:.*/
59
+ raise Inspec::Exceptions::ResourceFailed, "IBM Db2 connection error: #{out}"
60
+ else
61
+ Lines.new(cmd.stdout.strip, "IBM Db2 Query: #{q}")
62
+ end
63
+ end
64
+
65
+ def to_s
66
+ "IBM Db2 Session"
67
+ end
68
+ end
69
+ end