inspec 1.29.0 → 1.30.0

Sign up to get free protection for your applications and to get access to all the features.
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