inspec 1.29.0 → 1.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/bin/inspec +1 -1
  4. data/docs/profiles.md +14 -5
  5. data/docs/resources/iptables.md.erb +12 -5
  6. data/docs/resources/mssql_session.md.erb +11 -28
  7. data/docs/resources/mysql_session.md.erb +12 -0
  8. data/docs/resources/oracledb_session.md.erb +10 -28
  9. data/docs/resources/package.md.erb +6 -0
  10. data/docs/resources/postgres_conf.md.erb +2 -0
  11. data/examples/inheritance/controls/example.rb +0 -1
  12. data/examples/meta-profile/controls/example.rb +0 -1
  13. data/examples/profile/controls/example.rb +0 -1
  14. data/examples/profile/controls/gordon.rb +0 -1
  15. data/inspec.gemspec +1 -0
  16. data/lib/bundles/inspec-compliance/api.rb +12 -10
  17. data/lib/bundles/inspec-init/templates/profile/controls/example.rb +0 -1
  18. data/lib/inspec.rb +0 -1
  19. data/lib/inspec/backend.rb +0 -1
  20. data/lib/inspec/cli.rb +1 -1
  21. data/lib/inspec/metadata.rb +1 -1
  22. data/lib/inspec/polyfill.rb +0 -1
  23. data/lib/inspec/profile.rb +1 -1
  24. data/lib/inspec/resource.rb +1 -1
  25. data/lib/inspec/rule.rb +0 -1
  26. data/lib/inspec/runner.rb +0 -1
  27. data/lib/inspec/version.rb +1 -1
  28. data/lib/matchers/matchers.rb +0 -1
  29. data/lib/resources/apache.rb +0 -1
  30. data/lib/resources/apache_conf.rb +0 -1
  31. data/lib/resources/audit_policy.rb +0 -1
  32. data/lib/resources/auditd_conf.rb +0 -1
  33. data/lib/resources/auditd_rules.rb +0 -1
  34. data/lib/resources/command.rb +0 -1
  35. data/lib/resources/directory.rb +7 -3
  36. data/lib/resources/docker.rb +30 -3
  37. data/lib/resources/etc_group.rb +0 -1
  38. data/lib/resources/file.rb +0 -1
  39. data/lib/resources/grub_conf.rb +0 -1
  40. data/lib/resources/inetd_conf.rb +0 -1
  41. data/lib/resources/kernel_module.rb +0 -1
  42. data/lib/resources/kernel_parameter.rb +0 -1
  43. data/lib/resources/limits_conf.rb +0 -1
  44. data/lib/resources/login_def.rb +0 -1
  45. data/lib/resources/mssql_session.rb +62 -14
  46. data/lib/resources/mysql.rb +0 -1
  47. data/lib/resources/mysql_conf.rb +0 -1
  48. data/lib/resources/mysql_session.rb +15 -6
  49. data/lib/resources/nginx_conf.rb +95 -0
  50. data/lib/resources/ntp_conf.rb +0 -1
  51. data/lib/resources/oracledb_session.rb +109 -12
  52. data/lib/resources/os_env.rb +0 -1
  53. data/lib/resources/package.rb +47 -3
  54. data/lib/resources/packages.rb +0 -1
  55. data/lib/resources/parse_config.rb +0 -1
  56. data/lib/resources/passwd.rb +0 -1
  57. data/lib/resources/postgres.rb +9 -5
  58. data/lib/resources/postgres_conf.rb +12 -3
  59. data/lib/resources/postgres_session.rb +0 -1
  60. data/lib/resources/powershell.rb +0 -1
  61. data/lib/resources/processes.rb +0 -1
  62. data/lib/resources/registry_key.rb +0 -1
  63. data/lib/resources/service.rb +1 -1
  64. data/lib/resources/ssh_conf.rb +0 -1
  65. data/lib/resources/ssl.rb +0 -1
  66. data/lib/utils/database_helpers.rb +77 -0
  67. data/lib/utils/filter_array.rb +0 -1
  68. data/lib/utils/find_files.rb +0 -1
  69. data/lib/utils/nginx_parser.rb +4 -2
  70. data/lib/utils/simpleconfig.rb +0 -1
  71. metadata +18 -2
@@ -0,0 +1,95 @@
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+
5
+ require 'utils/nginx_parser'
6
+
7
+ # STABILITY: Experimental
8
+ # This resouce needs a proper interace to the underlying data, which is currently missing.
9
+ # Until it is added, we will keep it experimental.
10
+ #
11
+ # TODO: Support it on Windows. To do so, we need to recognize the base os and how
12
+ # it combines the file path. Calling `File.join` or similar methods may lead to errors
13
+ # when running remotely.
14
+ module Inspec::Resources
15
+ class NginxConf < Inspec.resource(1)
16
+ name 'nginx_conf'
17
+ desc 'Use the nginx_conf InSpec resource to test configuration data '\
18
+ 'for the NginX web server located in /etc/nginx/nginx.conf on '\
19
+ 'Linux and UNIX platforms.'
20
+ example "
21
+ describe nginx_conf.params ...
22
+ describe nginx_conf('/path/to/my/nginx.conf').params ...
23
+ "
24
+
25
+ attr_reader :contents
26
+
27
+ def initialize(conf_path = nil)
28
+ @conf_path = conf_path || '/etc/nginx/nginx.conf'
29
+ @contents = {}
30
+ return skip_resource 'The `nginx_conf` resource is currently not supported on Windows.' if inspec.os.windows?
31
+ end
32
+
33
+ def params
34
+ @params ||= parse_nginx(@conf_path)
35
+ rescue StandardError => e
36
+ skip_resource e.message
37
+ @params = {}
38
+ end
39
+
40
+ def to_s
41
+ "nginx_conf #{@conf_path}"
42
+ end
43
+
44
+ private
45
+
46
+ def read_content(path)
47
+ return @contents[path] if @contents.key?(path)
48
+ file = inspec.file(path)
49
+ if !file.file?
50
+ return skip_resource "Can't find file \"#{path}\""
51
+ end
52
+ @contents[path] = file.content
53
+ end
54
+
55
+ def parse_nginx(path)
56
+ return nil if inspec.os.windows?
57
+ content = read_content(path)
58
+ data = NginxConfig.parse(content)
59
+ resolve_references(data, File.dirname(path))
60
+ rescue StandardError => _
61
+ raise "Cannot parse NginX config in #{path}."
62
+ end
63
+
64
+ # Cycle through the complete parsed data structure and try to find any
65
+ # calls to `include`. In NginX, this is used to embed data from other
66
+ # files into the current data structure.
67
+ #
68
+ # The method steps through the object structure that is passed in to
69
+ # find any calls to 'include' and returns the object structure with the
70
+ # included data merged in.
71
+ #
72
+ # @param data [Hash] data structure from NginxConfig.parse
73
+ # @param rel_path [String] the relative path from which this config is read
74
+ # @return [Hash] data structure with references included
75
+ def resolve_references(data, rel_path)
76
+ # Walk through all array entries to find more references
77
+ return data.map { |x| resolve_references(x, rel_path) } if data.is_a?(Array)
78
+
79
+ # Return any data that we cannot step into to find more `include` calls
80
+ return data unless data.is_a?(Hash)
81
+
82
+ # Any call to `include` gets its data read, parsed, and merged back
83
+ # into the current data structure
84
+ if data.key?('include')
85
+ data.delete('include').flatten
86
+ .map { |x| File.expand_path(x, rel_path) }
87
+ .map { |path| parse_nginx(path) }
88
+ .map { |e| data.merge!(e) }
89
+ end
90
+
91
+ # Walk through the remaining hash fields to find more references
92
+ Hash[data.map { |k, v| [k, resolve_references(v, rel_path)] }]
93
+ end
94
+ end
95
+ end
@@ -2,7 +2,6 @@
2
2
  # copyright: 2015, Vulcano Security GmbH
3
3
  # author: Christoph Hartmann
4
4
  # author: Dominik Richter
5
- # license: All rights reserved
6
5
 
7
6
  require 'utils/simpleconfig'
8
7
 
@@ -1,42 +1,139 @@
1
1
  # encoding: utf-8
2
2
  # author: Nolan Davidson
3
- # license: All rights reserved
3
+ # author: Christoph Hartmann
4
+ # author: Dominik Richter
5
+
6
+ require 'hashie/mash'
7
+ require 'utils/database_helpers'
8
+ require 'htmlentities'
9
+ require 'rexml/document'
10
+ require 'csv'
4
11
 
5
12
  module Inspec::Resources
13
+ # STABILITY: Experimental
14
+ # This resource needs further testing and refinement
15
+ #
6
16
  class OracledbSession < Inspec.resource(1)
7
17
  name 'oracledb_session'
8
18
  desc 'Use the oracledb_session InSpec resource to test commands against an Oracle database'
9
19
  example "
10
20
  sql = oracledb_session(user: 'my_user', pass: 'password')
11
- describe sql.query('SELECT NAME FROM v$database;') do
12
- its('stdout') { should_not match(/test/) }
21
+ describe sql.query(\"SELECT UPPER(VALUE) AS VALUE FROM V$PARAMETER WHERE UPPER(NAME)='AUDIT_SYS_OPERATIONS'\").row(0).column('value') do
22
+ its('value') { should eq 'TRUE' }
13
23
  end
14
24
  "
15
25
 
16
- attr_reader :user, :pass, :host, :sid, :sqlplus_bin
17
-
26
+ attr_reader :user, :password, :host, :service
18
27
  def initialize(opts = {})
19
28
  @user = opts[:user]
20
- @pass = opts[:pass]
29
+ @password = opts[:password] || opts[:pass]
30
+ if opts[:pass]
31
+ warn '[DEPRECATED] use `password` option to supply password instead of `pass`'
32
+ end
33
+
21
34
  @host = opts[:host] || 'localhost'
22
- @sid = opts[:sid]
35
+ @port = opts[:port] || '1521'
36
+ @service = opts[:service]
37
+
38
+ # we prefer sqlci although it is way slower than sqlplus, but it understands csv properly
39
+ @sqlcl_bin = 'sql'
23
40
  @sqlplus_bin = opts[:sqlplus_bin] || 'sqlplus'
24
- return skip_resource("Can't run Oracle checks without authentication") if @user.nil? or @pass.nil?
41
+
42
+ return skip_resource "Can't run Oracle checks without authentication" if @user.nil? || @password.nil?
43
+ return skip_resource 'You must provide a service name for the session' if @service.nil?
25
44
  end
26
45
 
27
46
  def query(q)
28
47
  escaped_query = q.gsub(/\\/, '\\\\').gsub(/"/, '\\"')
29
- cmd = inspec.command("echo \"#{escaped_query}\" | #{@sqlplus_bin} -s #{@user}/#{@pass}@#{@host}/#{@sid}")
48
+ # escape tables with $
49
+ escaped_query = escaped_query.gsub('$', '\\$')
50
+
51
+ p = nil
52
+ # check if sqlplus is available and prefer that
53
+ if inspec.command(@sqlplus_bin).exist?
54
+ bin = @sqlplus_bin
55
+ opts = "SET MARKUP HTML ON\nSET FEEDBACK OFF"
56
+ p = :parse_html_result
57
+ elsif inspec.command(@sqlcl_bin).exist?
58
+ bin = @sqlcl_bin
59
+ opts = "set sqlformat csv\nSET FEEDBACK OFF"
60
+ p = :parse_csv_result
61
+ end
62
+
63
+ return skip_resource("Can't find suitable Oracle CLI") if p.nil?
64
+ command = "echo \"#{opts}\n#{verify_query(escaped_query)}\nEXIT\" | #{bin} -s #{@user}/#{@password}@//#{@host}:#{@port}/#{@service}"
65
+ cmd = inspec.command(command)
66
+
30
67
  out = cmd.stdout + "\n" + cmd.stderr
31
68
  if out.downcase =~ /^error/
32
- skip_resource("Can't connect to Oracle instance for SQL checks.")
69
+ # TODO: we need to throw an exception here
70
+ # change once https://github.com/chef/inspec/issues/1205 is in
71
+ warn "Could not execute the sql query #{out}"
72
+ DatabaseHelper::SQLQueryResult.new(cmd, Hashie::Mash.new({}))
33
73
  end
34
-
35
- cmd
74
+ DatabaseHelper::SQLQueryResult.new(cmd, send(p, cmd.stdout))
36
75
  end
37
76
 
38
77
  def to_s
39
78
  'Oracle Session'
40
79
  end
80
+
81
+ private
82
+
83
+ def verify_query(query)
84
+ # ensure we have a ; at the end
85
+ query + ';' if !query.strip.end_with?(';')
86
+ query
87
+ end
88
+
89
+ def parse_csv_result(stdout)
90
+ output = stdout.delete(/\r/)
91
+ table = CSV.parse(output, { headers: true })
92
+
93
+ # convert to hash
94
+ headers = table.headers
95
+
96
+ results = table.map { |row|
97
+ res = {}
98
+ headers.each { |header|
99
+ res[header.downcase] = row[header]
100
+ }
101
+ Hashie::Mash.new(res)
102
+ }
103
+ results
104
+ end
105
+
106
+ def parse_html_result(stdout) # rubocop:disable Metrics/AbcSize
107
+ result = stdout
108
+ # make oracle html valid html by removing the p tag, it does not include a closing tag
109
+ result = result.gsub('<p>', '').gsub('</p>', '').gsub('<br>', '')
110
+ doc = REXML::Document.new result
111
+ table = doc.elements['table']
112
+ hash = []
113
+ if !table.nil?
114
+ rows = table.elements.to_a
115
+ headers = rows[0].elements.to_a('th').map { |entry| entry.text.strip }
116
+ rows.delete_at(0)
117
+
118
+ # iterate over each row, first row is header
119
+ hash = []
120
+ if !rows.nil? && !rows.empty?
121
+ hash = rows.map { |row|
122
+ res = {}
123
+ entries = row.elements.to_a('td')
124
+ # ignore if we have empty entries, oracle is adding th rows in between
125
+ return nil if entries.empty?
126
+ headers.each_with_index { |header, index|
127
+ # we need htmlentities since we do not have nokogiri
128
+ coder = HTMLEntities.new
129
+ val = coder.decode(entries[index].text).strip
130
+ res[header.downcase] = val
131
+ }
132
+ Hashie::Mash.new(res)
133
+ }.compact
134
+ end
135
+ end
136
+ hash
137
+ end
41
138
  end
42
139
  end
@@ -2,7 +2,6 @@
2
2
  # copyright: 2015, Vulcano Security GmbH
3
3
  # author: Christoph Hartmann
4
4
  # author: Dominik Richter
5
- # license: All rights reserved
6
5
 
7
6
  # Usage:
8
7
  #
@@ -19,7 +19,7 @@ module Inspec::Resources
19
19
  end
20
20
  "
21
21
 
22
- def initialize(package_name = nil) # rubocop:disable Metrics/AbcSize
22
+ def initialize(package_name = nil, opts = {}) # rubocop:disable Metrics/AbcSize
23
23
  @package_name = package_name
24
24
  @name = @package_name
25
25
  @cache = nil
@@ -30,7 +30,7 @@ module Inspec::Resources
30
30
  if os.debian?
31
31
  @pkgman = Deb.new(inspec)
32
32
  elsif os.redhat? || %w{suse amazon fedora}.include?(os[:family])
33
- @pkgman = Rpm.new(inspec)
33
+ @pkgman = Rpm.new(inspec, opts)
34
34
  elsif ['arch'].include?(os[:family])
35
35
  @pkgman = Pacman.new(inspec)
36
36
  elsif ['darwin'].include?(os[:family])
@@ -46,6 +46,8 @@ module Inspec::Resources
46
46
  else
47
47
  return skip_resource 'The `package` resource is not supported on your OS yet.'
48
48
  end
49
+
50
+ evaluate_missing_requirements
49
51
  end
50
52
 
51
53
  # returns true if the package is installed
@@ -71,6 +73,14 @@ module Inspec::Resources
71
73
  def to_s
72
74
  "System Package #{@package_name}"
73
75
  end
76
+
77
+ private
78
+
79
+ def evaluate_missing_requirements
80
+ missing_requirements_string = @pkgman.missing_requirements.uniq.join(', ')
81
+ return if missing_requirements_string.empty?
82
+ skip_resource "The following requirements are not met for this resource: #{missing_requirements_string}"
83
+ end
74
84
  end
75
85
 
76
86
  class PkgManagement
@@ -78,6 +88,12 @@ module Inspec::Resources
78
88
  def initialize(inspec)
79
89
  @inspec = inspec
80
90
  end
91
+
92
+ def missing_requirements
93
+ # Each provider can provide an Array of missing requirements that will be
94
+ # combined into a `skip_resource` message
95
+ []
96
+ end
81
97
  end
82
98
 
83
99
  # Debian / Ubuntu
@@ -104,8 +120,25 @@ module Inspec::Resources
104
120
 
105
121
  # RHEL family
106
122
  class Rpm < PkgManagement
123
+ def initialize(inspec, opts)
124
+ super(inspec)
125
+
126
+ @dbpath = opts.fetch(:rpm_dbpath, nil)
127
+ end
128
+
129
+ def missing_requirements
130
+ missing_requirements = []
131
+
132
+ unless @dbpath.nil? || inspec.directory(@dbpath).directory?
133
+ missing_requirements << "RPMDB #{@dbpath} does not exist"
134
+ end
135
+
136
+ missing_requirements
137
+ end
138
+
107
139
  def info(package_name)
108
- cmd = inspec.command("rpm -qia #{package_name}")
140
+ rpm_cmd = rpm_command(package_name)
141
+ cmd = inspec.command(rpm_cmd)
109
142
  # CentOS does not return an error code if the package is not installed,
110
143
  # therefore we need to check for emptyness
111
144
  return nil if cmd.exit_status.to_i != 0 || cmd.stdout.chomp.empty?
@@ -133,6 +166,17 @@ module Inspec::Resources
133
166
  type: 'rpm',
134
167
  }
135
168
  end
169
+
170
+ private
171
+
172
+ def rpm_command(package_name)
173
+ cmd = ''
174
+ cmd += 'rpm -qia'
175
+ cmd += " --dbpath #{@dbpath}" if @dbpath
176
+ cmd += ' ' + package_name
177
+
178
+ cmd
179
+ end
136
180
  end
137
181
 
138
182
  # MacOS / Darwin implementation
@@ -2,7 +2,6 @@
2
2
  # copyright: 2017, Chef Software, Inc. <legal@chef.io>
3
3
  # author: Joshua Timberman
4
4
  # author: Alex Pop
5
- # license: All rights reserved
6
5
 
7
6
  require 'utils/filter'
8
7
 
@@ -2,7 +2,6 @@
2
2
  # copyright: 2015, Vulcano Security GmbH
3
3
  # author: Dominik Richter
4
4
  # author: Christoph Hartmann
5
- # license: All rights reserved
6
5
 
7
6
  # Usage example:
8
7
  #
@@ -2,7 +2,6 @@
2
2
  # copyright: 2015, Vulcano Security GmbH
3
3
  # author: Christoph Hartmann
4
4
  # author: Dominik Richter
5
- # license: All rights reserved
6
5
 
7
6
  # The file format consists of
8
7
  # - username
@@ -3,7 +3,6 @@
3
3
  # author: Dominik Richter
4
4
  # author: Christoph Hartmann
5
5
  # author: Aaron Lippold
6
- # license: All rights reserved
7
6
 
8
7
  module Inspec::Resources
9
8
  class Postgres < Inspec.resource(1)
@@ -11,8 +10,7 @@ module Inspec::Resources
11
10
 
12
11
  attr_reader :service, :data_dir, :conf_dir, :conf_path, :version, :cluster
13
12
  def initialize
14
- os = inspec.os
15
- if os.debian?
13
+ if inspec.os.debian?
16
14
  #
17
15
  # https://wiki.debian.org/PostgreSql
18
16
  #
@@ -31,7 +29,7 @@ module Inspec::Resources
31
29
  a version number and unversioned data directories were found.'
32
30
  nil
33
31
  else
34
- @version = version_from_dir('/var/lib/pgsql/')
32
+ @version = version_from_dir('/var/lib/pgsql')
35
33
  end
36
34
  end
37
35
  @data_dir = locate_data_dir_location_by_version(@version)
@@ -40,8 +38,14 @@ module Inspec::Resources
40
38
  @service = 'postgresql'
41
39
  @service += "-#{@version}" if @version.to_f >= 9.4
42
40
  @conf_dir ||= @data_dir
41
+
43
42
  verify_dirs
44
- @conf_path = File.join @conf_dir, 'postgresql.conf'
43
+ if !@version.nil? && !@conf_dir.empty?
44
+ @conf_path = File.join @conf_dir, 'postgresql.conf'
45
+ else
46
+ @conf_path = nil
47
+ return skip_resource 'Seems like PostgreSQL is not installed on your system'
48
+ end
45
49
  end
46
50
 
47
51
  def to_s
@@ -2,7 +2,7 @@
2
2
  # copyright: 2015, Vulcano Security GmbH
3
3
  # author: Dominik Richter
4
4
  # author: Christoph Hartmann
5
- # license: All rights reserved
5
+ # author: Aaron Lippold
6
6
 
7
7
  require 'utils/simpleconfig'
8
8
  require 'utils/find_files'
@@ -19,9 +19,13 @@ module Inspec::Resources
19
19
  "
20
20
 
21
21
  include FindFiles
22
+ include ObjectTraverser
22
23
 
23
24
  def initialize(conf_path = nil)
24
25
  @conf_path = conf_path || inspec.postgres.conf_path
26
+ if @conf_path.nil?
27
+ return skip_resource 'PostgreSQL conf path is not set'
28
+ end
25
29
  @conf_dir = File.expand_path(File.dirname(@conf_path))
26
30
  @files_contents = {}
27
31
  @content = nil
@@ -42,8 +46,13 @@ module Inspec::Resources
42
46
  res
43
47
  end
44
48
 
45
- def method_missing(name)
46
- param = params[name.to_s]
49
+ def value(key)
50
+ extract_value(key, @params)
51
+ end
52
+
53
+ def method_missing(*keys)
54
+ keys.shift if keys.is_a?(Array) && keys[0] == :[]
55
+ param = value(keys)
47
56
  return nil if param.nil?
48
57
  # extract first value if we have only one value in array
49
58
  return param[0] if param.length == 1